WPF+MVVM模式下ListBox选中项实现异常求助
问题描述
在WPF中通过MVVM实现ListBox选中项功能时,出现选中状态随滚动位置变化的异常:全选ListBox所有项后,仅前11个项的IsSelected被标记为true;滚动列表时,选中项的数量会增加;只有滚动到底部,所有项的IsSelected才会全部变为true。
提供的代码
XAML代码
<ListBox x:Name="DataListBox" SelectionMode="Extended" HorizontalAlignment="Left" Margin="5,5,0,0" Grid.Row="1" Grid.Column="0" Grid.RowSpan="8" VerticalAlignment="Top" Height="200" Width="200" ItemsSource="{Binding DataListBoxItemsSource, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding DataListBoxSelectedItem, UpdateSourceTrigger=PropertyChanged}" > <ListBox.InputBindings> <KeyBinding Command="ApplicationCommands.SelectAll" Modifiers="Ctrl" Key="A" /> </ListBox.InputBindings> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="IsSelected" Value="{Binding IsSelected}"/> </Style> </ListBox.ItemContainerStyle> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged" > <i:CallMethodAction TargetObject="{Binding}" MethodName="DataListBox_SelectionChanged"/> </i:EventTrigger> </i:Interaction.Triggers> </ListBox>
ViewModel代码
public async void CreateLayersTOC() { if (MapView.Active != null) { if (DataListBoxSelectedItem != null || FavoriteTabsSelectedItem != null) MainStackPanelIsEnabled = false; LayerNames = new List<string>(); await Task.Run(() => { MessageBox.Show("source count " + DataListBoxItemsSource.Count);//58 items all selected if (DataListBoxSelectedItem != null) foreach (ItemPresenter itemP in DataListBoxItemsSource) { if (itemP.IsSelected) { if (LayerNames.Contains(itemP.ToString()) == false) LayerNames.Add(itemP.ToString()); } } if (FavoriteTabsSelectedItem != null) { foreach (ItemPresenter itemP in FavListBoxItemsSource) { if (itemP.IsSelected) { if (LayerNames.Contains(itemP.ToString()) == false) LayerNames.Add(itemP.ToString()); } } } MessageBox.Show("Coll" + LayerNames.Count);//Count depends on scroll position }); //do stuff } else MessageBox.Show("Make sure to have a map available before adding layers to a map"); MainStackPanelIsEnabled = true; } public class ItemPresenter : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private readonly string _value; public ItemPresenter(string value) { _value = value; } public override string ToString() { return _value; } private bool _isSelected; public bool IsSelected { get { return _isSelected; } set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged(); } } } }
解决方案
问题根源是WPF ListBox默认启用UI虚拟化,仅实例化可见区域内的项。使用ApplicationCommands.SelectAll时,只会对已实例化的ListBoxItem设置IsSelected,未实例化的项不会触发绑定更新到ViewModel的ItemPresenter.IsSelected。
方案1:禁用UI虚拟化
直接关闭ListBox的虚拟化,让所有项立即实例化,全选时能同步更新所有ItemPresenter的选中状态:
<ListBox x:Name="DataListBox" SelectionMode="Extended" VirtualizingStackPanel.IsVirtualizing="False" <!-- 保留原有其他属性 --> >
注意:如果列表项数量较多,禁用虚拟化会导致初始化变慢、内存占用上升,需根据实际场景权衡。
方案2:自定义全选命令(推荐)
放弃默认的ApplicationCommands.SelectAll,在ViewModel中实现自定义全选逻辑,直接遍历数据源设置选中状态,同时保留UI虚拟化的性能优势:
- ViewModel中添加全选命令:
// 需先实现RelayCommand(或其他ICommand实现类) public ICommand SelectAllCommand => new RelayCommand(() => { foreach (var item in DataListBoxItemsSource) { item.IsSelected = true; } });
- 修改XAML的InputBindings:
<ListBox.InputBindings> <KeyBinding Command="{Binding SelectAllCommand}" Modifiers="Ctrl" Key="A" /> </ListBox.InputBindings>
方案3:拦截默认SelectAll命令
如果坚持使用ApplicationCommands.SelectAll,可在窗口后台拦截命令并替换为手动更新数据源的逻辑:
public YourWindow() { InitializeComponent(); // 注册ListBox的SelectAll命令拦截 CommandManager.RegisterClassCommandBinding(typeof(ListBox), new CommandBinding(ApplicationCommands.SelectAll, SelectAllExecuted)); } private void SelectAllExecuted(object sender, ExecutedRoutedEventArgs e) { var listBox = sender as ListBox; var viewModel = DataContext as YourViewModel; if (viewModel != null) { foreach (var item in viewModel.DataListBoxItemsSource) { item.IsSelected = true; } e.Handled = true; // 标记命令已处理,阻止默认逻辑 } }
内容的提问来源于stack exchange,提问作者Amadeus
相关产品推荐
相关产品推荐

