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

UWP ListView通过x:Bind切换SelectionMode触发StackOverflowException

解决UWP ListView切换SelectionMode时的StackOverflowException问题

问题原因分析

你遇到的StackOverflowException核心是属性变更通知的循环触发,结合你的代码细节,主要有两个关键诱因:

  1. 未正确指定RaisePropertyChanged的属性范围:如果你的RaisePropertyChanged实现没有使用[CallerMemberName]特性,且调用时未传入具体属性名(比如直接调用空参的RaisePropertyChanged()),会导致UWP认为所有属性都发生了变更,包括ListSelectionMode。这会形成死循环:
    • 设置ListSelectionMode → 更新ListView的SelectionMode → 触发SelectionChanged → 执行SelectionChangedCommand → 更新SelectedThreads → 触发全属性变更通知 → 再次设置ListSelectionMode → 无限递归。
  2. SelectedItem双向绑定与模式切换的联动冲突:从Single切换到Multiple模式时,ListView会自动将原SelectedItem同步到SelectedItems,触发SelectionChanged事件。如果SelectedItem的双向绑定在这个过程中触发额外的属性变更,也会间接加剧循环。

解决方案

1. 修复RaisePropertyChanged的调用(最关键)

确保所有属性的Set方法中,RaisePropertyChanged能精准通知当前属性的变更,推荐使用[CallerMemberName]自动获取属性名:

// 首先确保ViewModel基类的RaisePropertyChanged正确实现
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

// 然后修改所有属性的Set方法
private ListViewSelectionMode _listSelectionMode = ListViewSelectionMode.Single;
public ListViewSelectionMode ListSelectionMode
{
    get { return _listSelectionMode; }
    set
    {
        if (_listSelectionMode != value)
        {
            _listSelectionMode = value;
            RaisePropertyChanged(); // 自动获取当前属性名,无需手动传参
        }
    }
}

private List<object> _selectedThreads = new List<object>();
public List<object> SelectedThreads
{
    get { return _selectedThreads; }
    set
    {
        if (value != _selectedThreads)
        {
            _selectedThreads = value;
            RaisePropertyChanged(); // 自动通知"SelectedThreads"变更
        }
    }
}

private string _selectedThreadCounter;
public string SelectedThreadCounter
{
    get { return _selectedThreadCounter; }
    set
    {
        if (value != _selectedThreadCounter)
        {
            _selectedThreadCounter = value;
            RaisePropertyChanged(); // 自动通知"SelectedThreadCounter"变更
        }
    }
}

如果你的基类不支持[CallerMemberName],则手动传入属性名:RaisePropertyChanged(nameof(SelectedThreads))。

2. 避免模式切换时的不必要事件触发

在切换SelectionMode时,临时忽略SelectionChangedCommand的执行,打断循环链条:

private bool _isChangingSelectionMode;

// 切换SelectionMode的命令方法(绑定到按钮)
public void ToggleSelectionMode()
{
    _isChangingSelectionMode = true;
    try
    {
        ListSelectionMode = ListSelectionMode == ListViewSelectionMode.Single 
            ? ListViewSelectionMode.Multiple 
            : ListViewSelectionMode.Single;
    }
    finally
    {
        _isChangingSelectionMode = false;
    }
}

// 修改SelectionChangedCommand的逻辑
private RelayCommand<IList<object>> _selectionChangedCommand;
public RelayCommand<IList<object>> SelectionChangedCommand
{
    get
    {
        if (_selectionChangedCommand == null)
        {
            _selectionChangedCommand = new RelayCommand<IList<object>>(items =>
            {
                // 模式切换过程中忽略事件
                if (_isChangingSelectionMode) return;
                
                // 仅在Multiple模式下处理选中项
                if (ListSelectionMode == ListViewSelectionMode.Multiple)
                {
                    SelectedThreads = items.ToList();
                    SelectedThreadCounter = $"({SelectedThreads.Count})";
                }
            });
        }
        return _selectionChangedCommand;
    }
}

3. 优化SelectedItem绑定(可选)

在Multiple模式下,SelectedItem会自动同步为SelectedItems的第一个项,双向绑定可能导致不必要的联动。可以通过样式触发器动态调整绑定模式:

<ListView x:Name="MyList" Grid.Row="2" 
          ItemsSource="{x:Bind ViewModel.Threads, Mode=OneWay}" 
          ShowsScrollingPlaceholders="True" 
          SelectionMode="{x:Bind ViewModel.ListSelectionMode, Mode=OneWay}">
    <ListView.Style>
        <Style TargetType="ListView">
            <!-- Single模式下使用双向绑定 -->
            <Setter Property="SelectedItem" Value="{x:Bind ViewModel.Selected, Mode=TwoWay}" />
            <Style.Triggers>
                <!-- Multiple模式下改为单向绑定,避免联动冲突 -->
                <DataTrigger Binding="{x:Bind ViewModel.ListSelectionMode}" Value="Multiple">
                    <Setter Property="SelectedItem" Value="{x:Bind ViewModel.Selected, Mode=OneWay}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ListView.Style>
    <interactivity:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="SelectionChanged">
            <core:InvokeCommandAction Command="{x:Bind ViewModel.SelectionChangedCommand}" 
                                      InputConverter="{StaticResource SelectionChangedConverter}" 
                                      InputConverterParameter="{Binding ElementName=MyList}" />
        </core:EventTriggerBehavior>
    </interactivity:Interaction.Behaviors>
</ListView>

验证步骤

  1. 优先修复RaisePropertyChanged的调用,这是解决循环问题的核心;
  2. 测试模式切换,如果仍有问题,添加_isChangingSelectionMode的逻辑;
  3. 最后根据需求调整SelectedItem的绑定模式。

内容的提问来源于stack exchange,提问作者newone

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:24:51