Silverlight MVVM绑定以不需要的顺序更新

thm*_*shd 5 data-binding silverlight mvvm silverlight-4.0

场景:在Silverlight 4 MVVM项目中,我们有一个ListBox包含项目的控件,所选项目与ViewModel中的相应属性双向绑定.另一个控件(例如,我将其剥离为单个控件TextBox)是绑定到所选项目内容的数据.该值应在休假/焦点丢失时更新.

问题:当TextBox更改中的值并TextBox通过按Tab键离开时,一切都按预期工作 - 值会更新.但是,如果用户单击其中的其他项ListBox,则在触发setter的内容之前触发SelectedItem setter TextBox,从而没有机会处理用户输入.

屏幕

在向属性设置器添加断点时,您可以在调试器中看到在处理更新ListView之前首先应用新选择TextBox.

期望的行为:在用户选择另一个项目之前,我们需要知道当前选择的项目已被修改.不希望有一个自定义更新触发器,它会在每次按键时发出通知(我们知道这是可能的).

你能帮我吗?

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

视图模型

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");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

XAML代码背后

    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;
    }
Run Code Online (Sandbox Code Playgroud)

更新1 我提出了一个自行设计的解决方案供您查看,这可能是您接受的解决方案,我仍然希望鼓励您发表评论并提供您的提示.谢谢.

thm*_*shd 1

这是我们目前提出的一种解决方案。它的优点是将不同的任务分离到适当的层。例如,View 强制更新绑定,而 ViewModel 则告诉 View 这样做。另一个优点是它是同步处理的,例如,这允许在切换之前检查内容,并且调用堆栈保持不变,而不会引发“外部代码”(遍历Dispatcher或什至DispatcherTimer会这样做),这更有利于维护和流量控制。缺点是必须绑定和处理新事件(最后取消绑定。我仅出于示例原因提供匿名处理程序)。

到那里怎么走?

在 中ViewModelBase,实现一个新ForceBindingUpdate事件:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在 中MainViewModel,更新属性的 setter SelectedItem

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

        // Old stuff
        _selectedViewModel = value;
        OnPropertyChanged("SelectedItem");
    }
}
Run Code Online (Sandbox Code Playgroud)

更新MvvmTestView隐藏代码以实现新事件:

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;
}
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的一点是,最小的 XAML 更新:通过向XAML添加属性来TextBox命名。x:Name="ContentTextBox"TextBox

完毕。

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