ReactiveUI When任何行为依赖于何时将 ReactiveObject 添加到 ObservableColle
本文关键字:ReactiveObject 添加 ObservableColle 何时 When 任何行 依赖于 ReactiveUI | 更新日期: 2023-09-27 18:34:20
我遇到了一个奇怪的问题,不确定为什么会发生。我已经把它归结为WhenAny
或WhenAnyValue
方法。
我有两个具有父子关系的视图模型。父母有ObservableCollection
的孩子。家长监视每个孩子的HasError
属性,以便重新评估其自己的HasErrors
属性。
最初,子项被添加到ObservableCollection
,其HasErrors
属性通过WhenAnyValue
进行监视。在此之后,HasErrors
属性设置为按预期true
,因为其 Validate
方法由于WhenAnyValue
而被调用。
问题是当子项变为有效并且其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
设置可观察量时,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 行才会发生。这是某些状态似乎不同步的另一个示例。
如果你使用ReactiveList和一些聪明,你可以这样做:
var validators = Children.CreateDerivedCollection(
selector: x => x.WhenAnyValue(p => p.HasErrors).Subscribe(p => Validate()),
onRemoved: x => x.Dispose());
但更好的方法是这样做:
var allErrors = Children.CreateDerivedCollection(x => x.HasErrors);
var anyoneHasErrors = validators.Changed.StartWith(null)
.Select(_ => allErrors.Any(x => x != false));