22

我有一个将 SelectedItem 绑定到 ViewModel 的 ComboBox。

<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay}" ItemsSource="{Binding MyItems}">

当用户在 View ComboBox 中选择一个新项目时,我想显示一个提示并验证他们是否要进行更改。

在视图模型的 SetItem 属性设置器中,我显示一个对话框来确认选择。当他们说是时,它工作正常。

我的问题是,当用户单击“否”时,我不确定谁让 ComboBox 恢复到以前的值。ViewModel 中的 Property 具有正确的旧值,但在 View 中,ComboBox 显示新选择的值。

我希望用户选择一个项目,确认他们想要继续它,如果他们决定不这样做,我希望 ComboBox 恢复到上一个​​项目。

我怎样才能做到这一点?谢谢!

4

4 回答 4

26

当用户说“不”时,WPF 不知道该值已更改。就 WPF 而言,该值是用户选择的任何值。

您可以尝试提出属性更改通知:

public object SelItem
{
    get { ... }
    set
    {
        if (!CancelChange())
        {
            this.selItem = value;
        }

        OnPropertyChanged("SelItem");
    }
}

问题是,更改通知发生在选择事件的同一上下文中。因此,WPF 会忽略它,因为它已经知道属性已更改 - 更改为用户选择的项目!

您需要做的是在单独的消息中引发通知事件:

public object SelItem
{
    get { ... }
    set
    {
        if (CancelChange())
        {
            Dispatcher.BeginInvoke((ThreadStart)delegate
            {
                OnPropertyChanged("SelItem");
            });
            return;
        }

        this.selItem = value;
        OnPropertyChanged("SelItem");
    }
}

然后, WPF 将在处理完选择更改事件处理此消息,因此会将视图中的值恢复为应有的值。

您的 VM 显然需要访问当前的Dispatcher. 如果您需要有关如何执行此操作的一些指示,请参阅我关于基本 VM 类的博客文章。

于 2010-04-06T13:56:09.843 回答
14

感谢您提出这个问题和答案。Dispatcher.BeginInvoke 帮助了我,并且是我最终解决方案的一部分,但上述解决方案在我的 WPF 4 应用程序中并不完全有效。

我整理了一个小样本来找出原因。我必须添加实际上临时更改底层成员变量值的代码,以便当 WPF 重新查询 getter 时,它会看到值发生了变化。否则,UI 没有正确反映取消,并且 BeginInvoke() 调用没有做任何事情。

这是我的博客文章,其中包含显示非工作和工作实现的示例。

我的二传手最终看起来像这样:

    private Person _CurrentPersonCancellable;
    public Person CurrentPersonCancellable
    {
        get
        {
            Debug.WriteLine("Getting CurrentPersonCancellable.");
            return _CurrentPersonCancellable;
        }
        set
        {
            // Store the current value so that we can 
            // change it back if needed.
            var origValue = _CurrentPersonCancellable;

            // If the value hasn't changed, don't do anything.
            if (value == _CurrentPersonCancellable)
                return;

            // Note that we actually change the value for now.
            // This is necessary because WPF seems to query the 
            //  value after the change. The combo box
            // likes to know that the value did change.
            _CurrentPersonCancellable = value;

            if (
                MessageBox.Show(
                    "Allow change of selected item?", 
                    "Continue", 
                    MessageBoxButton.YesNo
                ) != MessageBoxResult.Yes
            )
            {
                Debug.WriteLine("Selection Cancelled.");

                // change the value back, but do so after the 
                // UI has finished it's current context operation.
                Application.Current.Dispatcher.BeginInvoke(
                        new Action(() =>
                        {
                            Debug.WriteLine(
                                "Dispatcher BeginInvoke " + 
                                "Setting CurrentPersonCancellable."
                            );

                            // Do this against the underlying value so 
                            //  that we don't invoke the cancellation question again.
                            _CurrentPersonCancellable = origValue;
                            OnPropertyChanged("CurrentPersonCancellable");
                        }),
                        DispatcherPriority.ContextIdle,
                        null
                    );

                // Exit early. 
                return;
            }

            // Normal path. Selection applied. 
            // Raise PropertyChanged on the field.
            Debug.WriteLine("Selection applied.");
            OnPropertyChanged("CurrentPersonCancellable");
        }
    }
于 2010-04-25T20:57:16.303 回答
1

另一种方法(确保您也阅读了评论):

http://amazedsaint.blogspot.com/2008/06/wpf-combo-box-cancelling-selection.html

来自链接:在没有全局变量的情况下递归调用事件处理程序问题的另一个解决方案是在程序选择更改之前取消处理程序分配,然后重新分配它。

前任:

cmb.SelectionChanged -= ComboBox_SelectionChanged;
cmb.SelectedValue = oldSel.Key;
cmb.SelectionChanged += ComboBox_SelectionChanged;
于 2010-04-06T14:01:33.840 回答
1

我的做法是让更改通过并在 Dispatcher 中 BeginInvoked 的 lambda 中执行验证。

    public ObservableCollection<string> Items { get; set; }
    private string _selectedItem;
    private string _oldSelectedItem;
    public string SelectedItem
    {
        get { return _selectedItem; }
        set {
            _oldSelectedItem = _selectedItem;
            _selectedItem = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
            }
            Dispatcher.BeginInvoke(new Action(Validate));                
        }
    }

    private void Validate()
    {            
        if (SelectedItem == "Item 5")
        {
            if (MessageBox.Show("Keep 5?", "Title", MessageBoxButton.YesNo) == MessageBoxResult.No)
            {
                SelectedItem = _oldSelectedItem;
            }
        }
    }

或在您的 ViewModel 中:

   Synchronization.Current.Post(new SendOrPostCallback(Validate), null);
于 2014-10-03T12:41:39.127 回答