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

时间:2011-05-04 07:18:28

标签: wpf mvvm

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

8 个答案:

答案 0 :(得分:6)

如果用户单击否并且您尝试还原该值然后调用OnPropertyChanged,则WPF将吞下该事件,因为它已经响应该事件。解决此问题的一种方法是使用调度程序并在当前事件上下文之外调用事件。

System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { OnPropertyChanged("ComSelectedValue"); }), null);

答案 1 :(得分:5)

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

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

在大多数情况下,它对我有用。 只需触发NotifyPropertyChanged,无需为回滚赋值。

答案 2 :(得分:4)

WPF似乎在更新UI之前验证绑定属性是否已更改。因此,简单地调用NotifyPropertyChanged()/ OnPropertyChanged()并不起作用。

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

这是我在ViewModels中处理它的令人难以置信的hackish方式;

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");
}

不漂亮,但效果可靠。

答案 3 :(得分:0)

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

这不起作用。为什么?

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

如果这令人困惑,试试这个:
循环开始:
1.用户在UI上更改值。 WPF改变目标。
2.绑定基础架构请求源自更新。
3.源自身更新(VM属性) 4.来源将控制权归还给具有约束力的内容 循环结束。

专家:在这方面找不到一些文件。以上是我相信事情是如何运作的。如果不正确,请纠正。


简答:
AFAIK,这不能仅通过纯VM代码完成。您将不得不放置一些代码隐藏代码 这是一种方式:http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html

答案 4 :(得分:0)

在大多数WPF应用程序中,您使用TwoWay模式将视图模型绑定到用户界面,然后您将设置为。

然而,这与典型的用户体验相悖,当您编辑某些内容并且无法保存时,即使您没有保存,也不会在整个应用程序中看到编辑反映您对数据库的更改。

WPF中可用的机制是Binding的UpdateSourceTrigger属性。使用此属性,您可以控制用户界面何时更新它所绑定的ViewModel。这允许您仅在用户保存他正在编辑的内容或类似内容时进行更新。

将UpdateSourceTrigger设置为Explicit的XAML绑定示例:

"{Binding Path=Privado, UpdateSourceTrigger=Explicit, Mode=TwoWay}"

当您想真正保存到ViewModel时,请致电:

UpdateSource();

答案 5 :(得分: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");
        }
     }
  }

答案 6 :(得分:0)

以下是我使用的一般流程:

  1. 我只是让更改通过ViewModel并跟踪之前传入的任何内容。 (如果您的业务逻辑要求所选项目不处于无效状态,我建议将其移至模型侧)。这种方法对使用单选按钮呈现的ListBox也很友好,因为尽快使SelectedItem setter出口不会阻止单选按钮在弹出消息框时突出显示。
  2. 无论传入的值如何,我都会立即调用OnPropertyChanged事件。
  3. 我在处理程序中放置了任何撤消逻辑,并使用SynchronizationContext.Post()调用它 (顺便说一句:SynchronizationContext.Post也适用于Windows应用商店应用。所以,如果你有共享的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("Cannot select that");
                SelectedItem = Items[0];
            }
        }
    
        public ViewModel()
        {
            Items = new List<string>();
            for (int i = 0; i < 10; ++i)
            {
                Items.Add(string.Format("Item {0}", i));
            }
        }
    }
    

答案 7 :(得分: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>

My View-Model(来自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;
        });
    }
}