日常开发记录分享——C#控件ToolTip实现分栏显示内容
文章目录
- 需求来源
- 实现思路
- 实施
- 请看VCR
- 等等别走,有优化
需求来源
需要在鼠标浮动到指定位置后提示出详细的信息,一开始使用的tooltip实现,但是里面的内容效果并不理想,需要有条理性,于是就想到能不能将展示的东西分列。
实现思路
使用两个字符串数据接收通过字符串切割后的内容,然后通过在tooltip的draw事件绘制时将内容分为两次绘制。
实施
自定封装一个ToolTip控件,继承ToolTIp然后添加两个事件,分别时Draw 和Popup
Draw 和 Popup 这两个事件在 ToolTip 类中扮演着重要的角色,用于自定义工具提示的显示和绘制。
Draw 事件在工具提示需要绘制时触发。通过处理这个事件,可以自定义工具提示的外观和内容。
-
作用:
- 自定义绘制工具提示:在处理
Draw事件时,可以完全控制工具提示的绘制,包括背景颜色、边框、文本内容和文本样式等。 - 实现高级图形效果:可以使用
Graphics对象来实现复杂的绘制效果,比如渐变色、图片、各种形状等。
- 自定义绘制工具提示:在处理
-
使用场景:
- 当默认的工具提示外观不能满足需求时,可以通过
Draw事件自定义绘制工具提示。 - 需要在工具提示中显示非文本内容(如图像、图表)时,可以在
Draw事件中实现。
- 当默认的工具提示外观不能满足需求时,可以通过
Popup 事件在工具提示显示之前触发。通过处理这个事件,可以动态调整工具提示的大小和内容。
-
作用:
- 动态调整工具提示大小:在处理
Popup事件时,可以根据内容的大小动态设置工具提示的尺寸,以确保内容完全显示。 - 准备绘制环境:可以在
Popup事件中进行一些准备工作,比如计算文本的最大宽度和高度,为后续的Draw事件做准备。
- 动态调整工具提示大小:在处理
-
使用场景:
- 需要根据内容动态调整工具提示的大小时,可以在
Popup事件中进行计算和设置。 - 需要在工具提示显示前进行一些准备工作,比如加载图片、计算文本尺寸等,可以在
Popup事件中处理。
- 需要根据内容动态调整工具提示的大小时,可以在
using System;
using System.Drawing;
using System.Windows.Forms;namespace Test1
{// 自定义工具提示类,继承自 ToolTippublic class CustomToolTip : ToolTip{private string[] Column1; // 用于存储第一列的文本数组private string[] Column2; // 用于存储第二列的文本数组private Font TextFont; // 工具提示文本的字体// 记录第一列的宽度private int Column1MaxWidth = 0;// 构造函数,初始化自定义工具提示public CustomToolTip(){TextFont = new Font("微软雅黑", 15.0f); // 设置字体为“微软雅黑”,大小为15this.OwnerDraw = true; // 启用自定义绘制工具提示this.Draw += new DrawToolTipEventHandler(OnDraw); // 订阅 Draw 事件this.Popup += new PopupEventHandler(OnPopup); // 订阅 Popup 事件}// 设置工具提示的内容,将其拆分为两列public void SetContent(string content){var parts = content.Split(new string[] { "," }, StringSplitOptions.None); // 按逗号拆分内容int midPoint = (parts.Length + 1) / 2; // 计算拆分成两列的中间点Column1 = new string[midPoint]; // 初始化第一列数组Column2 = new string[parts.Length - midPoint]; // 初始化第二列数组// 填充列数组for (int i = 0; i < parts.Length; i++){if (i < midPoint){Column1[i] = parts[i];}else{Column2[i - midPoint] = parts[i];}}}// 自定义工具提示的绘制事件处理程序private void OnDraw(object sender, DrawToolTipEventArgs e){e.DrawBackground(); // 绘制工具提示的背景e.DrawBorder(); // 绘制工具提示的边框Brush brush = Brushes.Black; // 用于绘制文本的画笔Rectangle rct2 = e.Bounds; // 工具提示的边界e.Graphics.FillRectangle(Brushes.Bisque, rct2); // 用浅橙色填充背景e.Graphics.DrawRectangle(Pens.DarkGray, new Rectangle(0, 0, rct2.Width - 1, rct2.Height - 1)); // 绘制边框// 绘制第一列文本for (int i = 0; i < Column1.Length; i++){e.Graphics.DrawString(Column1[i], TextFont, brush, new PointF(5, i * 25));}// 绘制第二列文本for (int i = 0; i < Column2.Length; i++){e.Graphics.DrawString(Column2[i], TextFont, brush, new PointF(Column1MaxWidth, i * 25));}}// 在工具提示显示之前计算其大小的事件处理程序private void OnPopup(object sender, PopupEventArgs e){int Column2MaxWidth = 0; // 用于存储第二列的最大宽度int maxHeight = 0; // 用于存储工具提示的最大高度// 计算第一列的最大宽度和高度foreach (var text in Column1){var sz = TextRenderer.MeasureText(text, TextFont);if (sz.Width > Column1MaxWidth)Column1MaxWidth = sz.Width;maxHeight += sz.Height;}// 计算第二列的最大宽度foreach (var text in Column2){var sz = TextRenderer.MeasureText(text, TextFont);if (sz.Width > Column2MaxWidth)Column2MaxWidth = sz.Width;}// 确保高度适应两列中较高的一列maxHeight = Math.Max(maxHeight, Column2.Length * TextRenderer.MeasureText("A", TextFont).Height);e.ToolTipSize = new Size(Column1MaxWidth + Column2MaxWidth + 20, maxHeight + 30); // 设置工具提示大小,并添加一些间距}}
}
这里对字符串的分割是根据,来的,根据个人需要修改SetContent方法中切割字符,当然也可以封装一下,这里本人偷懒了。
下面是使用的方式,先在我们窗体中创建一个自定义的Tooltip对象,具体使用就是先设置SetContent方法将要显示的内容传递进去。最后将要tooltip关联的控件对象绑定就行了
private CustomToolTip custom = new CustomToolTip();private void Form1_Load(object sender, EventArgs e){string aa = $"工作人员姓名:aaa,出勤地点:aaa333344445555555555," +$"工号:aaa,出勤时间:aaa," +$"手机:aaaaaaaa,本站时间:aaa," +$"站名:aaa,工作班制:aaa," +$"当前已工作时间:aaa,班制时长:aaa1111," +$"工作人员所属部门:aaa";custom.SetContent(aa);custom.SetToolTip(button1,aa);//这里传递第二个参数只要是字符串就行,因为在SetContent方法时已经设置好要显示的内容了。}
请看VCR

等等别走,有优化
鉴于上面我们使用的在From_Load方法中去使用自定义tip时调用SetToolTip时第二个参数传递有些冗余,这里把自定义的tip控件给优化了一下,优化虽小也是进步
using System;
using System.Drawing;
using System.Windows.Forms;namespace Test1
{// 自定义工具提示类,继承自 ToolTippublic class CustomToolTip : ToolTip{private string[] Column1; // 用于存储第一列的文本数组private string[] Column2; // 用于存储第二列的文本数组private Font TextFont; // 工具提示文本的字体priavte Control ParentCtrl;//父窗体控件// 记录第一列的宽度private int Column1MaxWidth = 0;// 构造函数,初始化自定义工具提示public CustomToolTip(){TextFont = new Font("微软雅黑", 15.0f); // 设置字体为“微软雅黑”,大小为15this.OwnerDraw = true; // 启用自定义绘制工具提示this.Draw += new DrawToolTipEventHandler(OnDraw); // 订阅 Draw 事件this.Popup += new PopupEventHandler(OnPopup); // 订阅 Popup 事件}// 设置工具提示的内容,将其拆分为两列private void SetContent(string content){var parts = content.Split(new string[] { "," }, StringSplitOptions.None); // 按逗号拆分内容int midPoint = (parts.Length + 1) / 2; // 计算拆分成两列的中间点Column1 = new string[midPoint]; // 初始化第一列数组Column2 = new string[parts.Length - midPoint]; // 初始化第二列数组// 填充列数组for (int i = 0; i < parts.Length; i++){if (i < midPoint){Column1[i] = parts[i];}else{Column2[i - midPoint] = parts[i];}}}// 自定义工具提示的绘制事件处理程序private void OnDraw(object sender, DrawToolTipEventArgs e){e.DrawBackground(); // 绘制工具提示的背景e.DrawBorder(); // 绘制工具提示的边框Brush brush = Brushes.Black; // 用于绘制文本的画笔Rectangle rct2 = e.Bounds; // 工具提示的边界e.Graphics.FillRectangle(Brushes.Bisque, rct2); // 用浅橙色填充背景e.Graphics.DrawRectangle(Pens.DarkGray, new Rectangle(0, 0, rct2.Width - 1, rct2.Height - 1)); // 绘制边框// 绘制第一列文本for (int i = 0; i < Column1.Length; i++){e.Graphics.DrawString(Column1[i], TextFont, brush, new PointF(5, i * 25));}// 绘制第二列文本for (int i = 0; i < Column2.Length; i++){e.Graphics.DrawString(Column2[i], TextFont, brush, new PointF(Column1MaxWidth, i * 25));}}// 在工具提示显示之前计算其大小的事件处理程序private void OnPopup(object sender, PopupEventArgs e){int Column2MaxWidth = 0; // 用于存储第二列的最大宽度int maxHeight = 0; // 用于存储工具提示的最大高度//设置将文本拆分两个数组,用于后期显示为两列---在这里通过tip控件自带的GetToolTip方法获取提示文本内容然后进行拆分初始化SetContent(this.GetToolTip(ParentCtrl));// 计算第一列的最大宽度和高度foreach (var text in Column1){var sz = TextRenderer.MeasureText(text, TextFont);if (sz.Width > Column1MaxWidth)Column1MaxWidth = sz.Width;maxHeight += sz.Height;}// 计算第二列的最大宽度foreach (var text in Column2){var sz = TextRenderer.MeasureText(text, TextFont);if (sz.Width > Column2MaxWidth)Column2MaxWidth = sz.Width;}// 确保高度适应两列中较高的一列maxHeight = Math.Max(maxHeight, Column2.Length * TextRenderer.MeasureText("A", TextFont).Height);e.ToolTipSize = new Size(Column1MaxWidth + Column2MaxWidth + 20, maxHeight + 30); // 设置工具提示大小,并添加一些间距}}
}
private CustomToolTip custom ;public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){custom = new CustomToolTip(button1);string aa = $"工作人员姓名:aaa,出勤地点:aaa333344445555555555," +$"工号:aaa,出勤时间:aaa," +$"手机:aaaaaaaa,本站时间:aaa," +$"站名:aaa,工作班制:aaa," +$"当前已工作时间:aaa,班制时长:aaa1111," +$"工作人员所属部门:aaa";custom.SetToolTip(button1,aa);}
相关文章:
日常开发记录分享——C#控件ToolTip实现分栏显示内容
文章目录 需求来源实现思路实施请看VCR等等别走,有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息,一开始使用的tooltip实现,但是里面的内容效果并不理想,需要有条理性,于是就想到能不能将展示的东西分列…...
Kettle下载安装
环境说明 虚拟机:Win7;MySql8.0 主机:Win11;JDK1.8;Kettle 9.4(Pentaho Data Integration 9.4)(下载方式见文末) 安装说明 【1】解压后运行Spoon.bat 【2】将jar包 复…...
最新版Golang pprof使用(引入、抓取、分析,图文结合)
最新版Golang pprof使用 🔥具体实践: Go调试神器pprof使用教程Golang线上内存爆掉问题排查(pprof) Github地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-pprof 引入pprof:import _ “net/http/pprof” …...
vue3学习记录1:emit的写法
emit是用于child组件向parent组件通信的工具,因为vue3的script可以设置为setup,写法同vue2有较大区别。 一、script setup - 直接写 <script lang"ts" setup>const emit defineEmits([close]);function handleClose() {emit(close);}…...
Visual Studio Code + vue快速安装配置Node.js+Vue+webpack+vscode
第一部分:Node.js 第一步:下载Node.js 方法1:链接 下载 | Node.js 中文网 (nodejs.cn) 方法2:百度网盘 链接:https://pan.baidu.com/s/1zIqu8H9rb_I1i-1OWD7swQ?pwdaurk 提取码:aurk --来自百度网盘…...
【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式
这是【Dart 教程系列第 49 篇】,如果觉得有用的话,欢迎关注专栏。 博文当前所用 Flutter SDK:3.22.1、Dart SDK:3.4.1 文章目录 一:什么是策略设计模式?二:为什么要使用策略设计模式࿱…...
BGP路由反射器
原理概述 缺省情况下,路由器从它的一个 IBGP对等体那里接收到的路由条目不会被该路由器再传递给其他IBGP对等体,这个原则称为BGP水平分割原则,该原则的根本作用是防止 AS内部的BGP路由环路。因此,在AS内部,一般需要每台…...
DolphinDB Web 端权限管理:可视化操作指南
在现代数据库管理中,高效和直观的权限管理对于用户的数据安全是至关重要的。过去 DolphinDB 用户需要依赖系统脚本来管理用户和权限,这对于缺乏技术背景的管理员来说既复杂又容易出错。 为了提升用户体验和操作效率,DolphinDB 目前在 Web 上…...
学习Vue2收藏这一篇就够了(如何创建Vue实例)
什么是Vue? Vue是什么:是一个用于构建用户界面的渐进式框架 什么是构建用户界面:基于数据动态渲染页面 什么是渐进式:循序渐进的学习 什么是框架:一整套完整的项目解决方案 创建Vue实例 核心步骤(4步…...
Mysql数据库第四次作业
mysql> create table student(sno int primary key auto_increment,sname varchar(30) not null unique,Ssex varchar(2) check (Ssex男 or Ssex女) not null,Sage int not null,Sdept varchar(10) default计算机 not null); mysql> create table Course(Con int primar…...
使用Docker搭建MySql的主从同步+ShardingSphere搭建Mysql的读写分离
参考课程 尚硅谷ShardingSphere5实战教程(快速入门掌握核心)_哔哩哔哩_bilibili 主服务器 创建容器 docker run -d \ -p 3306:3306 \ -v /kira/mysql/master/conf:/etc/mysql/conf.d \ -v /kira/mysql/master/data:/var/lib/mysql \ -e MYSQL_ROOT…...
数据结构:数据类型与抽象数据类型
数据类型与抽象数据类型 数据类型基本数据类型构造数据类型指针类型枚举类型 抽象数据类型(ADT)抽象数据类型的组成部分常见的抽象数据类型示例 数据类型与抽象数据类型的区别实现抽象数据类型的具体方式用数组实现栈用链表实现栈 总结 数据类型 数据类…...
西方逻辑史简介
西方逻辑史研究,对形式逻辑实现现代化,对加强西方哲学史研究,对开展科学方法论的研究都有重要意义。西方逻辑史一般被划分成古代、中世纪、现代三个历史时期。本文拟对这三个时期中的七个重要逻辑学家和逻辑学派:亚里士多德、斯多…...
【论文10】复现代码tips
一、准备工作 1.创建一个虚拟环境 conda create --name drgcnn38 python=3.8.18 2.激活虚拟环境 conda activate drgcnn38 注意事项 在Pycharm中终端(terminal)显示PS而不是虚拟环境base 问题如下所示 解决方法:shell路径改成cmd.exe 重启终端显示虚拟环境 3.安装torch …...
分布式缓存获取以及设置
1. 通用代码 public SysUser getCache(String sysUserId) {String cacheKey "litgery:warehouse:" sysUserId;// 尝试从缓存中获取数据CacheData cacheData redisUtils.get(cacheKey);if (null ! cacheData) {if (Boolean.TRUE.equals(cacheData.getExist())) {re…...
SMO算法,platt论文的原始算法及优化算法
platt论文:[PDF] Sequential Minimal Optimization : A Fast Algorithm for Training Support Vector Machines | Semantic Scholar 算法优化:[PDF] Improvements to Platts SMO Algorithm for SVM Classifier Design | Semantic Scholar 包含个人plat…...
2.3 openCv -- 对矩阵执行掩码操作
在矩阵上进行掩模操作相当简单。其基本思想是根据一个掩模矩阵(也称为核)来重新计算图像中每个像素的值。这个掩模矩阵包含的值决定了邻近像素(以及当前像素本身)对新的像素值产生多少影响。从数学角度来看,我们使用指定的值来做一个加权平均。 具体而言,掩模操作通常涉…...
【Django】 js实现动态赋值、显示show隐藏hide效果
文章目录 需要达到的前端效果预览:实现步骤复制bootstrp代码(buttons)复制bootstrp代码(Alert警告框)写js测试效果 需要达到的前端效果预览: {% load static %} <!DOCTYPE html> <html lang"…...
qt--做一个拷贝文件器
一、项目要求 使用线程完善文件拷贝器的操作 主窗口不能假死主窗口进度条必须能动改写文件大小的单位(自适应) 1TB1024GB 1GB1024MB 1MB1024KB 1KB1024字节 二、所需技术 1.QFileDialog 文件对话框 QFileDialog也继承了QDialog类,直接使用静态…...
Eclipse 搭建 C/C++ 开发环境以及eclipse的使用
一、下载、安装 MinGW 1、下载: 下载地址:MinGW - Minimalist GNU for Windows - Browse Files at SourceForge.net 点击“Download Latest Version”即可 下载完成后,得到一个名为 mingw-get-setup.exe 的安装文件。双击运行,安装即可。 …...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...
【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...
