【问题标题】:Using ReactiveUI's BindTo() to update a XAML property generates a warning使用 ReactiveUI 的 BindTo() 更新 XAML 属性会生成警告
【发布时间】:2015-05-20 14:21:09
【问题描述】:

我正在尝试更新视图 XAML 中元素的属性:

this.WhenAnyValue(x => x.ViewModel.IsEnabled).BindTo(this, x => x.MyButton.IsEnabled);

这按预期工作,但是,它会在运行时生成警告:

POCOObservableForProperty:rx_bindto_test.MainWindow 是 POCO 类型,不会发送更改通知,WhenAny 只会返回一个值!

我可以通过将表达式更改为:

this.WhenAnyValue(x => x.ViewModel.IsEnabled).Subscribe(b => MyButton.IsEnabled = b);

但我仍然想知道为什么它不能与 BindTo() 一起正常工作。

编辑:即使是常规的 BindOneWayBind 也会生成此警告。

  1. 我在这里做错了什么?
  2. 真的有必要将ViewModel 定义为View 的依赖属性以便能够观察到它吗? (当我将它声明为 View 上的常规属性时,ReactiveUI 会生成相同的 POCO 警告)我不能简单地让它从 ReactiveObject 继承,因为 C# 不支持多重继承。

MainWindow.xaml.cs

public partial class MainWindow : Window, IViewFor<MyViewModel>, IEnableLogger {
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel",
        typeof(MyViewModel), typeof(MainWindow));

    public MyViewModel ViewModel {
        get { return (MyViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    object IViewFor.ViewModel {
        get { return ViewModel; }
        set { ViewModel = (MyViewModel)value; }
    }

    public MainWindow() {
        InitializeComponent();

        this.WhenAnyValue(x => x.ViewModel).BindTo(this, x => x.DataContext);

        this.WhenAnyValue(x => x.ViewModel.IsEnabled).BindTo(this, x => x.MyButton.IsEnabled);

        ViewModel = new MyViewModel();
        ViewModel.IsEnabled = true;
    }
}

MainWindow.xaml

<Window x:Class="rx_bindto_test.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">
    <Grid>
        <Button x:Name="MyButton">My Button</Button>
    </Grid>
</Window>

MyViewModel.cs

public class MyViewModel : ReactiveObject, IEnableLogger {
    private bool isEnabled;

    public bool IsEnabled {
        get { return isEnabled; }
        set { this.RaiseAndSetIfChanged(ref isEnabled, value); }
    }
}

【问题讨论】:

    标签: c# wpf mvvm system.reactive reactiveui


    【解决方案1】:

    我认为混淆来自这样一个事实,即您在“MyButton”解析而不是 ViewModel 上收到警告。

    MyButton 是一个“常量”对象,没有任何生命周期(INPC 和 DependencyObject 都没有),因此您可以放心地忽略此警告。

    或者,您可以注册以下额外的属性解析器,它的行为类似于框架元素的每个内部字段的 POCO(减去警告),它非常接近 XAML 中的每个控件(我相信):

    Locator.CurrentMutable.Register(() => new CustomPropertyResolver(), typeof(ICreatesObservableForProperty));
    
    public class CustomPropertyResolver : ICreatesObservableForProperty
    {
        public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false)
        {
            if (!typeof(FrameworkElement).IsAssignableFrom(type))
                return 0;
            var fi = type.GetTypeInfo().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
              .FirstOrDefault(x => x.Name == propertyName);
    
            return fi != null ? 2 /* POCO affinity+1 */ : 0;
        }
    
        public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, System.Linq.Expressions.Expression expression, bool beforeChanged = false)
        {
            var foo = (FrameworkElement)sender;
            return Observable.Return(new ObservedChange<object, object>(sender, expression), new DispatcherScheduler(foo.Dispatcher))
                .Concat(Observable.Never<IObservedChange<object, object>>());
        }
    }
    

    【讨论】:

    • 这是正确答案。该警告是正确的,只是非常具有误导性,因为它不包括不可观察的 WhenAny(由 BindTo 或 OneWayBind 使用)中使用的属性。 XAML 中的任何命名 UI 元素始终是不可观察的,因此如果您绑定到任何命名元素,您可能会在每个包含命名 UI 元素的视图中看到此警告。
    • 为什么MyButton 需要是可观察的?我不是想观察它,只是在其他一些可观察到的变化时尝试更新它的值。是不是说每当ViewModel 属性发生变化时我都会遇到麻烦(因为它仍然会更新旧ViewModel 上的MyButton)?
    • RxUI 不知道(没有你的帮助) MyButton 是恒定的并且不需要观察。默认情况下,它会尝试观察您给出的表达式的任何部分,这就是为什么当您编写 x.ViewModel.IsEnabled 时它支持 ViewModel 中的更改(它观察 ViewModel 属性以进行更改),并尝试对x.MyButton.IsEnabled做同样的事情
    • 虽然警告本身是有效的,但严重性有点过分了(只是因为 RxUI 绑定不检查路径元素是否是私有的)。使用 Gluck 在答案中发布的自定义属性解析器将消除警告。但是,通常可以忽略这些警告。它们的存在只是为了警告开发人员他们正在尝试观察不可观察的元素。
    • 好的,谢谢大家。对于像我这样的最终用户来说,这只是令人困惑,因为从概念上讲,我并没有真正观察目标变量,对我来说它只是一个值接收器。我忽略了一个事实,即它可能想要监视目标变量,以防它所针对的对象在运行时被替换。
    【解决方案2】:

    我认为这里的主要问题是你只希望它朝一个方向前进 - 所以你想要单向绑定。如果您尝试这样做会怎样:

    this.OneWayBind(ViewModel x => x.IsEnabled, x => y.MyButton.IsEnabled);
    

    认为这里的问题是 IsEnabled 没有与之关联的属性更改通知(但我仍在学习 RxUI,它是一个复杂的代码库!)。

    要(尝试)回答您的问题:

    1. 见上文。
    2. 是的。我也希望。但是 VM 不是由窗口类定义的,因为不是每个人都使用 MVVM。为了使管道正常工作,您需要将其作为依赖属性(否则更改时不会发送通知)。而且我刚刚(痛苦地)发现,您的视图可能在其生命周期内拥有多个虚拟机 - 如果您有一个大型 ListView 并且视图缓存的东西打开了。

    【讨论】:

    • 对于这个简化的示例,使用简单的绑定确实会更好,但实际上我正在尝试使用 whereselect 组合多个属性以将值转换为单个输出。我也不明白为什么在这种情况下目标属性需要有更改通知,我不想观察它的值 afaik。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-10
    • 1970-01-01
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    • 2012-02-18
    相关资源
    最近更新 更多