Создание четырехстороннего разделителя сетки в WPF - PullRequest
0 голосов
/ 25 октября 2019

В моем приложении WPF у меня есть четыре отдельных квадранта, каждый со своей сеткой и данными. Четыре сетки разделены GridSplitters. GridSplitters позволяют пользователю изменять размер каждого блока, выбирая горизонтальный или вертикальный разделитель.

Я пытаюсь разрешить пользователю изменять размеры сетки путем , выбирая центральную точку ( обведено красным ). Four quadrants separated by Grid Splitters

Я ожидал иметь четырехсторонний указатель мыши, который можно использовать для перетаскивания вверх, вниз, влево и вправо. Но у меня есть только возможность перемещать окна вверх и вниз ... или влево и вправо.

Four way pointer


Что я пробовал:

<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>

Simple example showing four quadrants with the example code

1 Ответ

1 голос
/ 28 октября 2019

Позвольте мне начать с небольшого изменения вашего 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 не предоставляет тестирование попаданий в несколько слоев "из коробки", , поэтому нам нужно будет выполнить пользовательский :

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. Значение Change в DragDeltaEventArgs содержит ошибки , _lastHorizontalSplitterHorizontalDragChange - это обходной путь
  3. Чтобы «перетащить» другой сплиттер, нам придется изменить размеры нашегоColumn/RowDefinitions. Чтобы избежать странного поведения отсечения (сплиттер перетаскивает столбец / строку с ним), мы должны будем использовать размер в пикселях как размер в звездах

Итак, со всем этим вот соответствующий обработчик:

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;

Я надеюсь, что я не слишком смел, чтобы оставить реализацию изменения изображения курсора для вас.

...