【问题标题】:How can I properly reset a value associated with a ComboBox, from within a PropertyChanged event?如何从 PropertyChanged 事件中正确重置与 ComboBox 关联的值?
【发布时间】:2017-01-31 13:12:27
【问题描述】:

我有一个ComboBox,它绑定到我的 ViewModel 上的一个属性(从“VM”上听到)。当用户在ComboBox 上进行选择时,它会正确更新我的 VM 中的绑定属性。在我的 UI 代码中,我在我的 VM 上订阅了 PropertyChanged 事件。

正如它应该表现的那样,当用户在ComboBox 中进行选择时,我的PropertyChanged 事件在我的UI 后端代码中正确执行。当 UI 代码捕捉到此属性的更改时,在某些选择条件下,我需要停止进程并请求用户提供更多信息。从 UI 中,我向他们发送了一个对话框。如果他们取消对话框,我会重置与ComboBox 控件SelectedValue 关联的VM 中的值。

这是我观察到的。当用户取消操作时,我的 VM 属性被设置为新值。但是,ComboBox 仍然显示现在已更改的原始条目的文本值。如何强制 ComboBox 从我的 PropertyChanged 事件中更新自身?在这种情况下,我认为这只是一个文本问题或数字索引更改,它引用了绑定集合中的文本数据。 VM 中的数据正确,但ComboBox 的显示值错误。

示例

组合框详细信息

    <ComboBox
        ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
        SelectedValue="{Binding MySelectionIsAnEnumeration}"
        DisplayMemberPath="Text"
        SelectedValuePath="EnumerationValue"
        Height="27" />

对于虚拟机上的冗长属性,我们深表歉意,但这是为了解释正在发生的事情。我的ListOfComboBoxDisplayObjects 集合表示一组枚举值,这些值存储在SelectedValuePath 内的路径中。每个值的描述性文本都是从ListOfComboBoxDisplayObjects 中提取的,这是一个为 UI 严格创建的特殊列表。这基本上将枚举值与有意义的描述配对。

ListOfComboBoxDisplayObjects 定义(来自 VM)

编辑 #1 - 将此定义添加到我的示例中

 private ObservableCollection<BindableEnumerationItem<Values>> _listOfComboBoxDisplayObjects;
public ObservableCollection<BindableEnumerationItem<Values>> ListOfComboBoxDisplayObjects
{
    get { return _listOfComboBoxDisplayObjects; }
    private set
    {
        if (value != _listOfComboBoxDisplayObjects)
        {
            _listOfComboBoxDisplayObjects= value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(ListOfComboBoxDisplayObjects)));
        }
    }
}

MySelectionIsAnEnumeration 定义(来自 VM 内)

*编辑 #1:添加此代码定义。

    private Values_mySelectionIsAnEnumeration ;
    public Values MySelectionIsAnEnumeration 
    {
        get { return _mySelectionIsAnEnumeration; }
        set
        {
            //Double-checked this-- value is different on the second-call to change this value, once the UI cancels the operation.
            if (value != _mySelectionIsAnEnumeration)
            {
                _mySelectionIsAnEnumeration= value;
                PropertyChanged(this, new PropertyChangedEventArgs(nameof(MySelectionIsAnEnumeration )));
            }
        }
    }

与 ListOfComboBoxDisplayObjects 关联的相关值

这些值是在 VM 的 ctor 中生成的。它们在整个应用程序中都是固定的。

项目#1

  • 文本:“这是一个 Foo!”
  • 值:Values.Foo

项目#2:

  • 文本:“嗨,我是 Bar。”
  • 值:Values.Bar

项目 #3:

  • 文本:“我是 Baz。我需要先问一个问题才能使用。”
  • 值:Values.Baz

PropertyChanged 事件 - 来自 UI 后端

private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "MySelectionIsAnEnumeration":
                    if (VM.MySelectionIsAnEnumeration == Values.Baz)
                    {
                        //Prompt the user and get DialogResult.
                        bool answerGiven = AskAQuestionAndGetAResult();
                        if(!answerGiven)
                            VM.MySelectionIsAnEnumeration = Values.Foo;
                    }
                    break;
            }
        }

执行上述代码后,我观察到当用户取消AskAQuestionAndGetAResult() 中的操作时,VM.MySelectionIsAnEnumeration 的值确实变为Value.Foo 的正确值。但是,完成后ComboBox仍然显示“这是Baz。我需要问一个问题才能使用。”,这显然是与Value.Baz关联的显示值。

如何更新基础 VM 属性和 CombobBox 上的显示文本以正确显示现在存储在 VM.MySelectionIsAnEnumeration 中的值?

编辑#2

下面是我的 BindableEnumerationItem 的代码 e,我在我的 Observable Collections 中用于组合框和列表框。这在我的整个应用程序中都在更简单的情况下使用,并且没有引起任何问题。请注意,这是我实际的、未更改的代码。我没有重命名任何东西。我的组合框可以绑定到每个 Item 属性以获得类型安全的属性,DisplayText 是描述符文本。

public class BindableEnumerationItem<T> : INotifyPropertyChanged 
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    private T _item;

    public BindableEnumerationItem(T item, string displayText)
    {
        _item = item;
        _displayText = displayText;
    }

    private string _displayText;
    public string DisplayText
    {
        get { return _displayText; }
        set
        {
            if (value != _displayText)
            {
                _displayText = value;
                PropertyChanged(this, new PropertyChangedEventArgs("DisplayText"));
            }
        }
    }

    public T Item
    {
        get { return _item; }
        set
        {
            _item = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Item"));
        }
    }
}

【问题讨论】:

  • 如果视图没有更新绑定,那么某处缺少通知或绑定错误。你能显示ListOfComboBoxDisplayObjects,它的类型和MySelectionIsAnEnumeration吗?
  • @Sinatr,ListOfComboBoxDisplayObjects 中的值是与 ListOfComboBoxDisplayObjects 关联的相关值部分中提供的值。在这种情况下,枚举数类型为Values。这些是我为简化示例而创建的任意值。关于绑定,所有绑定操作似乎都正常运行。通常当绑定出现问题时,调试器会将问题输出到Debug输出窗口。在这种情况下,我在重新分配后没有看到任何消息或错误。
  • 需要明确的是,当用户选择FooBar 时,绑定工作正常并且已经过双重检查。当用户适当地响应提示时,即使Baz 也能正常工作。但是,当操作被取消时,ComboBox 中的 UI 元素不会更新文本,但我确信我的 VM 中的基础值是正确的。
  • 我要求的不是值,而是该属性的源代码、类型等。如果您确定问题不仅仅是缺少通知,那么值得尝试准备 @987654321 @。您很好地描述了问题的输入和输出,但没有显示您可能(理论上)犯错的所有关键部分。
  • BindableEnumerationItemValues 是什么?似乎您忘记为Values 覆盖Equals,这是比较值的预期行为吗?没有 - 它会比较引用,因此将 ComboBox.SelectedValue 设置为 ListOfComboBoxDisplayObjects 中未引​​用的内容显然不会更改 ComboBox 的当前选择(因为它找不到项目)。覆盖Equals(和GetHashCode ofc)的Values

标签: c# wpf combobox binding


【解决方案1】:

创建一个扩展,将 xaml 中的视图模型中的命令连接到选择器,在本例中为组合框。

public partial class Extensions
{
    public static readonly DependencyProperty SelectionChangedCommandProperty = DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
    {
        var element = s as Selector;

        if (element != null)
        {
            element.SelectionChanged -= OnSelectionChanged;

            if (e.NewValue != null)
            {
                element.SelectionChanged += OnSelectionChanged;
            }
        }
    }));

    public static ICommand GetSelectionChangedCommand(UIElement element)
    {
        return (ICommand)element.GetValue(SelectionChangedCommandProperty);
    }

    public static void SetSelectionChangedCommand(UIElement element, ICommand value)
    {
        element.SetValue(SelectionChangedCommandProperty, value);
    }

    private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var element = sender as Selector;
        var command = element.GetValue(SelectionChangedCommandProperty) as ICommand;

        if (command != null && command.CanExecute(element.SelectedItem))
        {
            command.Execute(element.SelectedItem);
            e.Handled = true;
        }
    }
}

在处理值更改事件的视图模型中创建命令。

public ICommand EnumerationValueChangedCommand
{
    get
    {
        return new Command(
            () =>
                {
                    if (VM.MySelectionIsAnEnumeration == Values.Baz)
                    {
                        //Prompt the user and get DialogResult.
                        bool answerGiven = AskAQuestionAndGetAResult();
                        if (!answerGiven)
                            VM.MySelectionIsAnEnumeration = Values.Foo;
                    }
                });
    }
}

然后使用该扩展名进行绑定。 ext 是您的扩展的命名空间。

<ComboBox
    ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
    SelectedValue="{Binding MySelectionIsAnEnumeration}"
    DisplayMemberPath="Text"
    SelectedValuePath="EnumerationValue"
    ext:Extensions.SelectionChangedCommand="{Binding EnumerationValueChangedCommand}"
    Height="27" />

【讨论】:

  • 我确实喜欢这种方法,但我想检查它的来源。这段代码来自哪里,或者它是从什么代码库修改而来的?
  • 抱歉,还有一个问题。该命令需要启动一个提示窗口。它是否会从 UI 中触发提示?
  • 当我厌倦了组合框不提供开箱即用的命令可连接性并且在 MVVM 中所有逻辑都应该在视图模型中时,这是我为选择器编写的。命令执行时会在ui线程中执行,所以你的对话框最好是模态对话框,因为你确实想打断用户。
猜你喜欢
  • 2012-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多