WPF 双向绑定和 PropertyChanged

WPF TwoWay Binding and PropertyChanged

所以假设有一个枚举

    enum SampleEnum 
    {
        Item1,
        Item2
    }

然后是组合框

    <ComboBox ItemsSource="{Binding SomeItemSource}"    
        SelectedItem="{Binding 
            Path=ItemX,
            Mode=TwoWay,
            UpdateSourceTrigger=PropertyChanged,
            Converter={StaticResource ResourceKey=SomeConverter}}">

组合框有一个 ViewModel 作为其 DataContext

    ViewModel : INotifyPropertyChanged, ...
    {
        ...

        public SampleEnum ItemX
        {
            get => model.GetItemX();
            set
            {
                model.SetItemX(value);
                RaisePropertyChanged();
            }
        }

        ...
    }

并且RaisePropertyChanged([CallerMemberName] string caller = "")用属性的名字调用PropertyChanged

但是.

当我 运行 我的代码,打开我的 CheckBox,select 一项时,我得到以下行为:我的代码输入 setter,设置模型的值,引发 PropertyChanged,然后调用我的 getter,检索值,但它永远不会达到 ComboBoxComboBox 显示我手动选择的值,而不是访问者 return 编辑的值。

例如如果你将 get => SampleEnum.Item2 重写为 return 总是相同的值,ComboBox 仍然会显示我在 UI 中手选的值,而不是 return ] 由访问器编辑,即使我 100% 确定 getter 被调用,但值传送到 Converter 和 Convrter 也是 return 的正确值。

但是 如果从任何其他地方调用 RaisePropertyChanged(nameof(ItemX))ComboBox 会立即从访问器中检索值并显示它。

简而言之,如果从 setter 调用,ComboBox 会忽略 PropertyChanged,但在任何其他情况下它都可以正常工作。直接指定 属性 的名称(而不是依赖编译器服务)或在 setter 中连续调用多个 RasiePropertyChanged 是行不通的。

一般来说,组合框中 select 的值和 getter 的 return 是相同的,但有时模型会拒绝提供的值和取而代之的是 return 默认值。不是最好的行为,但它是可能的。在这种情况下,用户会被误导 ComboBox 的哪一项实际上是 selected.

我只是想知道这个访问器有什么特别之处以至于 ComboBox 忽略了它。

tl;dr:您要做的是数据验证。这是一个已解决的问题:您可以使用 IDataErrorInfo, or in your view with ValidationRules 在您的视图模型中实现验证。其中任何一个 with WPF 而不是反对它。反对 WPF 几乎总是失败的提议。

您还可以为禁用无效项的 ComboBox 编写 ItemContainerStyle,或者您的视图模型可以更新 ComboBox 项集合以排除当前不可选择的任何项。我更喜欢这种方法:与 "here, you can choose any of these options -- BZZZT, LOL, WRONG CHOICE!" 相比,仅向他们提供他们 可以 选择的选项似乎更友好。

如果您在他们做出选择后知道哪些选项有效,那么您几乎肯定也可以事先知道。


ComboBox ignores PropertyChanged if invoked from setter, but in any other case it works perfectly fine.

没错。 ComboBox 仍在处理它从用户那里获得的更改,并且在 setter 完成后的某个时间才会完成。 ComboBox 将忽略当前正在更新的 属性 上的任何 PropertyChanged 事件。这是设计使然。

标准解决方法是以 ApplicationIdle 优先级调用 BeginInvoke(),并在委托中引发 PropertyChanged。在 ComboBox 完全完成当前选择更改事件后,这将产生再次引发 PropertyChanged 的​​效果。但这不是视图模型应该关心的。

正如你所说,"Not the best behaviour"。最好首先编写 拒绝 错误值的验证,而不是像上面那样编写一个奇怪的解决方法。