如何使用WPF MVVM回滚组合框的SelectedValue?

6

我有一些类似的东西,它会弹出给用户确认更改。如果他点击否,我将在视图模型中将选定的值设置为以前的选择。但在视图中显示不正确。请帮忙。


.xaml 代码: <ComboBox SelectedValuePath=Code DisplayMemberPath=Display SelectedValie={Binding ComSelectedValue} /> - gowri
.cs(ViewModel)ComSelectedValue = SomeCodeoftheItemsSource; 在显示中没有改变选定的值。 - gowri
你在 ViewModel 中的 set 方法后面调用了 OnPropertyChanged 吗?你的绑定没有指定 UpdateSourceTrigger? - Willem
我已经调用了OnPropertyChanged,即使使用更新源触发器也无效。还有一点信息,我在其属性更改事件中将相同的属性设置为先前的值。 - gowri
8个回答

12

.NET 4.5.1+的非常简单的解决方案:

<ComboBox SelectedItem="{Binding SelectedItem, Delay=10}" ItemsSource="{Binding Items}"  />

这对我在所有情况下都有效。只需触发NotifyPropertyChanged而不进行值分配即可回滚。


2
如果可以的话,我会给这个答案点赞很多次,因为我最喜欢它了。 - NielW
1
救了我的命。喜欢这个答案。 - Eric Wood

9
如果用户点击“否”并尝试恢复值,然后调用OnPropertyChanged,WPF将会吞噬该事件,因为它已经在响应该事件。解决这个问题的一种方法是使用调度程序,并在当前事件上下文之外调用事件。
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { OnPropertyChanged("ComSelectedValue"); }), null);

这个方法可行,但我不敢相信你必须这样做。肯定有更好的方法来完成这个任务吧? - Dan
谢谢!我简直不敢相信我因此浪费了一天的工作时间。很抱歉我只能给一个赞! - Ben

4

WPF似乎在更新UI之前验证绑定属性是否已更改。因此,仅调用NotifyPropertyChanged()/OnPropertyChanged()并不能解决问题。

问题在于,由于属性没有更改,WPF认为它不需要刷新组合框。

以下是我在ViewModels中处理它的可怕方法;

private int _someProperty = -1;
public int SomeProperty
{
    if (_someProperty != -1 && doYesNo() == Yes)
    {
       _someProperty = value;
    }
    else if (_someProperty != -1)
    {
       int oldValue = _someProperty;
       _someProperty = -1; // Set to a different/invalid value so WPF sees a change
       Dispatcher.BeginInvoke(new Action(() => { SomeProperty = oldValue; }));
    }
    else 
    {
       _someProperty = value;
    }
    NotifyPropertyChanged("SomeProperty");
}

这不太好看,但它可靠地工作。


0

假设:
- 当用户从ComboBox中选择某个值时,您会显示一个带有消息和OKCancel按钮的对话框。
- 如果用户按下OK,则一切正常。 :)
- 如果用户按下取消,则您会说vmPropSelectedValue=previousValue。

这不起作用。为什么?

我没有确切的答案,但我相信当您显示对话框时,系统刚刚更改了所选值,并通过绑定基础结构通知了源。如果此时(当源具有控制权)您现在从VM代码更改ViewModel属性的值,您期望会触发INotifyPropertyChanged的OnPropertyChanged,您期望会要求WPF使用您请求的值更新目标。但是,WPF尚未完成循环-它仍在等待源将控件返回给它。因此,它只是拒绝您的请求(否则它将进入无限循环)。

如果这很令人困惑,请尝试以下操作:
循环开始:
1. 用户更改UI上的值。 WPF更改目标。
2.绑定基础结构请求源更新自身。
3.源更新自身(VM属性)。
4.源将控件返回到绑定基础架构。
循环结束。

专家们:在这方面找不到一些文档。以上是我对事情运作方式的看法。如果不正确,请纠正。


简短回答:
据我所知,这不能仅通过纯虚拟机代码完成。您将不得不放置一些代码后端代码。
以下是一种方法:http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html


我赞同publicgk所说的。在绑定事务中间,您无法更改值。即使您设法更改该值(例如通过OnPropertyChanging事件),UI也会忽略您,除非您取消设置并重置DataContext,这很糟糕。因此,您必须像上面的链接中那样变得有点肮脏,实际上这是最干净的方式。 - Dominic

0

如果您尝试异步触发属性更改事件会怎样呢?这类似于Shaun和NebulaSleuth的示例。

  public int SomeProperty
  {
     get { return m_someProperty; }
     set
     {
        if (value == m_someProperty)
           return;

        if (doYesNo() == No)
        {
           // Don't update m_someProperty but let the UI know it needs to retrieve the value again asynchronously.
           Application.Current.Dispatcher.BeginInvoke((Action) (() => NotifyOfPropertyChange("SomeProperty")));
        }
        else
        {
           m_someProperty = value;
           NotifyOfPropertyChange("SomeProperty");
        }
     }
  }

0
在大多数WPF应用程序中,您使用TwoWay模式将视图模型绑定到用户界面,然后就可以开始了。
然而,这与典型的用户体验相矛盾,当您编辑某些内容并且没有保存时,即使您没有将更改保存到数据库中,您也不会在整个应用程序中看到该编辑反映出来。
WPF中可用的机制是Binding的UpdateSourceTrigger属性。使用此属性,您可以控制用户界面何时更新其绑定的ViewModel。这使您可以仅在用户保存他正在编辑的内容或类似情况下进行更新。
以下是UpdateSourceTrigger设置为Explicit的XAML Binding示例:
"{Binding Path=Privado, UpdateSourceTrigger=Explicit, Mode=TwoWay}"

当你想要真正保存到ViewModel时,调用:

UpdateSource();

0

我知道这是一个旧帖子,但似乎没有人以正确的方式完成这个任务。我使用了System.Interactivity.Triggers和Prism来处理SelectionChanged事件,并手动触发SelectedItem。这将防止UI和View-Model中不必要的选定项更改。

我的视图:

<Window x:Class="Lind.WPFTextBlockTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:Lind.WPFTextBlockTest"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
    Title="MainWindow" Height="649" Width="397">
<Window.DataContext>
    <vm:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
    <ComboBox ItemsSource="{Binding Data}" SelectedItem="{Binding SelectedData, Mode=OneWay}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <prism:InvokeCommandAction Command="{Binding TryChangeSelectedData}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ComboBox>
</StackPanel>

我的视图模型(来自Prism 5的BindeableBase和DelegateCommand):

public class MainWindowViewModel : BindableBase
{
    public ObservableCollection<string> Data { get; private set; }
    private string selectedData;
    public string SelectedData
    {
        get { return selectedData; }
        set
        {
            SetProperty(ref selectedData, value);
        }
    }
    public DelegateCommand<SelectionChangedEventArgs> TryChangeSelectedData { get; private set; }
    public MainWindowViewModel()
    {
        Data = new ObservableCollection<string>() { "Foo", "Bar", "Dick", "Head" };
        SelectedData = Data.First();
        TryChangeSelectedData = new DelegateCommand<SelectionChangedEventArgs>(args =>
        {
            var newValue = args.AddedItems.Cast<string>().FirstOrDefault();
            if (newValue == "Dick")
                this.OnPropertyChanged(() => this.SelectedData);
            else
                SelectedData = newValue;
        });
    }
}

0

这是我通常使用的一般流程:

  1. 我只是让更改通过ViewModel并跟踪之前传递的任何内容。(如果您的业务逻辑要求所选项目不处于无效状态,建议将其移至Model侧)。这种方法也适用于使用单选按钮呈现的ListBox,因为使SelectedItem setter尽快退出不会防止单选按钮在消息框弹出时被突出显示。
  2. 无论传入的值如何,我立即调用OnPropertyChanged事件。
  3. 我将任何撤消逻辑放在处理程序中,并使用SynchronizationContext.Post()调用它(BTW: SynchronizationContext.Post也适用于Windows Store应用程序。因此,如果您有共享的ViewModel代码,则此方法仍将起作用)。

    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public List<string> Items { get; set; }
    
        private string _selectedItem;
        private string _previouslySelectedItem;
        public string SelectedItem
        {
            get
            {
                return _selectedItem;
            }
            set
            {
                _previouslySelectedItem = _selectedItem;
                _selectedItem = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
                }
                SynchronizationContext.Current.Post(selectionChanged, null);
            }
        }
    
        private void selectionChanged(object state)
        {
            if (SelectedItem != Items[0])
            {
                MessageBox.Show("不能选择该项");
                SelectedItem = Items[0];
            }
        }
    
        public ViewModel()
        {
            Items = new List<string>();
            for (int i = 0; i < 10; ++i)
            {
                Items.Add(string.Format("项目 {0}", i));
            }
        }
    }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接