【问题标题】:Creating a four-way grid splitter in WPF在 WPF 中创建四向网格拆分器
【发布时间】:2019-10-29 01:20:46
【问题描述】:

在我的 WPF 应用程序中,我有四个独立的象限,每个象限都有自己的网格和数据。四个网格由 GridSplitters 分隔。 GridSplitters 允许用户通过选择水平或垂直拆分器来调整每个框的大小。

我试图让用户通过选择中心点用红色圈出)来调整网格的大小。

我希望有一个可用于向上、向下、向左和向右拖动的四向鼠标指针。但是,我只能选择上下移动窗口...或左右移动。


我尝试过的:

<Grid> <!-- Main Grid that holds A, B, C, and D -->
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="5"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

        <Grid x:Name="gridA" Grid.Column="0" Grid.Row="0"/>
        <GridSplitter Grid.Column="0" Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/>

        <Grid x:Name="gridC" Grid.Column="2" Grid.Row="0"/>
        <GridSplitter Grid.Column="3" Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/>

        <Grid x:Name="gridB" Grid.Column="0" Grid.Row="2"/>
        <GridSplitter Grid.Column="1" Grid.Row="0" Width="5" HorizontalAlignment="Stretch"/>

        <Grid x:Name="gridD" Grid.Column="2" Grid.Row="2"/>
        <GridSplitter Grid.Column="1" Grid.Row="2" Width="5" HorizontalAlignment="Stretch"/>
</Grid>

【问题讨论】:

  • 嘿!过去两天我一直在为您解决问题,几乎所有工作都可以正常工作,只剩下一些需要修复的事情。你还有兴趣吗?
  • @ArtemK 当然!
  • 有什么东西阻止你接受我的回答吗?

标签: wpf xaml gridsplitter


【解决方案1】:

首先让我稍微更改一下您的 XAML,因为现在我们有四个不同的 GridSplitters,但两个就足够了:

<Grid Name="SplitGrid">    
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="5"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Grid x:Name="GridA" Grid.Column="0" Grid.Row="0" Background="Red" />
    <Grid x:Name="GridC" Grid.Column="2" Grid.Row="0" Background="Orange" />
    <Grid x:Name="GridB" Grid.Column="0" Grid.Row="2" Background="Green" />
    <Grid x:Name="GridD" Grid.Column="2" Grid.Row="2" Background="Yellow" />

    <GridSplitter x:Name="VerticalSplitter" 
                  Grid.Column="1" 
                  Grid.Row="0" 
                  Grid.RowSpan="3"     
                  HorizontalAlignment="Stretch"
                  VerticalAlignment="Stretch" 
                  Width="5" 
                  Background="Black" />

    <GridSplitter x:Name="HorizontalSplitter" 
                  Grid.Column="0" 
                  Grid.Row="1" 
                  Grid.ColumnSpan="3" 
                  Height="5" 
                  HorizontalAlignment="Stretch" 
                  Background="Black" />
</Grid>

这个标记更重要的是我们现在在两个拆分器之间有了一个交点:

为了一次拖动两个拆分器,我们需要知道什么时候应该。为此,让我们定义一个Boolean 标志:

public partial class View : Window
{
    private bool _mouseIsDownOnBothSplitters;
}

每当用户点击任一拆分器时,我们都需要更新标志(请注意,使用了 Preview 事件 - GridSplitter 实现将 Mouse 事件标记为 Handled):

void UpdateMouseStatusOnSplittersHandler(object sender, MouseButtonEventArgs e)
{
    UpdateMouseStatusOnSplitters(e);
}

VerticalSplitter.PreviewMouseLeftButtonDown += UpdateMouseStatusOnSplittersHandler;
HorizontalSplitter.PreviewMouseLeftButtonDown += UpdateMouseStatusOnSplittersHandler;

VerticalSplitter.PreviewMouseLeftButtonUp += UpdateMouseStatusOnSplittersHandler;
HorizontalSplitter.PreviewMouseLeftButtonUp += UpdateMouseStatusOnSplittersHandler;

UpdateMouseStatusOnSplitters 是这里的核心方法。 WPF 不提供“开箱即用”的多层命中测试,so we'll have to do a custom one

private void UpdateMouseStatusOnSplitters(MouseButtonEventArgs e)
{    
    bool horizontalSplitterWasHit = false;
    bool verticalSplitterWasHit = false;

    HitTestResultBehavior HitTestAllElements(HitTestResult hitTestResult)
    {
        return HitTestResultBehavior.Continue;
    }

    //We determine whether we hit our splitters in a filter function because only it tests the visual tree 
    //HitTestAllElements apparently only tests the logical tree
    HitTestFilterBehavior IgnoreNonGridSplitters(DependencyObject hitObject)
    {
        if (hitObject == SplitGrid)
        {
            return HitTestFilterBehavior.Continue;
        }

        if (hitObject is GridSplitter)
        {
            if (hitObject == HorizontalSplitter)
            {
                horizontalSplitterWasHit = true;

                return HitTestFilterBehavior.ContinueSkipChildren;
            }
            if (hitObject == VerticalSplitter)
            {
                verticalSplitterWasHit = true;

                return HitTestFilterBehavior.ContinueSkipChildren;
            }
        }

        return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
    }

    VisualTreeHelper.HitTest(SplitGrid, IgnoreNonGridSplitters, HitTestAllElements, new PointHitTestParameters(e.GetPosition(SplitGrid)));

    _mouseIsDownOnBothSplitters = horizontalSplitterWasHit && verticalSplitterWasHit;
}

现在我们可以实现并发拖动了。这将通过DragDelta 的处理程序完成。但是,有一些警告:

  1. 我们只需要实现位于顶部的拆分器的处理程序(在我的例子中是HorizontalSplitter
  2. DragDeltaEventArgs is bugged 中的 Change 值,_lastHorizontalSplitterHorizontalDragChange 是一种解决方法
  3. 要真正“拖动”另一个拆分器,我们必须更改Column/RowDefinitions 的尺寸。为了避免奇怪的剪裁行为(拆分器拖动列/行),we'll have to use the size of it in pixels as the the size of it in stars

所以,除此之外,这里是相关的处理程序:

private void HorizontalSplitter_DragDelta(object sender, DragDeltaEventArgs e)
{
    if (_mouseIsDownOnBothSplitters)
    {
        var firstColumn = SplitGrid.ColumnDefinitions[0];
        var thirdColumn = SplitGrid.ColumnDefinitions[2];

        var horizontalOffset = e.HorizontalChange - _lastHorizontalSplitterHorizontalDragChange;

        var maximumColumnWidth = firstColumn.ActualWidth + thirdColumn.ActualWidth;

        var newProposedFirstColumnWidth = firstColumn.ActualWidth + horizontalOffset;
        var newProposedThirdColumnWidth = thirdColumn.ActualWidth - horizontalOffset;

        var newActualFirstColumnWidth = newProposedFirstColumnWidth < 0 ? 0 : newProposedFirstColumnWidth;

        var newActualThirdColumnWidth = newProposedThirdColumnWidth < 0 ? 0 : newProposedThirdColumnWidth;

        firstColumn.Width = new GridLength(newActualFirstColumnWidth, GridUnitType.Star);
        thirdColumn.Width = new GridLength(newActualThirdColumnWidth, GridUnitType.Star);

        _lastHorizontalSplitterHorizontalDragChange = e.HorizontalChange;
    }
}

现在,这几乎是一个完整的解决方案。但是,它的缺点是即使您将鼠标水平移动到网格之外,VerticalSplitter 仍然会随之移动,这与默认行为不一致。为了解决这个问题,让我们将此检查添加到处理程序的代码中:

if (_mouseIsDownOnBothSplitters)
{
   var mousePositionRelativeToGrid = Mouse.GetPosition(SplitGrid);
   if (mousePositionRelativeToGrid.X > 0 && mousePositionRelativeToGrid.X < SplitGrid.ActualWidth)
   {
       //The rest of the handler's code
   }
}

最后,我们需要在拖动结束时将_lastHorizontalSplitterHorizontalDragChange 重置为零:

HorizontalSplitter.DragCompleted += (o, e) => _lastHorizontalSplitterHorizontalDragChange = 0;

希望我不要太胆小,把光标图像变化的实现交给你。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 2011-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多