Silverlight MVVM binding updates fire in undesired

2019-05-03 23:30发布

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.

Screen

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.

7条回答
地球回转人心会变
2楼-- · 2019-05-04 00:35

Solution №1

public class LazyTextBox: TextBox
{
    //bind to that property instead..
    public string LazyText
    {
        get { return (string)GetValue(LazyTextProperty); }
        set { SetValue(LazyTextProperty, value); }
    }

    public static readonly DependencyProperty LazyTextProperty =
        DependencyProperty.Register("LazyText", typeof(string), typeof(LazyTextBox), 
        new PropertyMetadata(null));

    //call this method when it's really nessasary...
    public void EnsureThatLazyTextEqualText()
    {
        if (this.Text != this.LazyText)
        {
            this.LazyText = this.Text;
        }
    }
}
查看更多
登录 后发表回答