首先让我稍微更改一下您的 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 的处理程序完成。但是,有一些警告:
- 我们只需要实现位于顶部的拆分器的处理程序(在我的例子中是
HorizontalSplitter)
-
DragDeltaEventArgs is bugged 中的 Change 值,_lastHorizontalSplitterHorizontalDragChange 是一种解决方法
- 要真正“拖动”另一个拆分器,我们必须更改
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;
希望我不要太胆小,把光标图像变化的实现交给你。