【发布时间】:2015-02-15 00:08:30
【问题描述】:
我遇到了一个奇怪的问题,不知道为什么会发生。我已将其确定为 WhenAny 或 WhenAnyValue 方法。
我有两个具有父子关系的视图模型。父母有一个ObservableCollection 的孩子。父母监控每个孩子的HasError 属性,以便重新评估它自己的HasErrors 属性。
最初,孩子被添加到ObservableCollection 中,其HasErrors 属性由WhenAnyValue 监控。此后,HasErrors 属性按预期设置为true,因为WhenAnyValue 调用了它的Validate 方法。
问题是当孩子变为有效并且其HasErrors 属性设置为false 时,父母不会通过WhenAnyValue 得到通知,因此无法重新评估自己的HasErrors。
解决方案似乎是使用WhenAnyValue 或使用ObservableForProperty 将孩子添加到集合之后。这个顺序很重要,因为ReactiveObject 和ObservableCollection 似乎是独立的,这似乎很奇怪。
以下是不起作用的代码,但交换最后两行或使用ObservableForProperty 使其工作:
private void AddChild()
{
var child = new ChildViewModel();
Children.Add(child);
child.WhenAnyValue(p => p.HasErrors).Subscribe(p => Validate());
}
我使用的是 ReactiveUI 版本 6.2.1.1,但版本 6.0.1 中存在相同的行为。
完整代码示例:
public class ParentViewModel : ReactiveObject
{
private bool _hasErrors;
public ObservableCollection<ChildViewModel> Children { get; private set; }
public ReactiveCommand<object> AddChildCommand { get; private set; }
public bool HasErrors
{
get { return _hasErrors; }
set { this.RaiseAndSetIfChanged(ref _hasErrors, value); }
}
public ParentViewModel()
{
Children = new ObservableCollection<ChildViewModel>();
AddChildCommand = ReactiveCommand.Create();
AddChildCommand.Subscribe(p => AddChild());
}
public bool Validate()
{
HasErrors = !Children.All(p => p.Validate());
return HasErrors;
}
private void AddChild()
{
var child = new ChildViewModel();
Children.Add(child);
child.WhenAnyValue(p => p.HasErrors).Subscribe(p => Validate());
}
}
public class ChildViewModel : ReactiveObject, IDataErrorInfo
{
private string _name;
private bool _hasErrors;
public string Name
{
get { return _name; }
set { this.RaiseAndSetIfChanged(ref _name, value); }
}
public bool HasErrors
{
get { return _hasErrors; }
set { this.RaiseAndSetIfChanged(ref _hasErrors, value); }
}
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get { return Validate() ? null : "Required"; }
}
public bool Validate()
{
HasErrors = string.IsNullOrWhiteSpace(Name);
return !HasErrors;
}
}
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<StackPanel Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="HasErrors: " />
<TextBlock Text="{Binding HasErrors}"
Margin="3,0,0,0" />
</StackPanel>
<Button Content="Add"
Command="{Binding AddChildCommand}" />
<ItemsControl ItemsSource="{Binding Children}"
Height="500">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name, ValidatesOnDataErrors=True}"
Width="200" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
编辑 1: 我想我发现了一些可以解释为什么会出现问题的东西。虽然我不确定为什么会这样。
举个例子:
var child = new ChildViewModel();
child.WhenAnyValue(p => p.HasErrors).Subscribe(p => child.HasErrors = true);
child.HasErrors = false;
最初使用 WhenAnyValue 设置 observable 时,HasErrors 的值是 false。然后,订阅作为初始设置的一部分被调用,并将HasErrors 设置为true。但是,WhenAnyValue 没有接受此更改。因此,稍后将HasErrors 设置为false 时,不会将其作为更改拾取。在确定更改是否实际发生时,似乎维护了一些不同步的状态。
还有一件有趣的事:
var child = new ChildViewModel();
child.WhenAnyValue(p => p.Name).Subscribe(p => child.Name = Guid.NewGuid().ToString());
child.Name = Guid.NewGuid().ToString();
对于上面的示例,堆栈溢出直到第 3 行才发生。这是另一个似乎某些状态不同步的示例。
【问题讨论】:
-
我不太清楚你的问题是什么,但是 ReactiveUI 有一个 ReactiveList 类,它可以/可以替换很多围绕 ObservaableCollection 的逻辑。也许这会帮助您入门stackoverflow.com/questions/20225705/reactivelist-and-whenany
-
@kenny 我试过了,但结果还是一样。
-
我认为你最终会更好,因为你可以让任何 ReactiveList 调用你父母对更改的验证,我不认为你的 AddChild() 中的 child.WheyAny* 是一个好策略。
标签: c# .net reactiveui