Silverlight MVVM绑定更新以不期望的顺序更新 [英] Silverlight MVVM binding updates fire in undesired order

查看:137
本文介绍了Silverlight MVVM绑定更新以不期望的顺序更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

场景:在Silverlight 4 MVVM项目中,我们有一个 ListBox 控件包含项目,所选项目是双向绑定的ViewModel中的相应属性。另一个控件(例如,我把它删除了一个 TextBox )是绑定到所选项的内容的数据。



问题:当 TextBox中的值被更改,我们通过按Tab键离开 TextBox ,一切都按照需要工作 - 该值被更新。但是,如果用户单击 ListBox 中的其他项目,那么SelectedItem设置器将在 TextBox setter被触发,没有机会处理用户输入。





您可以在调试器中看到向属性设置器添加断点时,新的 ListView 选择是



所需行为:我们需要使用 TextBox 要知道当前选择的项目在用户选择了另一个项目之前被修改了。不需要有一个自定义更新触发器,它会在每个按键上通知(我们知道这是可能的)。



你能帮助吗?



代码(一个非常简单的例子):



ViewModel

  public abstract class ViewModelBase:INotifyPropertyChanged 
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if(handler!= null)
handler(this,new PropertyChangedEventArgs(propertyName));
}
}

public class ItemViewModel:ViewModelBase
{
private string _content;

public ItemViewModel(string initContent)
{
_content = initContent;
}

public string Content
{
get
{
return _content;
}
set
{
if(_content!= value)
{
_content = value;
OnPropertyChanged(Content);
}
}
}
}

public class MainViewModel:ViewModelBase
{
private ObservableCollection< ItemViewModel> _items =
new ObservableCollection< ItemViewModel>();
private ItemViewModel _selectedViewModel;

public ObservableCollection< ItemViewModel>项目
{
get
{
return _items;
}
}

public ItemViewModel SelectedItem
{
get
{
return _selectedViewModel;
}
set
{
if(_selectedViewModel!= value)
{
_selectedViewModel = value;
OnPropertyChanged(SelectedItem);
}
}
}
}

strong> XAML

 < Grid x:Name =LayoutRootBackground =White> 
< ListBox Height =100
Horizo​​ntalAlignment =Left
Margin =12,12,0,0
VerticalAlignment =Top
ItemsSource ={Binding Items}
SelectedItem ={Binding SelectedItem,Mode = TwoWay}
DisplayMemberPath =Content
Width =220/>
< TextBox Height =23
Horizo​​ntalAlignment =Left
Margin =12,118,0,0
Text ={Binding SelectedItem.Content,Mode = TwoWay }
VerticalAlignment =Top
Width =220/>
< / Grid>

后面的XAML代码

  public MvvmTestView()
{
InitializeComponent();

加载+ = new RoutedEventHandler(MvvmTestView_Loaded);
}

void MvvmTestView_Loaded(object sender,RoutedEventArgs e)
{
MainViewModel viewModel = new MainViewModel();
viewModel.Items.Add(新的ItemViewModel(Hello StackOverflow));
viewModel.Items.Add(new ItemViewModel(感谢Community));

DataContext = viewModel;
}

更新1
我呈现< a href =http://stackoverflow.com/questions/5338546/silverlight-mvvm-binding-updates-fire-in-undesired-order/5394399#5394399>自我设计的解决方案供您查看,这可能是被接受的,我仍然希望鼓励你发表评论并给出你的提示。谢谢。

解决方案

这是我们目前提出的一个解决方案。它具有将不同任务分解为适当层的优点。例如,View强制执行绑定更新,而ViewModel会通知View来执行此操作。另一个优点是它同步处理,例如允许在切换之前检查内容,并且调用堆栈保持不变,而不会增加外部代码(Going over Dispatcher 甚至 DispatcherTimer 将这样做),这对维护和流量控制更有利。一个缺点是新事件必须被绑定和处理(最终没有绑定,我仅提供一个匿名处理程序,例如原因)。



如何获取在 ViewModelBase 中,实现一个新的 ForceBindingUpdate 事件:

  public abstract class ViewModelBase:INotifyPropertyChanged 
{
// --- - 从原始代码中删除所有内容------

public event EventHandler ForceBindingUpdate;
protected void OnForceBindingUpdate()
{
var handler = ForceBindingUpdate;
if(handler!= null)
handler(this,EventArgs.Empty);
}
}

MainViewModel ,更新 SelectedItem 属性的设置者:

  set // of SelectedItem Property 
{
if(_selectedViewModel!= value)
{
//确保数据更新 - 新的部分
OnForceBindingUpdate();

//旧东西
_selectedViewModel = value;
OnPropertyChanged(SelectedItem);
}
}

更新 MvvmTestView 代码实现新事件的代码:

  void MvvmTestView_Loaded(object sender,RoutedEventArgs e)
{
//保持不变
Mvvm.MainViewModel viewModel = new Mvvm.MainViewModel();
viewModel.Items.Add(new Mvvm.ItemViewModel(Hello StackOverflow));
viewModel.Items.Add(new Mvvm.ItemViewModel(Thanks to Community));

//通过重新绑定内容属性确保数据更新 - 新部分
viewModel.ForceBindingUpdate + =(s,a)=>
{
var expr = ContentTextBox.GetBindingExpression(TextBox.TextProperty);
expr.UpdateSource();
};

//保持不变
DataContext = viewModel;
}

最后但并非最不重要的是,最小的XAML更新:给 TextBox 通过添加的名称x:Name =ContentTextBox属性到 TextBox s XAML。



完成。



其实我不知道这是否是最干净的解决方案,但它接近于我们的想法。


Scenario: In a Silverlight 4 MVVM project, we have a ListBox control containing items, the selected item is two-way-bound to the appropriate property in the ViewModel. Another control (for example reasons, I've stripped it down to a single TextBox) is data bound to the selected item's content. The value should update on leave/focus lost.

Problem: When the value in the TextBox is changed and we leave that TextBox by pressing the Tab key, everything works as desired - the value is updated. However, if the user clicks on a different item in the ListBox, then the SelectedItem setter is fired before the content of TextBox setter is fired, leaving no chance to handle the user input.

You can see in debugger, when adding breakpoints to the property setters, that the new ListView selection is applied first, before the TextBox update is processed.

Desired behavior: We need to know that the currently selected item was modified before the user has selected another item. It's not desired to have a custom update trigger which would notify on each key press (we know that's possible).

Can you help?

Code (a very simple example):

ViewModel

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class ItemViewModel : ViewModelBase
{
    private string _content;

    public ItemViewModel(string initContent)
    {
        _content = initContent;
    }

    public string Content
    {
        get
        {
            return _content;
        }
        set
        {
            if (_content != value)
            {
                _content = value;
                OnPropertyChanged("Content");
            }
        }
    }
}

public class MainViewModel : ViewModelBase
{
    private ObservableCollection<ItemViewModel> _items =
        new ObservableCollection<ItemViewModel>();
    private ItemViewModel _selectedViewModel;

    public ObservableCollection<ItemViewModel> Items
    {
        get
        {
            return _items;
        }
    }

    public ItemViewModel SelectedItem
    {
        get
        {
            return _selectedViewModel;
        }
        set
        {
            if (_selectedViewModel != value)
            {
                _selectedViewModel = value;
                OnPropertyChanged("SelectedItem");
            }
        }
    }
}

XAML

<Grid x:Name="LayoutRoot" Background="White">
    <ListBox Height="100"
             HorizontalAlignment="Left"
             Margin="12,12,0,0"
             VerticalAlignment="Top"
             ItemsSource="{Binding Items}"
             SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
             DisplayMemberPath="Content"
             Width="220" />
    <TextBox Height="23"
             HorizontalAlignment="Left"
             Margin="12,118,0,0"
             Text="{Binding SelectedItem.Content, Mode=TwoWay}"
             VerticalAlignment="Top"
             Width="220" />
</Grid>

XAML Code Behind

    public MvvmTestView()
    {
        InitializeComponent();

        Loaded += new RoutedEventHandler(MvvmTestView_Loaded);
    }

    void MvvmTestView_Loaded(object sender, RoutedEventArgs e)
    {
        MainViewModel viewModel = new MainViewModel();
        viewModel.Items.Add(new ItemViewModel("Hello StackOverflow"));
        viewModel.Items.Add(new ItemViewModel("Thanks to Community"));

        DataContext = viewModel;
    }

UPDATE 1 I present a self designed solution for you to check out, which will be probably be the accepted one, I still want to encourage you to make comments and give your hints. Thanks.

解决方案

This is one solution we currently came up with. It has the advantage that it separates different tasks to the appropriate layer. For example, the View enforces an update of the binding, while the ViewModel tells the View to do so. Another advantage is that its handled synchronously, which would for example allow to check the content right before switching away, and the call-stack remains unchanged without raising "External Code" (Going over Dispatcher or even DispatcherTimer would do so) which is better for maintenance and flow control. A disadvantage is the new Event which has to be bound and handled (and finally unbound. I present an anonymous handler only for example reasons).

How to get there?

In ViewModelBase, implement a new ForceBindingUpdate event:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    // ----- leave everything from original code ------

    public event EventHandler ForceBindingUpdate;
    protected void OnForceBindingUpdate()
    {
        var handler = ForceBindingUpdate;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }
}

In MainViewModel, update the setter of the SelectedItem property:

set // of SelectedItem Property
{
    if (_selectedViewModel != value)
    {
        // Ensure Data Update - the new part
        OnForceBindingUpdate();

        // Old stuff
        _selectedViewModel = value;
        OnPropertyChanged("SelectedItem");
    }
}

Update the MvvmTestView Code Behind to implement the new event:

void MvvmTestView_Loaded(object sender, RoutedEventArgs e)
{
    // remains unchanged
    Mvvm.MainViewModel viewModel = new Mvvm.MainViewModel();
    viewModel.Items.Add(new Mvvm.ItemViewModel("Hello StackOverflow"));
    viewModel.Items.Add(new Mvvm.ItemViewModel("Thanks to Community"));

    // Ensure Data Update by rebinding the content property - the new part
    viewModel.ForceBindingUpdate += (s, a) =>
    {
        var expr = ContentTextBox.GetBindingExpression(TextBox.TextProperty);
        expr.UpdateSource();
    };

    // remains unchanged
    DataContext = viewModel;
}

Last but not least, the minimal XAML Update: Give the TextBox a name by adding x:Name="ContentTextBox" Attribute to the TextBoxs XAML.

Done.

Actually, I don't know if this is the cleanest solution, but it gets close to what we had in mind.

这篇关于Silverlight MVVM绑定更新以不期望的顺序更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆