ReactiveUI When任何行为依赖于何时将 ReactiveObject 添加到 ObservableColle

本文关键字:ReactiveObject 添加 ObservableColle 何时 When 任何行 依赖于 ReactiveUI | 更新日期: 2023-09-27 18:34:20

我遇到了一个奇怪的问题,不确定为什么会发生。我已经把它归结为WhenAnyWhenAnyValue方法。

我有两个具有父子关系的视图模型。父母有ObservableCollection的孩子。家长监视每个孩子的HasError属性,以便重新评估其自己的HasErrors属性。

最初,子项被添加到ObservableCollection,其HasErrors属性通过WhenAnyValue进行监视。在此之后,HasErrors 属性设置为按预期true,因为其 Validate 方法由于WhenAnyValue而被调用。

问题是当子项变为有效并且其HasErrors属性设置为 false 时,父项不会通过 WhenAnyValue 收到通知,因此无法重新评估自己的HasErrors

解决方案似乎是在使用WhenAnyValue或使用ObservableForProperty将子项添加到集合中。奇怪的是,这个顺序很重要,因为ReactiveObjectObservableCollection似乎是独立的。

下面是不起作用的代码,但交换最后两行或使用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 行才会发生。这是某些状态似乎不同步的另一个示例。

ReactiveUI When任何行为依赖于何时将 ReactiveObject 添加到 ObservableColle

如果你使用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));