小工具知识网

当前位置: 首页 > 行业知识 > C# wpf 实现任意控件(包括窗口)更多调整大小功能

行业知识

C# wpf 实现任意控件(包括窗口)更多调整大小功能

2026-11-09 06:42:09 来源:互联网转载

WPF拖动改变大小系列

���一节 Grid内控件拖动调整大小

第二节 Canvas内控件拖动调整大小

第三节 窗口拖动调整大小

第四节 附加属性实现拖动调整大小

第五章 拓展更多调整大小功能(本章)


一、添加的功能

在第四章基础上添加了如下功能。

1、任意控件DragResize

我们知道wpf的Window有DragMove功能,在鼠标左键按下事件中调用此方法就能实现拖动功能很方便。对于调整大小也可以实现类似的DragResize功能, 实际效果和点击画板拖出一个形状差不多。

代码示例如下:

 ///  /// 手动触发拖动改变大小,与Window.DragMove类似,只能在鼠标左键按下时调用。 /// 实际效果和点击画板拖出一个形状差不多。 /// 此方法为拓展方法,FrameworkElement的子类控件(即有宽高属性的控件)都可以调用此方法。 ///  ///  /// 返回Task,await等待拖动完成 ///  public static async Task DragResize(this FrameworkElement elememt) {     if (Mouse.LeftButton != MouseButtonState.Pressed)     {         throw new InvalidOperationException("Left button down to call this method");     }     if (elememt.Parent == null && elememt is not Window)     {         throw new InvalidOperationException("Element should be on the visual tree");     }     //生成Resizeable对象,第四章完整代码中。     //获取右下角Thumb     //手动触发Thumb拖动事件     //等待拖动完成 }

2、边界限制

添加一个IsResizeInBounds附加属性,表示拖动范围是否在父控件内。

代码示例如下:

public static bool GetIsResizeInBounds(DependencyObject obj){    return (bool)obj.GetValue(IsResizeInBoundsProperty);}public static void SetIsResizeInBounds(DependencyObject obj, bool value){    obj.SetValue(IsResizeInBoundsProperty, value);}/// /// 是否在父控件范围内拖动/// public static readonly DependencyProperty IsResizeInBoundsProperty =    DependencyProperty.RegisterAttached("IsResizeInBounds", typeof(bool), typeof(Resize), new PropertyMetadata(false));

第四章的拖动逻辑中添加相应的限制功能,本质上就是判断如果超出边界则控件刚好依附在边界上,代码如下:

 var dx = left - margin.Left; var dy = top - margin.Top; if (GetIsResizeInBounds(c)) {     var pos = c.GetPosition();     var parent = _resizeTarget.Parent as FrameworkElement;     Size size;     if (parent == null)     {         size.Width = SystemParameters.PrimaryScreenWidth;         size.Height = SystemParameters.PrimaryScreenHeight;     }     else     {         size.Width = parent.ActualWidth;         size.Height = parent.ActualHeight;     }     if (pos.X + dx  size.Width)     {         width = size.Width - pos.X;         right = margin.Right + c.ActualWidth - width;     }     if (pos.Y + dy  size.Height)     {         height = size.Height - pos.Y;         bottom = margin.Bottom + c.ActualHeight - height;     } }                

3、交叉拖动

交叉拖动是曾经用gdi画图时会出现的一种情况,gdi绘制的宽高可以为负数,所以可以直接穿过起点反向拖动也能绘制出图形。在wpf中的控件是不支持宽高负数的,所以我们需要用其他方式实现。

下列步骤以横向为例:

(1)判断控件边界

 if (width  

(2)固定到控件边界

SetTargetMargin为前3章的集合,根据不同控件类型比如Window是设置Left、Top、Grid则设置Margin等。minWidth是控件的MinWidth属性。margin参考第四张完整代码。

if (thumb.HorizontalAlignment == HorizontalAlignment.Left)//左拖动点{    SetTargetMargin(new Thickness(margin.Left + c.Width - minWidth, margin.Top, margin.Right - c.Width + minWidth, margin.Bottom));}else//右拖动点{    SetTargetMargin(new Thickness(margin.Left - c.Width + minWidth, margin.Top, margin.Right + c.Width - minWidth, margin.Bottom));}

(3)事件转移

//当前拖动点触发鼠标弹起事件MouseButtonEventArgs upEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left){ RoutedEvent = UIElement.MouseLeftButtonUpEvent };thumb.RaiseEvent(upEvent);//反向拖动点触发鼠标按下事件MouseButtonEventArgs downEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left){ RoutedEvent = UIElement.MouseLeftButtonDownEvent };t.RaiseEvent(downEvent);

4、拖动点模板

添加附加属性ThumbsTemplate

public static ControlTemplate GetThumbsTemplate(DependencyObject obj){    return (ControlTemplate)obj.GetValue(ThumbsTemplateProperty);}public static void SetThumbsTemplate(DependencyObject obj, ControlTemplate value){    obj.SetValue(ThumbsTemplateProperty, value);}/// /// 拖动点的模板/// public static readonly DependencyProperty ThumbsTemplateProperty =    DependencyProperty.RegisterAttached("ThumbsTemplate", typeof(ControlTemplate), typeof(Resize), new PropertyMetadata(null));

生成拖动点时会应用模板

var thumbsTemplate = GetThumbsTemplate(_resizeTarget);thumb.Template = thumbsTemplate;

5、拖动点容器模板

拖动点的容器模板,主要用于设置margin调整拖动点的整体位置,添加附加属性ThumbsPanel。

 public static ItemsPanelTemplate GetThumbsPanel(DependencyObject obj) {     return (ItemsPanelTemplate)obj.GetValue(ThumbsPanelProperty); } public static void SetThumbsPanel(DependencyObject obj, ItemsPanelTemplate value) {     obj.SetValue(ThumbsPanelProperty, value); } ///  /// 拖动点的容器,主要用于设置margin ///  public static readonly DependencyProperty ThumbsPanelProperty =     DependencyProperty.RegisterAttached("ThumbsPanel", typeof(ItemsPanelTemplate), typeof(Resize), new PropertyMetadata(null));

生成拖动点布局时会应用模板

var itemsPanel = GetThumbsPanel(_resizeTarget);_defalutPanel.ItemsPanel = itemsPanel;

6、整体模板

拖动点模板和拖动点布局模板已经很大程度灵活了使用,如果需要更高的定制性,直接使用整体模板,整体模板赋值后拖动点模板和拖动点布局模板会失效。此功能与第四章的ResizeTemplate相同但名称改为Template。基本规则是第一级控件为容器、第二级控件为Thumb类型自动识别为拖动点,拖动方向由HorizontalAlignment和VerticalAlignment决定。

7、窗口平滑拖动

之所有要对窗口拖动平滑处理是因为,自定义的调整大小只能设置Window的Left、Top、Width、Height,当窗口进行左或上拖动时右或下会出现残影,这种情况通过SetWindowPos和MoveWindow也无法改善。在不使用窗口自带的拖动功能的情况下,目前笔者研究出的方法就是使用透明窗口全屏,控件模拟窗口进行拖动。当然这种实现的限制就是一定要透明窗口,AllowTransparency为true或者WindowChrome的GlassFrameThickness为-1。

因为这种实现还不是很完美对装饰器不兼容,所以提供IsWindowDragSmooth属性,可以打开和关闭功能。

public static bool GetIsWindowDragSmooth(DependencyObject obj){    return (bool)obj.GetValue(IsWindowDragSmoothProperty);}public static void SetIsWindowDragSmooth(DependencyObject obj, bool value){    obj.SetValue(IsWindowDragSmoothProperty, value);}/// /// 拖拽窗口调整大小是否平滑处理,作用是避免拖拽窗口左上时右下闪烁。/// 此属性只对窗口有效/// 此属性为true时需要透明窗口才能生效,即AllowTransparency为true或者WindowChrome的GlassFrameThickness为-1。/// 当前版本不兼容有装饰器的窗口,拖动中装饰器可能会显示在窗口外面。/// // Using a DependencyProperty as the backing store for IsWindowDragSmooth.  This enables animation, styling, binding, etc...public static readonly DependencyProperty IsWindowDragSmoothProperty =    DependencyProperty.RegisterAttached("IsWindowDragSmooth", typeof(bool), typeof(Resize), new PropertyMetadata(false));

8、拖动事件

提供3个拖动事件,拖动开始、拖动变化、拖动结束。

代码示例如下:

/// ///  拖动开始事件/// public static readonly RoutedEvent DragResizeStartedEvent = EventManager.RegisterRoutedEvent("DragResizeStarted", RoutingStrategy.Direct, typeof(EventHandler), typeof(Resize));/// /// 拖动变化事件/// public static readonly RoutedEvent DragResizeDeltaEvent = EventManager.RegisterRoutedEvent("DragResizeDelta", RoutingStrategy.Direct, typeof(EventHandler), typeof(Resize));/// /// 拖动结束事件/// public static readonly RoutedEvent DragResizeCompletedEvent = EventManager.RegisterRoutedEvent("DragResizeCompleted", RoutingStrategy.Direct, typeof(EventHandler), typeof(Resize));

9、其他功能

(1)适应MinWidth、MinHeight

在第四章完整带的基础上将边界判断修改为控件的MinWidth、MinHeight即可。

横向

if (width >= minWidth/*原本是0*/){    //略}

纵向与横向逻辑一致,修改对应变量即可,略

(2)适应MaxWidth、MaxHeight

超过了最大值需要进行修正示例如下

横向:

if (width > c.MaxWidth){    if (thumb.HorizontalAlignment == HorizontalAlignment.Left)    {        left += width - c.MaxWidth;        right = margin.Right;    }    else    {        left = margin.Left;        right += width - c.MaxWidth;    }    width = c.MaxWidth;}

纵向与横向逻辑一致,修改对应变量即可,略。

(3)适配任意dpi

所有改变坐标以及大小的代码已经适配了任意dpi。

主要注意的就是PointToScreen得到的坐标需要dpi转换。

下列是获取dpi的方法。

static Point GetDpiFromVisual(Visual visual){    var source = PresentationSource.FromVisual(visual);    var dpiX = 96.0;    var dpiY = 96.0;    if (source?.CompositionTarget != null)    {        dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;        dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;    }    return new Point(dpiX, dpiY);}

二、完整代码

vs2022 wpf .net 6.0 项目,包含了第四章的功能,不需要重复下载。如果需要单独使用Resize.cs文件需要添加依赖,参考附录。

https://download.csdn.net/download/u013113678/88749991

上一篇:宝塔面板怎么调亮度?

下一篇:B站热门视频弹幕情感分析(含B站视频弹幕爬虫,可用于爬取任意视频的弹幕)已修改,可直接运行