Позвольте мне начать с небольшого изменения вашего 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>
Что более важно в этой разметке, так это то, что мыиметь точку пересечения между двумя разделителями:
![](https://i.stack.imgur.com/34aZx.png)
Чтобы перетащить два разделителя одновременно, нам нужно знать, когда мы должны. Для этого давайте определим флаг 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
. Однако есть несколько предостережений:
- Нам нужно реализовать обработчик только для сплиттера, который находится сверху (в моем случае это будет
HorizontalSplitter
) - Значение
Change
в DragDeltaEventArgs
содержит ошибки , _lastHorizontalSplitterHorizontalDragChange
- это обходной путь - Чтобы «перетащить» другой сплиттер, нам придется изменить размеры нашего
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;
Я надеюсь, что я не слишком смел, чтобы оставить реализацию изменения изображения курсора для вас.