WPF: Cancel a user selection in a databound ListBox?

前端 未结 8 775
独厮守ぢ
独厮守ぢ 2020-12-05 01:02

How do I cancel a user selection in a databound WPF ListBox? The source property is set correctly, but the ListBox selection is out of sync.

I have an MVVM app that

8条回答
  •  萌比男神i
    2020-12-05 01:23

    I had a very similar problem, the difference being that I am using ListView bound to an ICollectionView and was using IsSynchronizedWithCurrentItem rather than binding the SelectedItem property of the ListView. This worked well for me until I wanted to cancel the CurrentItemChanged event of the underlying ICollectionView, which left the ListView.SelectedItem out of sync with the ICollectionView.CurrentItem.

    The underlying problem here is keeping the view in sync with the view model. Obviously cancelling a selection change request in the view model is trivial. So we really just need a more responsive view as far as I'm concerned. I'd rather avoid putting kludges into my ViewModel to work around limitations of the ListView synchronization. On the other hand I'm more than happy to add some view-specific logic to my view code-behind.

    So my solution was to wire my own synchronization for the ListView selection in the code-behind. Perfectly MVVM as far as I'm concerned and more robust than the default for ListView with IsSynchronizedWithCurrentItem.

    Here is my code behind ... this allows changing the current item from the ViewModel as well. If the user clicks the list view and changes the selection, it will immediately change, then change back if something down-stream cancels the change (this is my desired behavior). Note I have IsSynchronizedWithCurrentItem set to false on the ListView. Also note that I am using async/await here which plays nicely, but requires a little double-checking that when the await returns, we are still in the same data context.

    void DataContextChangedHandler(object sender, DependencyPropertyChangedEventArgs e)
    {
        vm = DataContext as ViewModel;
        if (vm != null)
            vm.Items.CurrentChanged += Items_CurrentChanged;
    }
    
    private async void myListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var vm = DataContext as ViewModel; //for closure before await
        if (vm != null)
        {
            if (myListView.SelectedIndex != vm.Items.CurrentPosition)
            {
                var changed = await vm.TrySetCurrentItemAsync(myListView.SelectedIndex);
                if (!changed && vm == DataContext)
                {
                    myListView.SelectedIndex = vm.Items.CurrentPosition; //reset index
                }
            }
        }
    }
    
    void Items_CurrentChanged(object sender, EventArgs e)
    {
        var vm = DataContext as ViewModel; 
        if (vm != null)
            myListView.SelectedIndex = vm.Items.CurrentPosition;
    }
    

    Then in my ViewModel class I have ICollectionView named Items and this method (a simplified version is presented).

    public async Task TrySetCurrentItemAsync(int newIndex)
    {
        DataModels.BatchItem newCurrentItem = null;
        if (newIndex >= 0 && newIndex < Items.Count)
        {
            newCurrentItem = Items.GetItemAt(newIndex) as DataModels.BatchItem;
        }
    
        var closingItem = Items.CurrentItem as DataModels.BatchItem;
        if (closingItem != null)
        {
            if (newCurrentItem != null && closingItem == newCurrentItem)
                return true; //no-op change complete
    
            var closed = await closingItem.TryCloseAsync();
    
            if (!closed)
                return false; //user said don't change
        }
    
        Items.MoveCurrentTo(newCurrentItem);
        return true; 
    }
    

    The implementation of TryCloseAsync could use some kind of dialog service to elicit a close confirmation from the user.

提交回复
热议问题