初级代码游戏的专栏介绍与文章目录-CSDN博客
我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
源码指引:github源码指引_初级代码游戏的博客-CSDN博客
C#是我多年以来的业余爱好,新搞的东西能用C#的就用C#了。
WinUI3里面有个SplitView,实现了两块面板,但是是不带鼠标拖动改变大小的功能的,其设计目标不是两块面板,而是根据需要隐藏一个面板。
一般来说,移动设备上确实不需要用户改变界面比例,但是你是WinUI3啊。所以说微软脑子里都是些什么啊,不如大家都散了算了。
因为写Winforms程序的时候我非常依赖手动调整界面比例,对于不能自主调整的完全不能忍,所以,我必须实现一个SplitPanel。
目录
一、设计SplitPanel的基本原理
二、编写xaml
三、处理鼠标事件
3.1 初始化
3.2 鼠标事件
四、效果
一、设计SplitPanel的基本原理
通过鼠标改变界面大小的功能总是涉及到这些技术:
- 动态设置控件/窗口的位置和尺寸,当然这没什么难度
- 处理鼠标事件,根据鼠标位置计算鼠标移动的距离,从而改变界面比例
- 捕获鼠标,为什么需要捕获鼠标?因为鼠标移出控件/窗口之后就无法获得鼠标事件,这样能实现缩小但是无法实现变大。而捕获鼠标就是告诉操作系统即使鼠标移出也仍然要发送鼠标事件
结合以上几点,一般实现鼠标改变界面大小的实现方法是:
- 在鼠标按下时记录鼠标位置,捕获鼠标
- 在鼠标移动时根据鼠标移动距离改变界面大小
- 在鼠标释放时停止捕获鼠标
在WinUI3里面我们可以借助Grid来实现:
左面板 | 分隔条 | 右面板 |
分隔条用Border就可以了,宽度2-3像素,1个像素很难点击,7、8个像素又太粗壮不好看。
左面板、右面板任意。
二、编写xaml
<Grid><Grid.ColumnDefinitions><ColumnDefinition Width="3*" x:Name="col_mainlist" /><ColumnDefinition Width="3" /><ColumnDefinition Width="1*" x:Name="col_preview" /></Grid.ColumnDefinitions><!-- 主列表 --><controls:DataGrid Grid.Column="0" x:Name="MainDataList" BorderBrush="Black" BorderThickness="1" Margin="2" Padding="2"Height="Auto" Background="LightGray"GridLinesVisibility="All" HorizontalGridLinesBrush="Blue" VerticalGridLinesBrush="Green" AutoGenerateColumns="True" /><!-- 分隔条 --><Border Grid.Column="1" x:Name="border_split" BorderBrush="Gray" BorderThickness="3" Margin="0,2,0,2" /><!-- 预览区 --><Grid Grid.Column="2" BorderBrush="Black" BorderThickness="1" Margin="2" Padding="2">。。。。。。</Grid></Grid>
这个Grid只有一行,所以没有定义行。有三个列,分别对应左面板、分隔条、右面板。分隔条固定为3个像素,左面板和右面板为3:1分配。左面板是DataGrid,右面板是个子Grid。
左右面板都有x:Name,因为我们需要同时改变左右面板的大小。你说改变DataGrid和子Grid的大小,让总Grid自适应行不行?这种自适应的东西很诡异,我是不太放心的。
用作分隔条的Border上也有x:Name,我们设置鼠标事件都在它上面。
这段xaml中的主列表和预览区是我实际的内容,和要实现的SplitPanel功能没有任何关系。
三、处理鼠标事件
3.1 初始化
//设置拖动手柄border_split.PointerPressed += OnPointerPressed;border_split.PointerReleased += OnPointerReleased;
在窗口的构造函数里增加上述代码。为什么没有设置PointerMoved事件?因为没有按下鼠标的时候处理PointerMoved是没有意义的。
3.2 鼠标事件
private void OnPointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e){FrameworkElement? control = sender as FrameworkElement;if (null == control) return;try{foreach (var pointer in control.PointerCaptures){//control.PointerCaptures.Contains(e.Pointer)是无效的,必须用PointerId来比较if (pointer.PointerId== e.Pointer.PointerId){ChangeControlSize(control, e);}}}catch (Exception ex){_ = ShowMessageBox(ex.Message, "Exception");}}private void OnPointerReleased(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e){FrameworkElement? control = sender as FrameworkElement;if (null == control) return;try{foreach (var pointer in control.PointerCaptures){//control.PointerCaptures.Contains(e.Pointer)是无效的,必须用PointerId来比较if (pointer.PointerId == e.Pointer.PointerId){control.PointerMoved -= OnPointerMoved; ;this.Title = "PointerReleased";control.ReleasePointerCapture(e.Pointer);}}}catch (Exception ex){_ = ShowMessageBox(ex.Message, "Exception");}}private void OnPointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e){FrameworkElement? control = sender as FrameworkElement;if (null == control) return;if (!control.CapturePointer(e.Pointer)){this.Title = "CapturePointer error";return;}control.PointerMoved += OnPointerMoved; ;pos = e.GetCurrentPoint(control).Position.X;this.Title = "PointerPressed " + pos.ToString() + " - " + control.ActualWidth.ToString()+ " control.PointerCaptures "+ control.PointerCaptures.Count.ToString();}double pos = 0;private void ChangeControlSize(FrameworkElement? control, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e){if (null == control) return;double move = e.GetCurrentPoint(control).Position.X - pos;if (move >= 0){if (col_preview.ActualWidth - move < 0) return;}else{if (col_mainlist.ActualWidth + move < 0) return;}col_preview.Width = new GridLength(col_preview.ActualWidth - move);col_mainlist.Width = new GridLength(col_mainlist.ActualWidth + move);}
这段代码还是比较严谨的,左右面板的名字可以随意替换。唯一不太严谨的是记录鼠标初始位置的pos,一般不建议全局变量用这样简单的方式命名。
这个代码里面有一部分没有完善处理:假设了只有一个鼠标,但仍然判断了是否正在进行鼠标捕获(根据这个假设和鼠标按下才处理鼠标移动,并不需要检查是否是正在捕获的鼠标)。严谨的方式是根据鼠标ID来分别处理,设想,如果有两个鼠标同时操作会怎么样?
处理是否是正在捕获的鼠标的时候遇到了一个坑,我已经在代码里指出。并不能简单地用鼠标对象是否在集合中来判断,必须一个一个比较ID。
这段代码只处理了宽度,如果要做纵向的,稍微修改就可以了。
四、效果
差不多就是这个意思了。当然,最好能把鼠标改成专门的鼠标形状。
(这里是文档结束)