You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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虚拟化的性能优势:

  1. ViewModel中添加全选命令:
// 需先实现RelayCommand(或其他ICommand实现类)
public ICommand SelectAllCommand => new RelayCommand(() =>
{
    foreach (var item in DataListBoxItemsSource)
    {
        item.IsSelected = true;
    }
});
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 09:15:42