WPF拖放控件

news/2024/7/25 3:17:31/文章来源:https://blog.csdn.net/xunmeng2002/article/details/138969153

拖放官方文档

拖放操作通常涉及两个参与方:拖动对象所源自的拖动源和接收放置对象的拖放目标。 拖动源和放置目标可能是相同应用程序或不同应用程序中的 UI 元素。

我这里实现的是对TabControl的Tab页面进行拖放,以达成类似Chrome浏览器的拖放功能。

对拖放相关事件研究后发现,最终只需要在拖动源事件处理程序中调用 DoDragDrop 方法启动拖动操作,然后在拖放目标上,响应Drop事件。启动拖放操作的拖动源事件通常是 MouseMove事件,另外需要判断鼠标左键是否为按下状态。另外,最好判断一下鼠标移动的距离是否大于最小拖动距离(在我没有添加这个检查之前,双击也会触发拖动事件)。

除了正常拖动(将Tab页从一个TabControl 拖动到另一个 TabControl 中),考虑将Tab页拖动到窗口之外的情况(浏览器在这种情况下会创建一个新的窗口来显示这个Tab页),我这里采用类似的方式,创建一个预设的子窗口来显示该Tab页面。

此外,当Tab页面在原TabControl内部拖动时,根据拖动位置,调整Tab页面的顺序。

我这里对原生的TabControl进行了继承封装了几个响应事件,轻量化的实现了拖放效果。

TabControlDragable.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;namespace WpfLibrary;public class TabControlDragable : TabControl
{public TabControlDragable(){AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown), true);AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnMouseLeftButtonUp), true);AddHandler(MouseMoveEvent, new MouseEventHandler(OnMouseMove), true);AddHandler(DropEvent, new DragEventHandler(OnDrop), true);MinWidth = 100;MinHeight = 100;AllowDrop = true;}private bool IsDraging = false;private object? DragData;private Point DragStartPosition;private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e){Point p = e.GetPosition(this);DragData = DragUtility.GetDataObjectFromItemsControl(this, p);if (DragData != null){DragStartPosition = p;}e.Handled = true;}private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e){ResetState();e.Handled = true;}private void OnMouseMove(object sender, MouseEventArgs e){if (DragData == null || DragData is not TabItem){return;}Point currentPosition = e.GetPosition(this);if (e.LeftButton == MouseButtonState.Pressed && !IsDraging&& ((Math.Abs(currentPosition.X - DragStartPosition.X) > SystemParameters.MinimumHorizontalDragDistance) || (Math.Abs(currentPosition.Y - DragStartPosition.Y) > SystemParameters.MinimumVerticalDragDistance))){DataObject dataObject = new DataObject();dataObject.SetData("DragData", DragData);var effects = DragDrop.DoDragDrop(this, dataObject, DragDropEffects.Move);if (effects == DragDropEffects.None){if (Items.Count != 1){var tabItem = DragData as TabItem;var parentWindow = DragUtility.GetParentWindow(tabItem);Items.Remove(tabItem);ChildWindow newChildWindow = new ChildWindow();if (parentWindow is ChildWindow){newChildWindow.Owner = parentWindow.Owner;}else{newChildWindow.Owner = parentWindow;}newChildWindow.DataContext = parentWindow?.DataContext;var tabControl = new TabControlDragable();newChildWindow.MyGrid.Children.Add(tabControl);tabControl.Items.Add(tabItem);newChildWindow.Show();}}ResetState();}e.Handled = true;}private int MeasureIndex(TabControl targetTabControl, Point point){double preWidth = 0.0;for (var i = 0; i < targetTabControl.Items.Count; i++){var item = targetTabControl.Items[i] as TabItem;if (item == null){continue;}if (point.Y > item.ActualHeight){return targetTabControl.Items.Count;}if (point.X < preWidth + item.ActualWidth / 2){return i;}else{preWidth += item.ActualWidth;}}return targetTabControl.Items.Count;}private void OnDrop(object sender, DragEventArgs e){var tabItem = e.Data.GetData("DragData") as TabItem;var parentTabControl = tabItem?.Parent as TabControl;if (tabItem == null || parentTabControl == null){return;}var point = e.GetPosition(this);int index = MeasureIndex(this, point);if (this != parentTabControl){var parentWindow = DragUtility.GetParentWindow(parentTabControl);if (parentTabControl.Items.Count == 1){var childWindow = parentWindow as ChildWindow;if (childWindow != null){parentTabControl.Items.Remove(tabItem);this.Items.Insert(index, tabItem);this.SelectedItem = tabItem;childWindow?.Close();}}else{parentTabControl.Items.Remove(tabItem);this.Items.Insert(index, tabItem);this.SelectedItem = tabItem;}}else if (index < parentTabControl.Items.IndexOf(tabItem)){parentTabControl.Items.Remove(tabItem);parentTabControl.Items.Insert(index, tabItem);parentTabControl.SelectedItem = tabItem;}else if (index > parentTabControl.Items.IndexOf(tabItem)){parentTabControl.Items.Remove(tabItem);parentTabControl.Items.Insert(index - 1, tabItem);parentTabControl.SelectedItem = tabItem;}e.Handled = true;}private void ResetState(){IsDraging = false;DragData = null;}
}

使用起来也很简单,只需要将TabControl 替换为 TabControlDragable 即可,事件已经绑定好了。

MainWindow.xaml

<Window x:Class="WpfLibraryTest.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:WpfLibraryTest"xmlns:wpfLibrary="clr-namespace:WpfLibrary;assembly=WpfLibrary"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><Grid><Grid.RowDefinitions><RowDefinition Height="*"/><RowDefinition Height="*"/></Grid.RowDefinitions><wpfLibrary:TabControlDragable Grid.Row="0" x:Name="tabControl1"><TabItem Header="tabControl1Item1"><DataGrid ItemsSource="{Binding Path=Accounts}" IsReadOnly="True"></DataGrid></TabItem><TabItem Header="tabControl1Item2"><DataGrid ItemsSource="{Binding Path=Accounts2}" IsReadOnly="True"></DataGrid></TabItem><TabItem Header="tabControl1Item3"><DataGrid ItemsSource="{Binding Path=Accounts3}" IsReadOnly="True"></DataGrid></TabItem></wpfLibrary:TabControlDragable><wpfLibrary:TabControlDragable Grid.Row="1" x:Name="tabControl2"><TabItem Header="tabControl2Item1"><DataGrid ItemsSource="{Binding Path=Accounts}" IsReadOnly="True"></DataGrid></TabItem><TabItem Header="tabControl2Item2"><DataGrid ItemsSource="{Binding Path=Accounts2}" IsReadOnly="True"></DataGrid></TabItem><TabItem Header="tabControl2Item3"><DataGrid ItemsSource="{Binding Path=Accounts3}" IsReadOnly="True"></DataGrid></TabItem></wpfLibrary:TabControlDragable></Grid>
</Window>

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

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

相关文章

视频监控平台AS-V1000 的场景管理,一键查看多画面视频的场景配置、调用、管理(一键浏览多路视频)

目录 一、场景管理的定义 二、场景管理的功能和特点 1、功能 &#xff08;1&#xff09;场景配置 &#xff08;2&#xff09;实时监控 &#xff08;3&#xff09;权限管理 2、特点 三、AS-V1000的场景配置和调用 1、场景配置 &#xff08;1&#xff09;实时视频预览 …

为什么会有websocket(由来)

一、HTTP 协议的缺点和解决方案 1、HTTP 协议的缺点和解决方案 用户在使用淘宝、京东这样的网站的时候&#xff0c;每当点击一个按钮其实就是发送一个http请求。那我们先来回顾一下http请求的请求方式。 一个完整的http请求是被分为request请求节点和response响应阶段的&…

UIKit之猜图器Demo

需求 实现猜图器Demo 功能分解&#xff1a; 1>下一题切换功能 2>点击图片后能放大并背景变暗&#xff08;本质是透明度的变化&#xff09;。再次点击则缩小&#xff0c;删除暗色背景。 3> 答案区按钮点击则文字消失&#xff0c;选择区对应文字恢复。 4> 选择区…

四川景源畅信:新人做抖店的成本很高吗?

随着社交媒体的兴起&#xff0c;抖音成为了一个新兴的电商平台——抖店。不少创业者和商家看中了其庞大的用户基础&#xff0c;想要通过开设抖店来拓展销路。然而&#xff0c;对于刚入行的新手来说&#xff0c;成本问题总是让人犹豫不决。究竟新人做抖店的成本高不高?本文将围…

Java 文件操作和输入输出流

在 Java 编程中&#xff0c;文件操作和输入输出流是非常常见和重要的任务&#xff0c;它们允许你读取和写入文件、处理数据流等。 文件操作概述 文件操作是指对文件进行创建、读取、写入、删除等操作的过程。在 Java 中&#xff0c;文件操作通常涉及到使用文件对象、输入输出…

跟进2年弄丢1.8亿,你的大客管理错在哪里?

数量并非目的之所在&#xff0c;质量才是根本之道。重视1%的超级用户&#xff0c;才是提高效率的关键所在。 ——凯文凯利 在当今的商业环境中&#xff0c;大客户已成为销售服务型企业最宝贵的资产。他们不仅贡献了企业收入的重要一环&#xff0c;…

自定义RedisTemplate序列化器

大纲 RedisSerializerFastJsonRedisSerializer自定义二进制序列化器总结代码 在《RedisTemplate保存二进制数据的方法》一文中&#xff0c;我们将Java对象通过《使用java.io库序列化Java对象》中介绍的方法转换为二进制数组&#xff0c;然后保存到Redis中。实际可以通过定制Red…

牛!华为《Linux 面试笔记大全》太赞了,完整版PDF 开放下载!

在QQ和微信社群中&#xff0c;我注意到许多人都在寻找一份全面的Linux学习资料。因此&#xff0c;我在这里为大家整理和分类了相关的信息&#xff0c;可以看作是对重点内容的梳理和归纳。 这份《Linux面试笔记》主要分为三大部分&#xff1a;基础篇-进阶篇-高级篇 本书笔记针…

R语言绘制相关性热图全总结

R语言绘制相关性热图全总结 引言 相关性热图是科研论文中一种常见的可视化手段&#xff0c;而在地学领域&#xff0c;我们常常需要分析一些环境因素之间的相关性&#xff0c;来判断环境生物因素中是否存在相关性情况。 尤其是在进行多变量分析时&#xff0c;分析目标因素和各变…

设计模式基础——设计原则介绍

1.概述 ​ 对于面向对象软件系统的设计而言&#xff0c;如何同时提高一个软件系统的可维护性、可复用性、可拓展性是面向对象设计需要解决的核心问题之一。面向对象设计原则应运而生&#xff0c;这些原则你会在设计模式中找到它们的影子&#xff0c;也是设计模式的基础。往往判…

数据结构的希尔排序(c语言版)

一.希尔排序的概念 1.希尔排序的基本思想 希尔排序是一种基于插入排序算法的优化排序方法。它的基本思想如下: 选择一个增量序列 t1&#xff0c;t2&#xff0c;......&#xff0c;tk&#xff0c;其中 ti > tj, 当 i < j&#xff0c;并且 tk 1。 按增量序列个数k&#…

音乐系统java在线音乐网站基于springboot+vue的音乐系统带万字文档

文章目录 音乐系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码和万字论文参考&#xff08;9.9&#xffe5;带走&#xff09; 音乐系统 一、项目演示 在线音乐系统 二、项目介绍 基于springbootvue的前后端分离在线音乐系…

创新指南|降低 TikTok CPA 的 9 项专家策略

企业在 TikTok 上投放广告&#xff0c;往往最想确保获得最佳的投资回报。然而&#xff0c;这往往说起来容易做起来难。您需要了解如何利用不同的营销工具、定位策略和创意执行来实现您的业务目标并提高成本效率。本文将分享 9 个行之有效的策略&#xff0c;助您有效降低 TikTok…

蓝桥杯备赛——DP续【python】

一、小明的背包2 试题链接&#xff1a;https://www.lanqiao.cn/problems/1175/learning/ 输入示例 5 20 1 6 2 5 3 8 5 15 3 3 输出示例 120 问题分析 这题是完全背包&#xff0c;每个物品有无数个&#xff0c;所以对于任意dp[i][j]&#xff08;其表示的意思为选到第i个…

关于我转生从零开始学C++这件事:升级Lv.25

❀❀❀ 文章由不准备秃的大伟原创 ❀❀❀ ♪♪♪ 若有转载&#xff0c;请联系博主哦~ ♪♪♪ ❤❤❤ 致力学好编程的宝藏博主&#xff0c;代码兴国&#xff01;❤❤❤ OK了老铁们&#xff0c;又是一个周末&#xff0c;大伟又来继续给大家更新我们的C的内容了。那么根据上一篇博…

【ai】LiveKit Agent 的example及python本地开发模式工程实例

title: ‘LiveKit Agent Playground’ playgroundLiveKit Community playground的环境变量&#xff1a;LiveKit API # LiveKit API Configuration LIVEKIT_API_KEYYOUR_API_KEY LIVEKIT_API_SECRETYOUR_API_SECRET# Public configuration NEXT_PUBLIC_LIVEKIT_URLwss://YOUR_…

论文阅读笔记:Task-Customized Mixture of Adapters for General Image Fusion

论文阅读笔记&#xff1a;Task-Customized Mixture of Adapters for General Image Fusion 1 背景2 创新点3 方法4 模块4.1 任务定制混合适配器4.2 提示生成4.3 提示驱动融合4.4 互信息正则化MIR4.5 任务定制化损失 5 实验5.1 VIF任务5.2 MEF任务5.3 MFF任务5.4 消融实验5.5 性…

IDEA社区版创建并运行maven管理的web项目的基本流程

一、前言 注意&#xff0c;这是社区版&#xff0c;旗舰版可以绕路。 二、过程 1、下载安装社区版 2、安装jdk&#xff0c;tomcat&#xff0c;maven 3、创建并启动项目 注意选择的骨架是maven-archetype-webapp&#xff0c;然后next&#xff0c;设置项目名&#xff0c;存放…

5.27作业

定义自己的命名空间my_sapce&#xff0c;在my_sapce中定义string类型的变量s1&#xff0c;再定义一个函数完成对字符串的逆置。 #include <iostream> #include <string.h>using namespace std; namespace my_space {string s1;void RevString(string &s1); } v…

香橙派 AIpro开发板初上手

一、香橙派 AIpro开箱 最近拿到了香橙派 AIpro&#xff08;OrangePi AIpro&#xff09;&#xff0c;下面就是里面的板子和相关的配件。包含主板、散热组件、电源适配器、双C口电源线、32GB SD卡。我手上的这个是8G LPDDR4X运存的版本。 OrangePi AIpro开发板是一款由香橙派与华…