当 ListView 获得焦点时,WPF GridSplitter 会卡住

WPF GridSplitter gets jammed when ListView gets focus

我的 WPF 应用程序中的 GridSplitter 行为异常。我的应用程序有一个 3 面板布局,实现为具有 5 列的网格,其中 0/2/4 有内容,1/3 有 GridSplitter。中间面板是带有 ListView 的 Grid。

一切正常,直到 ListView 获得焦点,此时拆分器大部分停止移动。您可以将它们拖动一两个像素,然后它们就会冻结。该应用程序看到一个初始运动事件,但没有进一步的。您可以重复此操作几次以将拆分器移动多个像素,但如果您尝试移动另一个拆分器,则前一个拆分器会跳回到原来的位置。侧板上还有两个分离器继续正常工作。

重新加载项目导致 ListView 失去焦点,拆分器再次开始工作。

这可能很难想象,所以制作了 a short video

XAML 非常简单。唯一的怪癖是中心面板被定义了两次,具有互斥的可见性。

<Grid Name="triptychGrid" DockPanel.Dock="Top">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" MinWidth="100"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*" MinWidth="150"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*" MinWidth="100"/>
    </Grid.ColumnDefinitions>

    <GridSplitter Grid.Column="1" Width="4" HorizontalAlignment="Left"/>
    <GridSplitter Grid.Column="3" Width="4" HorizontalAlignment="Center"/>

    <Grid Grid.Column="0" Name="leftPanel"> ... </Grid>

    <Grid Grid.Column="2" Name="launchPanel" Visibility="{Binding Path=LaunchPanelVisibility}"> ... </Grid>

    <Grid Grid.Column="2" Name="codeListGrid" Visibility="{Binding Path=CodeListVisibility}"> ... </Grid>

    <Grid Grid.Column="4" Name="rightPanel"> ... </Grid>
</Grid>

面板宽度上有事件侦听器,因此它们会在运行之间被记住。我在应用程序第一次启动时恢复了面板的宽度,但之后不要触摸它们。

在 ListView 获得焦点之前一切正常。如果它完全冻结就不会那么奇怪了。一次移动几个像素然后在触摸其他控件时跳回看起来很奇怪。

我在桌面 运行 最新的 Win10(.NET 报告 10.0.18362)和虚拟机 运行 最新的 Win7 上重现了这个。该项目是 open source.

出现此问题是因为您正在处理 ItemContainerGenerator.StatusChanged 事件。当 listView 由于网格拆分器而调整大小时,每个像素左右都会生成偶数,这会扰乱操作的流畅性。

一个快速而懒惰的解决方法是在滑动进行时取消该处理程序,您只需使用 boolean 即可实现,将其设置为 truefalse 通过处理 GridSplitterDragStartedDragOver:

  <GridSplitter Name="leftSplitter" Width="4" Grid.Column="1" HorizontalAlignment="Left" DragStarted="LeftSplitter_OnDragStarted" DragOver="LeftSplitter_OnDragOver"/>
  <GridSplitter Name="rightSplitter" Width="4" Grid.Column="3" HorizontalAlignment="Center" DragStarted="LeftSplitter_OnDragStarted" DragOver="LeftSplitter_OnDragOver"/>

处理程序看起来像这样:

private bool _isBeingDraged = false;
private void LeftSplitter_OnDragStarted(object sender, DragStartedEventArgs e)
{
        _isBeingDraged = true;
}

private void LeftSplitter_OnDragOver(object sender, DragEventArgs e)
{
        _isBeingDraged = false;
}

然后更新您的 ItemContainerGenerator_StatusChanged 以考虑该布尔值:

 private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) {
        if(_isBeingDraged)
            return;
        if (codeListView.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) {
            int index = codeListView.SelectedIndex;

            if (index >= 0) {
                ListViewItem item =
                    (ListViewItem)codeListView.ItemContainerGenerator.ContainerFromIndex(index);

                if (item != null) {
                    item.Focus();
                }
            }
        }
    }

ItemContainerGenerator 可能会做一些更复杂的事情,但这需要更多的挖掘。