C# wpf 实现任意控件(包括窗口)更多拖动功能

系列文章目录

第一章 Grid内控件拖动
第二章 Canvas内控件拖动
第三章 任意控件拖动
第四章 窗口拖动
第五章 附加属性实现任意拖动
第六章 拓展更多拖动功能(本章)


文章目录

  • 系列文章目录
  • 前言
  • 一、添加的功能
    • 1、任意控件MoveTo
    • 2、任意控件DragMove
    • 3、边界限制
    • 4、窗口最大化拖动还原
    • 5、拖动事件
  • 二、完整代码
  • 三、使用示例
    • 1、MoveTo
    • 2、DragMove
    • 3、边界限制
    • 4、窗口最大化拖动还原
    • 5、拖动事件
  • 总结


前言

上一章我们以及实现了任意控件统一的拖动功能,以及能够方便的给任意控件添加拖动了。开发过程中发现还是有些功能可以继续拓展的,比如cs代码中移动控件、响应事件后触发拖动、限制拖动范围等功能。


一、添加的功能

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

1、任意控件MoveTo

这个功能相对简单,对不同类型的容器进行判断区分不同的移动逻辑即可。
代码示例如下:

/// <summary>
/// 任意控件移动到指定坐标点
/// </summary>
/// <param name="elememt">this</param>
/// <param name="parentPoint">父容器的坐标点,之所以采样容器的坐标是因为,采样自身坐标控件位置改变后就会无效,采样屏幕坐标则需要自己换算dpi(PointToScreen不会做dpi换算)</param>
public static void MoveTo(this FrameworkElement elememt, Point parentPoint)
{var parent = VisualTreeHelper.GetParent(elememt);if (parent is Canvas){//Canvas移动逻辑}else if (elememt is Window){//Window移动逻辑}else{//Grid或Transform移动逻辑,两种都能适用任意控件}
}

在拓展一个获取位置的方法,方便MoveTo使用

/// <summary>
/// 获取控件坐标,基于父控件。Window则是桌面位置。
/// </summary>
/// <param name="elememt"></param>
public static Point GetPosition(this FrameworkElement elememt)
{var parent = VisualTreeHelper.GetParent(elememt);if (elememt is Window){var window = elememt as Window;return new Point(window!.Left, window.Top);}return elememt.TranslatePoint(new Point(0, 0), parent as UIElement);
}

2、任意控件DragMove

我们知道wpf的Window有DragMove功能,在鼠标左键按下事件中调用此方法就能实现拖动功能很方便。任意控件的DragMove也是可以实现的,我们需要使用第五章的DragMoveable对象结合手动触发事件来实现。
代码示例如下:

/// <summary>
/// 点击拖动
/// 与Window的DragMove类似,必须鼠标左键按下调用此方法。
/// await 可以等待拖动结束
/// </summary>
/// <param name="elememt">this</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static Task DragMove(this FrameworkElement elememt)
{if (Mouse.LeftButton != MouseButtonState.Pressed){throw new InvalidOperationException("Left button down to call this method");}var tcs = new TaskCompletionSource();//初始化DragMoveable对象//手动触发elememt的鼠标左键按下事件//拖动完成后tcs.SetResult();    return tcs.Task;
}

3、边界限制

添加一个IsMoveInBounds附加属性,表示拖动范围是否在父控件内。
代码示例如下:

 public static bool GetIsMoveInBounds(DependencyObject obj){return (bool)obj.GetValue(IsMoveInBoundsProperty);}public static void SetIsMoveInBounds(DependencyObject obj, bool value){obj.SetValue(IsMoveInBoundsProperty, value);}/// <summary>/// 是否在父容器区域内拖动,不会超出边界/// </summary>// Using a DependencyProperty as the backing store for IsMoveInBounds.  This enables animation, styling, binding, etc...public static readonly DependencyProperty IsMoveInBoundsProperty =DependencyProperty.RegisterAttached("IsMoveInBounds", typeof(bool), typeof(Move), new PropertyMetadata(true));

在第五章 附加属性实现任意拖动的拖动逻辑中添加相应的限制功能,比如Canvas的示例如下:

var p = _parent as Canvas;
if (GetIsMoveInBounds(c))
//修正移动范围
{if (left < 0) left = 0;if (top < 0) top = 0;if (left + c.ActualWidth > p.ActualWidth) left = p.ActualWidth - c.ActualWidth;if (top + c.ActualHeight > p.ActualHeight) top = p.ActualHeight - c.ActualHeight;
}                     

4、窗口最大化拖动还原

Windows系统的窗口最大化拖动标题时会自动恢复为普通状态的窗口,实现无边框窗口后则失去了这个功能,需要自己实现,而且恢复普通状态的窗口的位置还有一定的逻辑。
代码示例如下:

if (window.WindowState == WindowState.Maximized)
//最大化时拖动逻辑
{   //恢复为普通窗口window.WindowState = WindowState.Normal;double width = SystemParameters.PrimaryScreenWidth;//得到屏幕整体宽度double height = SystemParameters.PrimaryScreenHeight;//得到屏幕整体高度//根据鼠标的位置调整窗口位置,基本逻辑是横向为鼠标为中点,纵向为鼠标顶部,超出屏幕范围则修正到靠近的那一边。
}

5、拖动事件

提供3个拖动事件,拖动结束、拖动变化、拖动结束。
代码示例如下:

 /// <summary>///  拖动开始事件/// </summary>public static readonly RoutedEvent DragMoveStartedEvent = EventManager.RegisterRoutedEvent("DragMoveStarted", RoutingStrategy.Direct, typeof(EventHandler<DragMoveStartedEventArgs>), typeof(Move));
/// <summary>
/// 拖动变化事件
/// </summary>
public static readonly RoutedEvent DragMoveDeltaEvent = EventManager.RegisterRoutedEvent("DragMoveDelta", RoutingStrategy.Direct, typeof(EventHandler<DragMoveDeltaEventArgs>), typeof(Move));
/// <summary>
/// 拖动结束事件
/// </summary>
public static readonly RoutedEvent DragMoveCompletedEvent = EventManager.RegisterRoutedEvent("DragMoveCompleted", RoutingStrategy.Direct, typeof(EventHandler<DragMoveCompletedEventArgs>), typeof(Move));

二、完整代码

vs2022 wpf .net 6.0 项目,包含了第五章的功能,不需要重复下载。
https://download.csdn.net/download/u013113678/88513646


三、使用示例

由于本章是第五章的拓展,基本功能可以参考第五章。

1、MoveTo

xaml

<Window x:Class="WpfMove.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfMove"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><Grid><Button Width="180" Height="30" Click="Button_Click">Gird中点击按钮右移10</Button><StackPanel><Button Width="180" Height="30" Click="Button_Click">StackPanel中点击按钮右移10</Button></StackPanel><Canvas><Button Width="180" Height="30" Click="Button_Click">Canvas中点击按钮右移10</Button></Canvas></Grid>
</Window>

因为是拓展方法,所以获取到控件对象直接调用moveTo即可。
cs

using AC;
using System.Windows;
using System.Windows.Media;namespace WpfMove
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private void Button_Click(object sender, RoutedEventArgs e){var fe = sender as FrameworkElement;//获取控件的位置var p = fe.GetPosition();//右偏移10p.Offset(10, 0);fe.MoveTo(p);}}
}

效果预览
在这里插入图片描述

2、DragMove

xaml

<Window x:Class="WpfMove.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfMove"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><Grid><TextBlock Background="Aqua" TextAlignment="Center" Margin="0,80,0,0" Width="180" Height="30"  MouseDown="TextBlock_MouseDown" >Gird中点击拖动</TextBlock><StackPanel><TextBlock Background="Aqua" TextAlignment="Center" Margin="0,80,0,0" Width="180" Height="30"  MouseDown="TextBlock_MouseDown" >StackPanel中点击拖动</TextBlock></StackPanel><Canvas ><TextBlock Background="Aqua" TextAlignment="Center" Margin="0,80,0,0" Width="180" Height="30"  MouseDown="TextBlock_MouseDown" >Canvas中点击拖动</TextBlock></Canvas></Grid>
</Window>

此方法也是拓展方法,在鼠标按下事件中任意控件都可以调用此方法,可以通过await等待拖动完成。
cs

using AC;
using System;
using System.Windows;namespace WpfMove
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private async void TextBlock_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e){var fe = sender as FrameworkElement;await fe.DragMove();Console.WriteLine("拖动完成");}}
}

效果预览
在这里插入图片描述

3、边界限制

通过附加属性IsMoveInBounds设置是否限制边界,默认为false。
xaml

<Window x:Class="WpfMove.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfMove"xmlns:ac="clr-namespace:AC"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><Grid><TextBlock HorizontalAlignment="Left" Background="Aqua" TextAlignment="Center" Margin="0,80,0,0" Width="180" Height="60"  ac:Move.IsDragMoveable="True" ac:Move.IsMoveInBounds="True">拖动限制边界</TextBlock><TextBlock HorizontalAlignment="Right" Background="Aqua" TextAlignment="Center" Margin="0,80,0,0" Width="180" Height="60" ac:Move.IsDragMoveable="True" ac:Move.IsMoveInBounds="False">拖动不限制边界</TextBlock></Grid>
</Window>

效果预览
在这里插入图片描述

4、窗口最大化拖动还原

内部实现已支持最大化拖动还原,只需要设置窗口可拖动即可,使用场景是无边框窗口自定义标题栏。
xaml

<Window x:Class="WpfMove.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfMove"xmlns:ac="clr-namespace:AC"mc:Ignorable="d"WindowStyle="None"ResizeMode="NoResize"WindowState="Normal"Title="MainWindow" Height="450" Width="800"x:Name="window"><Grid><Border VerticalAlignment="Top" Height="40" Background="#333333" ac:Move.DragMoveTarget="{Binding ElementName= window}"  MouseLeftButtonDown="Border_MouseLeftButtonDown"><TextBlock Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Right"  Foreground="White" Text="标题栏拖动窗口"></TextBlock></Border></Grid>
</Window>

cs

using System.Windows;
namespace WpfMove
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private void Border_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e){if (e.ClickCount == 2) WindowState = WindowState != WindowState.Maximized ? WindowState = WindowState.Maximized : WindowState = WindowState.Normal;}}
}

效果预览
在这里插入图片描述

5、拖动事件

xaml

<Window x:Class="WpfMove.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfMove"xmlns:ac="clr-namespace:AC"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><StackPanel><TextBlock Background="Aqua" TextAlignment="Center"Margin="0,80,0,0" Width="180" Height="30"  ac:Move.IsDragMoveable="True"   ac:Move.DragMoveStarted="window_DragMoveStarted"ac:Move.DragMoveCompleted="window_DragMoveCompleted"ac:Move.DragMoveDelta="window_DragMoveDelta">StackPanel中点击拖动</TextBlock></StackPanel>
</Window>

cs

using AC;
using System;
using System.Windows;namespace WpfMove
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private void window_DragMoveStarted(object sender, DragMoveStartedEventArgs e){Console.WriteLine("拖动开始");}private void window_DragMoveCompleted(object sender, DragMoveCompletedEventArgs e){Console.WriteLine("拖动完成");}private void window_DragMoveDelta(object sender, DragMoveDeltaEventArgs e){Console.WriteLine("横向偏移:"+e.HorizontalOffset + "," +"纵向偏移:"+ e.VerticalOffset);}}
}
效果预览

在这里插入图片描述


总结

以上就是今天要讲的内容,拓展更多的拖动功能后使用变得更加方便了,灵活度也提高了。使用xmal或cs代码都能实现拖动,实现自定义标题栏也变得很简单,有了拖动事件也可以做一些撤销重做的功能。总的来说,本文的拖动功能一定程度可以作为通用的模块在项目中使用了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/136904.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【STM32】STM32的Cube和HAL生态

1.单片机软件开发的时代变化 1.单片机的演进过程 (1)第1代&#xff1a;4004、8008、Zilog那个年代&#xff08;大约1980年代之前&#xff09; (2)第2代&#xff1a;51、PIC8/16、AVR那个年代&#xff08;大约2005年前&#xff09; (3)第3代&#xff1a;51、PIC32、Cortex-M0、…

解决IDEA使用卡顿的问题

*问题&#xff1a;使用IDEA的时候卡顿 原因&#xff1a;IDEA默认分配的内存有上限 **可以查看内存分配情况及使用情况__ 解决&#xff1a; 设置JVM的启动参数&#xff1a; 进入idea的安装目录的bin文件夹 -Xms1024m -Xmx2048m -XX:ReservedCodeCacheSize1024m -XX:UseG1G…

IP-guard WebServer RCE漏洞复现

0x01 产品简介 IP-guard是由溢信科技股份有限公司开发的一款终端安全管理软件&#xff0c;旨在帮助企业保护终端设备安全、数据安全、管理网络使用和简化IT系统管理。 0x02 漏洞概述 漏洞成因 在Web应用程序的实现中&#xff0c;参数的处理和验证是确保应用安全的关键环节…

springboot中定时任务cron不生效,fixedRate指定间隔失效,只执行一次的问题

在调试计算任务的时候&#xff0c;手动重置任务为初始状态&#xff0c;但是并没有重新开始计算&#xff0c;检查定时任务代码&#xff1a; 从Scheduled(fixedRate 120000)可以看到&#xff0c;应该是间隔120秒执行一次该定时任务&#xff0c;查看后台日志&#xff0c;并没有重…

Uniapp实现多语言切换

前言 之前做项目过程中&#xff0c;也做过一次多语言切换&#xff0c;大致思想都是一样的&#xff0c;想了解的可以看下之前的文章C#WinForm实现多语言切换 使用i18n插件 安装插件 npm install vue-i18n --saveMain.js配置 // 引入 多语言包 import VueI18n from vue-i18n…

OpenGL_Learn08(坐标系统与3D空间)

目录 1. 概述 2. 局部空间 3. 世界空间 4. 观察空间 5. 剪裁空间 6. 初入3D 7. 3D旋转 8. 多个正方体 9. 观察视角 1. 概述 OpenGL希望在每次顶点着色器运行后&#xff0c;我们可见的所有顶点都为标准化设备坐标(Normalized Device Coordinate, NDC)。也就是说&#x…

关于 国产系统UOS系统Qt开发Tcp服务器外部连接无法连接上USO系统 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/134254817 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

Termius for Mac:掌控您的云端世界,安全高效的SSH客户端

你是否曾经在Mac上苦苦寻找一个好用的SSH客户端&#xff0c;让你能够远程连接到Linux服务器&#xff0c;轻松管理你的云端世界&#xff1f;现在&#xff0c;我们向你介绍一款强大而高效的SSH客户端——Termius。 Termius是一款专为Mac用户设计的SSH客户端&#xff0c;它提供了…

this.$message提示内容添加换行

0 效果 1 代码 let msgArr [只允许上传doc/docx/xls/xlsx/pdf/png/jpg/bmp/ppt/pptx/rar/zip格式文件,且单个文件大小不能超过20MB,已过滤无效的文件] let msg msgArr.join(<br/>) this.$message({dangerouslyUseHTMLString: true,message: msg,type: warning })

安卓 车轮视图 WheelView kotlin

安卓 车轮视图 WheelView kotlin 前言一、代码解析1.初始化2.初始化数据3.onMeasure4.onDraw5.onTouchEvent6.其他 6.ItemObject二、完整代码总结 前言 有个需求涉及到类似这个视图&#xff0c;于是在网上找了个轮子&#xff0c;自己改吧改吧用&#xff0c;拿来主义当然后&…

物联网中的毫米波雷达:连接未来的智能设备

随着物联网&#xff08;IoT&#xff09;技术的飞速发展&#xff0c;连接设备的方式和效能变得越来越重要。毫米波雷达技术作为一种先进的感知技术&#xff0c;正在为物联网设备的连接和智能化提供全新的可能性。本文将深入探讨毫米波雷达在物联网中的应用&#xff0c;以及它是如…

高防CDN与高防服务器:为什么高防服务器不能完全代替高防CDN

在当今的数字化时代&#xff0c;网络安全已经成为企业不容忽视的关键问题。面对不断增长的网络威胁和攻击&#xff0c;许多企业采取了高防措施以保护其网络和在线资产。然而&#xff0c;高防服务器和高防CDN是两种不同的安全解决方案&#xff0c;各自有其优势和局限性。在本文中…

Apache APISIX Dashboard 未经认证访问导致 RCE(CVE-2021-45232)漏洞复现

漏洞描述 Apache APISIX 是一个动态、实时、高性能的 API 网关&#xff0c;而 Apache APISIX Dashboard 是一个简单易用的前端界面&#xff0c;用于管理 Apache APISIX。 在 2.10.1 之前的 Apache APISIX Dashboard 中&#xff0c;Manager API 使用了两个框架&#xff0c;并在…

Mysql数据库 12.SQL语言 触发器

一、触发器&#xff08;操作日志表&#xff09; 1.介绍 不需要主动调用的一种储存过程&#xff0c;是一个能够完成特定过程&#xff0c;存储在数据库服务器上的SQL片段。 对当前表中数据增删改查的一种记录<日志表>&#xff0c;根据触发器自动执行&#xff0c;记录当前…

图解三傻排序 选择排序、冒泡排序、插入排序

&#xff08;1&#xff09;选择排序 // 交换 void swap(int arr[], int i, int j) {int tmp arr[i];arr[i] arr[j];arr[j] tmp; }// 选择排序 void selectionSort(int arr[],int len) {if (len < 2) return;for (int minIndex, i 0; i < len - 1; i) {minIndex i;f…

【11】使用透视投影建立一个3D空间的测试

核心操作&#xff1a; 1.proj view model 这三个矩阵 glm::mat4 mvp m_Proj * m_View * model; m_Shader->Bind(); m_Shader->SetUniformMat4f("u_MVP", mvp);着色器里面就&#xff1a; proj:投影矩阵&#xff0c;可以选择正交投影&#xff0c;或者透视投影…

实操创建属于自己的亚马逊云科技VPS服务:Amazon Lightsail

本文主要讲述如何独立创建自己的亚马逊云科技VPS服务&#xff0c;希望此文能帮助你对亚马逊云科技VPS服务也就是Amazon Lightsail&#xff0c;有个新的认识&#xff0c;对所使用的VPS有所帮助。 Amazon Lightsail是一款无论云计算的新手还是专家&#xff0c;都可通过其快速启动…

将 Ordinals 与比特币智能合约集成:第 4 部分

控制 BSV-20 代币的分配 在上一篇文章中&#xff0c;我们展示了智能合约可以在铸造后控制 BSV-20 代币的转移。 今天&#xff0c;我们演示如何控制此类代币的分发/发行。 无Tick模式 BSV-20 在 V2 中引入了无Tick模式&#xff0c;并采用了与 V1 不同的方法。 部署 (Deploy) …

Queue 中 poll()和 remove()的区别(详解)

系列文章目录 1.SpringBoot整合RabbitMQ并实现消息发送与接收 2. 解析JSON格式参数 & 修改对象的key 3. VUE整合Echarts实现简单的数据可视化 4. List&#xff1c;HashMap&#xff1c;String,String&#xff1e;&#xff1e;实现自定义字符串排序&#xff08;key排序、Val…

使用EvoMap/Three.js模拟无人机灯光秀

一、创建地图对象 首先我们需要创建一个EM.Map对象&#xff0c;该对象代表了一个地图实例&#xff0c;并设置id为"map"的文档元素作为地图的容器。 let map new EM.Map("map",{zoom:22.14,center:[8.02528, -29.27638, 0],pitch:71.507,roll:2.01,maxPit…