当前位置: 首页 > news >正文

在C#中编程绘制和移动线段

这个示例允许用户绘制和移动线段。它允许您根据鼠标下方的内容执行三种不同的操作。

  • 当鼠标位于某个线段上时,光标会变成手的形状。然后您可以单击并拖动来移动该线段。
  • 当鼠标位于线段的终点上时,光标会变成箭头。然后您可以单击并拖动以移动终点。
  • 当鼠标悬停在空白处时,您可以单击并拖动来绘制新的线段。

 

程序使用MouseDownMouseMoveMouseUp事件处理所有这些情况,但在一组事件处理程序中处理所有可能的组合会造成混乱。为了便于管理,程序使用单独的MouseMoveMouseUp事件处理程序来执行不同的任务。

这篇文章分为以下几个部分,与程序的基本状态相对应。

  • 绘画
  • 什么都没动
  • 绘制新线段
  • 移动端点
  • 移动线段
  • 下一步是什么?

绘画

程序将线段端点的坐标存储在列表Pt1Pt2中。

// The points that make up the line segments.
private List Pt1 = new List<Point>();
private List Pt2 = new List<Point>();

绘制新线段时,变量IsDrawing,程序将新线段的端点存储在变量NewPt1NewPt2中。

// Points for the new line.
private bool IsDrawing = false;
private Point NewPt1, NewPt2;

Paint事件处理 程序只是循环遍历Pt1Pt2列表,绘制线段及其端点。然后绘制新线(如果您正在绘制一条线)。

// Draw the lines.
private void picCanvas_Paint(object sender, PaintEventArgs e)
{// Draw the segments.for (int i = 0; i < Pt1.Count; i++){// Draw the segment.e.Graphics.DrawLine(Pens.Blue, Pt1[i], Pt2[i]);}// Draw the end points.foreach (Point pt in Pt1){Rectangle rect = new Rectangle(pt.X - object_radius, pt.Y - object_radius,2 * object_radius + 1, 2 * object_radius + 1);e.Graphics.FillEllipse(Brushes.White, rect);e.Graphics.DrawEllipse(Pens.Black, rect);}foreach (Point pt in Pt2){Rectangle rect = new Rectangle(pt.X - object_radius, pt.Y - object_radius,2 * object_radius + 1, 2 * object_radius + 1);e.Graphics.FillEllipse(Brushes.White, rect);e.Graphics.DrawEllipse(Pens.Black, rect);}// If there's a new segment under constructions, draw it.if (IsDrawing){e.Graphics.DrawLine(Pens.Red, NewPt1, NewPt2);}
}

什么都没动

如果鼠标移动而您没有移动线段或终点,则会执行以下事件处理程序。

// The mouse is up. See whether we're over an end point or segment.
private void picCanvas_MouseMove_NotDown(object sender,MouseEventArgs e)
{Cursor new_cursor = Cursors.Cross;// See what we're over.Point hit_point;int segment_number;if (MouseIsOverEndpoint(e.Location, out segment_number,out hit_point))new_cursor = Cursors.Arrow;else if (MouseIsOverSegment(e.Location, out segment_number))new_cursor = Cursors.Hand;// Set the new cursor.if (picCanvas.Cursor != new_cursor)picCanvas.Cursor = new_cursor;
}

此代码调用后面描述的MouseIsOverEndPointMouseIsOverSegment方法来查看鼠标是否位于任何有趣的东西上。然后它显示相应的光标。(如果位于端点上,则显示箭头;如果位于线段上,则显示移交;如果位于空处,则显示交叉。)

如果您没有移动任何内容并按下鼠标,则会执行以下事件处理程序。

// See what we're over and start doing whatever is appropriate.
private void picCanvas_MouseDown(object sender, MouseEventArgs e)
{// See what we're over.Point hit_point;int segment_number;if (MouseIsOverEndpoint(e.Location, out segment_number,out hit_point)){// Start moving this end point.picCanvas.MouseMove -= picCanvas_MouseMove_NotDown;picCanvas.MouseMove += picCanvas_MouseMove_MovingEndPoint;picCanvas.MouseUp += picCanvas_MouseUp_MovingEndPoint;// Remember the segment number.MovingSegment = segment_number;// See if we're moving the start end point.MovingStartEndPoint =(Pt1[segment_number].Equals(hit_point));// Remember the offset from the mouse to the point.OffsetX = hit_point.X - e.X;OffsetY = hit_point.Y - e.Y;}else if (MouseIsOverSegment(e.Location, out segment_number)){// Start moving this segment.picCanvas.MouseMove -= picCanvas_MouseMove_NotDown;picCanvas.MouseMove += picCanvas_MouseMove_MovingSegment;picCanvas.MouseUp += picCanvas_MouseUp_MovingSegment;// Remember the segment number.MovingSegment = segment_number;// Remember the offset from the mouse// to the segment's first point.OffsetX = Pt1[segment_number].X - e.X;OffsetY = Pt1[segment_number].Y - e.Y;}else{// Start drawing a new segment.picCanvas.MouseMove -= picCanvas_MouseMove_NotDown;picCanvas.MouseMove += picCanvas_MouseMove_Drawing;picCanvas.MouseUp += picCanvas_MouseUp_Drawing;IsDrawing = true;NewPt1 = new Point(e.X, e.Y);NewPt2 = new Point(e.X, e.Y);}
}

此方法使用MouseIsOverEndPointMouseIsOverSegment方法来查看鼠标是否位于任何有趣的对象上。如果鼠标位于端点或线段上,则代码开始移动该对象。

注意代码如何卸载picCanvas_MouseMove_NotDown事件处理程序并为其启动的操作 安装新的MouseMoveMouseUp事件处理程序。

以下代码显示MouseIsOverEndPointMouseIsOverSegment方法。

// See if the mouse is over an end point.
private bool MouseIsOverEndpoint(Point mouse_pt,out int segment_number, out Point hit_pt)
{for (int i = 0; i < Pt1.Count; i++ ){// Check the starting point.if (FindDistanceToPointSquared(mouse_pt, Pt1[i]) <over_dist_squared){// We're over this point.segment_number = i;hit_pt = Pt1[i];return true;}// Check the end point.if (FindDistanceToPointSquared(mouse_pt, Pt2[i]) <over_dist_squared){// We're over this point.segment_number = i;hit_pt = Pt2[i];return true;}}segment_number = -1;hit_pt = new Point(-1, -1);return false;
}// See if the mouse is over a line segment.
private bool MouseIsOverSegment(Point mouse_pt,out int segment_number)
{for (int i = 0; i < Pt1.Count; i++){// See if we're over the segment.PointF closest;if (FindDistanceToSegmentSquared(mouse_pt, Pt1[i], Pt2[i], out closest)< over_dist_squared){// We're over this segment.segment_number = i;return true;}}segment_number = -1;return false;
}

这些方法只是调用FindDistanceToPointSquaredFindDistanceToSegmentSquared方法。FindDistanceToPointSquared很简单。有关FindDistanceToSegmentSquared工作原理的说明,请参阅文章“在 C# 中查找点和线段之间的最短距离

该程序测试距离的平方,因此不需要计算平方根,因为平方根相对较慢。请注意,当且仅当 x 2 < y 2时,x < y 才成立,因此此测试仍可确定对象是否在鼠标所需的距离内。

绘制新线段

以下代码显示了绘制新线段时处于活动状态的 MouseMoveMouseUp事件处理程序。

// We're drawing a new segment.
private void picCanvas_MouseMove_Drawing(object sender,MouseEventArgs e)
{// Save the new point.NewPt2 = new Point(e.X, e.Y);// Redraw.picCanvas.Invalidate();
}// Stop drawing.
private void picCanvas_MouseUp_Drawing(object sender,MouseEventArgs e)
{IsDrawing = false;// Reset the event handlers.picCanvas.MouseMove -= picCanvas_MouseMove_Drawing;picCanvas.MouseMove += picCanvas_MouseMove_NotDown;picCanvas.MouseUp -= picCanvas_MouseUp_Drawing;// Create the new segment.Pt1.Add(NewPt1);Pt2.Add(NewPt2);// Redraw.picCanvas.Invalidate();
}

当鼠标移动时,MouseMove事件处理程序会更新NewPt2的值以保存鼠标的当前位置。然后,它使程序的PictureBox无效,以便其Paint事件处理程序绘制当前段和正在进行的新段。

当释放鼠标时,MouseUp事件处理程序将恢复“不移动任何内容”事件处理程序,将新段的点添加到Pt1Pt2列表中,并使PictureBox无效以重新绘制。


移动端点

以下代码显示了移动端点时处于活动状态的 MouseMoveMouseUp事件处理程序

3

当鼠标移动时,MouseMove事件处理程序会更新移动点的位置,然后使PictureBox无效并使其重新绘制。MouseUp事件处理程序只是恢复“不移动任何内容”事件处理程序并重新绘制。


移动线段

以下代码显示了移动端点时处于活动状态的 MouseMoveMouseUp事件处理程序。

3

当鼠标移动时,MouseMove事件处理程序更新线段端点的位置并重绘以显示新位置。MouseUp事件处理程序只是恢复“不移动任何内容”事件处理程序并重绘。

另:允许您将线段和端点捕捉到网格。

当绘图PictureBox调整大小或者选中或取消选中“对齐网格”复选框时,程序将调用以下代码所示的 MakeBackgroundGrid方法。

如果未选中复选框,则此方法将picCanvas控件的背景设置为 null。否则,它将制作一个适合PictureBox 的位图,在其上绘制点以显示网格,并将PictureBoxBackgroundImage属性设置为位图。

程序的另一个变化是它处理新点的方式。每当程序要对某个点执行某些操作时,它都会调用以下SnapToGrid方法将该点的坐标捕捉到网格(如果合适)。

该方法将其xy参数四舍五入为网格大小的最接近倍数。

以下代码显示了程序如何使用SnapToGrid方法的示例。当用户移动线段的终点时,将执行此事件处理程序。

private void MakeBackgroundGrid()
{if (!chkSnapToGrid.Checked){picCanvas.BackgroundImage = null;}else{Bitmap bm = new Bitmap(picCanvas.ClientSize.Width, picCanvas.ClientSize.Height);for (int x = 0; x < picCanvas.ClientSize.Width;x += grid_gap){for (int y = 0; y < picCanvas.ClientSize.Height;y += grid_gap){bm.SetPixel(x, y, Color.Black);}}picCanvas.BackgroundImage = bm;}
}
// Snap to the nearest grid point.
private void SnapToGrid(ref int x, ref int y)
{if (!chkSnapToGrid.Checked) return;x = grid_gap * (int)Math.Round((double)x / grid_gap);y = grid_gap * (int)Math.Round((double)y / grid_gap);
}
// We're moving an end point.
private void picCanvas_MouseMove_MovingEndPoint(object sender, MouseEventArgs e)
{// Move the point to its new location.int x = e.X + OffsetX;int y = e.Y + OffsetY;SnapToGrid(ref x, ref y);if (MovingStartEndPoint)Pt1[MovingSegment] = new Point(x, y);elsePt2[MovingSegment] = new Point(x, y);// Redraw.picCanvas.Invalidate();
}

相关文章:

在C#中编程绘制和移动线段

这个示例允许用户绘制和移动线段。它允许您根据鼠标下方的内容执行三种不同的操作。 当鼠标位于某个线段上时&#xff0c;光标会变成手的形状。然后您可以单击并拖动来移动该线段。当鼠标位于线段的终点上时&#xff0c;光标会变成箭头。然后您可以单击并拖动以移动终点。当鼠…...

web自动化测试框架playwright

一、背景&#xff1a;UI自动化的痛点&#xff1a; 1、设计脚本耗时&#xff1a; 需要思考要如何模拟用户的操作&#xff0c;如何触发页面的事件&#xff0c;还要思考如何设计脚本&#xff0c;定位和操作要交互的元素、路径、位置&#xff0c;再编写代码逻辑&#xff0c;往复循…...

【报错记录】Ubuntu22.04解决开机卡在 /dev/sda5 : clean , *files , *blocks

一个愿意伫立在巨人肩膀上的农民...... 一、错误现象 本人的电脑安装Windows10和Ubuntu22.04双系统&#xff0c;一次训练中电脑死机无法开机&#xff0c;重启之后便出现如下错误&#xff0c;在网上寻找过很多方法均无效&#xff0c;在root下禁用了samba服务&#xff0c;也无济…...

【AIGC】如何高效使用ChatGPT挖掘AI最大潜能?26个Prompt提问秘诀帮你提升300%效率的!

还记得第一次使用ChatGPT时&#xff0c;那种既兴奋又困惑的心情吗&#xff1f;我是从一个对AI一知半解的普通用户&#xff0c;逐步成长为现在的“ChatGPT大神”。这一过程并非一蹴而就&#xff0c;而是通过不断的探索和实践&#xff0c;掌握了一系列高效使用的技巧。今天&#…...

免费生成AI PPT产品推荐?

要完全免费几乎是没有的&#xff0c;要知道AI还是非常烧钱的。 不过免费蹭还是有很多方法的&#xff0c;这里收集了一些&#xff1a; 下面分享我自己免费蹭过的几款AI制作PPT的工具。 1 金山-WPS PPT对我们来说并不陌生&#xff0c;而微软的PowerPoint与金山的WPS也是我们最常…...

ubuntu22.04 使用crash

文章目录 前言一、apt 安装dbgsym vnlinux二、使用.ddeb包安装dbgsym vnlinux三、dbgsym发行版四、crash调试参考资料 前言 最近在适配 ubuntu系统&#xff0c;记录一下其crash的安装。 一、apt 安装dbgsym vnlinux # echo "deb http://ddebs.ubuntu.com $(lsb_release…...

Linux高性能服务器编程 | 读书笔记 |9.定时器

9. 定时器 网络程序需要处理定时事件&#xff0c;如定期检测一个客户连接的活动状态。服务器程序通常管理着众多定时事件&#xff0c;有效地组织这些定时事件&#xff0c;使其在预期的时间被触发且不影响服务器的主要逻辑&#xff0c;对于服务器的性能有至关重要的影响。为此&…...

OSG开发笔记(三十九):OSG中模型的透明度实现、球体透明度Demo

​若该文为原创文章&#xff0c;未经允许不得转载 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/144424531 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 长沙红胖子Qt…...

phpSpider如何处理网页内容的动态加载问题

phpSpider处理网页内容的动态加载问题&#xff0c;主要采取以下几种策略&#xff1a; 一、分析并直接请求API 现代网站中&#xff0c;很多动态加载的内容是通过后端的API接口以JSON或XML等格式返回的。phpSpider可以通过分析网页的请求&#xff0c;找到这些API接口的URL&…...

【Go】-倒排索引的简单实现

目录 什么是倒排索引 定义 基本结构和原理 分词在倒排索引中的重要性 简单倒排索引的实现 接口定义 简单数据库的实现 倒排索引 正排索引 测试 总结 什么是倒排索引 定义 倒排索引&#xff08;Inverted Index&#xff09;是一种索引数据结构&#xff0c;它是文档检…...

Python:基于PyCharm的简单程序创建及运行-HelloWorld

1. 新建项目 2. 设置文件位置&#xff0c;并创建项目 文件位置由“目录项目名称”组成&#xff0c;如&#xff1a;D:\PycharmProjects\HelloWorld&#xff0c;“HelloWorld”则是项目名称。 3. 创建Python文件 4. 定义文件名称&#xff0c;如HelloWorld。双击【Python 文件】完…...

设置HP条UI

概述 设置常见的生命值条&#xff0c; 实现过程 设置UI/image作为形状 设置UI/Image作为背景 设置UI/image&#xff08;healthfill&#xff09;作为填充图片&#xff0c;层数低于背景 设置heathfill的imagetype为filled fillmethod为horizontal [SerializeField] private Im…...

开源分布式系统追踪-03-CNCF jaeger-02-快速开始

分布式跟踪系列 CAT cat monitor 分布式监控 CAT-是什么&#xff1f; cat monitor-02-分布式监控 CAT埋点 cat monitor-03-深度剖析开源分布式监控CAT cat monitor-04-cat 服务端部署实战 cat monitor-05-cat 客户端集成实战 cat monitor-06-cat 消息存储 skywalking …...

手机实时提取SIM卡打电话的信令声音--社会价值(一、方案解决了什么问题)

手机实时提取SIM卡打电话的信令声音 --社会价值(一、方案解决了什么问题) 一、前言 这段时间&#xff0c;我们在技术范围之外陷入了一个自证或者说下定义的怪圈&#xff0c;即要怎么样去介绍或者描述&#xff1a;我们是一个什么样的产品。它在当前这个世界上&#xff0c;处于…...

FFmpeg功能使用

步骤&#xff1a;1&#xff0c;安装FFmpeg Download FFmpeg 在这里点击->Windows builds from gyan.dev&#xff1b;如下图 会跳到另外的下载界面&#xff1a; 在里面下拉选择点击ffmpeg-7.1-essentials_build.zip&#xff1a; 即可下载到FFmpeg&#xff1b; 使用&#…...

Windows安装WSL子系统及docker,以及WSL和docker配置、使用及问题解决

在Windows操作系统中,Ubuntu子系统(也称为Windows Subsystem for Linux, WSL)为开发者提供了一个在Windows环境下运行Linux环境的平台。然而,有时用户在按照Ubuntu子系统或者使用WSL时,可能会遇到各种问题,下面总结一下解决方式。 想要在Windows上安装Docker(实际上是基…...

飞牛 fnos docker镜像部署OpenSpeedtest宽带网速测试教程

penSpeedTest是一个跨平台的网络测速应用&#xff0c;支持不同操作系统的浏览器&#xff0c;无需安装额外软件或插件。您可以在iPhone、iPad、Android设备、Windows和Linux系统的电脑、手机和平板上直接测试设备与NAS之间的宽带速度。 通过这个可以排查出设备与NAS之间的传输速…...

【kubernetes】资源管理方式

目录 1. 说明2. 命令式对象管理3. 命令式对象配置4. 声明式对象配置5. 三种方式的对比 1. 说明 1.在Kubernetes&#xff08;k8s&#xff09;中&#xff0c;资源管理是一个核心功能&#xff0c;它允许用户通过操作资源来管理Kubernetes集群。2.Kubernetes将所有的内容都抽象为资…...

chromedriver可运行的docker环境

以常见的linux x86服务器为例 chrome driver 官网&#xff1a;https://googlechromelabs.github.io/chrome-for-testing/ 下载chrome linux64位&#xff1a;https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.85/xxx 下载chrome driver linux64位&#x…...

【YashanDB知识库】如何将mysql含有group by的SQL转换成崖山支持的SQL

本文内容来自YashanDB官网&#xff0c;原文内容请见 https://www.yashandb.com/newsinfo/7610112.html?templateId1718516 问题现象 以下SQL在MYSQL下均能执行成功&#xff0c;在崖山下执行报错。 SELECT Sname,Ssex, min(Sage) FROM Student group by Ssex;SELECT Sname,c…...

终极指南:如何使用IDM激活脚本实现永久免费下载体验

终极指南&#xff1a;如何使用IDM激活脚本实现永久免费下载体验 【免费下载链接】IDM-Activation-Script IDM Activation & Trail Reset Script 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script Internet Download Manager&#xff08;IDM&…...

如何在Hermes Agent项目中自定义Provider接入Taotoken多模型服务

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 如何在Hermes Agent项目中自定义Provider接入Taotoken多模型服务 Hermes Agent 是一个功能强大的AI代理框架&#xff0c;它允许开发…...

AntiMicroX手柄映射技术方案:解决PC游戏输入兼容性难题的终极方案

AntiMicroX手柄映射技术方案&#xff1a;解决PC游戏输入兼容性难题的终极方案 【免费下载链接】antimicrox Graphical program used to map keyboard buttons and mouse controls to a gamepad. Useful for playing games with no gamepad support. 项目地址: https://gitcod…...

从串口调试到上位机显示:手把手教你用Python写一个STM32 OV2640的JPEG图传接收端

从串口调试到上位机显示&#xff1a;Python实现STM32 OV2640的JPEG图传接收端全解析 当STM32成功通过OV2640摄像头捕获JPEG图像并通过串口发送后&#xff0c;如何稳定接收、解析并实时显示这些数据成为开发者面临的下一个挑战。本文将深入探讨如何用Python构建一个高效可靠的上…...

量子纠缠与动态电路:CHSH不等式在NISQ时代的应用

1. 量子纠缠与CHSH不等式&#xff1a;动态电路性能评估在量子计算领域&#xff0c;高质量的量子纠缠是实现量子优势的关键资源。就像建筑需要坚固的钢筋骨架一样&#xff0c;量子算法依赖于稳定的纠缠态作为其计算基础。然而在当前的NISQ&#xff08;Noisy Intermediate-Scale …...

Nacos启动成功了但访问不了8848?可能是这几个‘隐藏’的权限和路径问题(附排查命令)

Nacos启动成功却无法访问8848&#xff1f;深度排查权限与路径的隐藏陷阱 当你看到Nacos的启动脚本顺利执行完毕&#xff0c;屏幕上打印出"nacos is starting..."的提示时&#xff0c;内心是否已经松了一口气&#xff1f;但紧接着在浏览器中输入http://localhost:8848…...

macOS完整安装器下载工具终极指南:轻松获取Big Sur系统安装包

macOS完整安装器下载工具终极指南&#xff1a;轻松获取Big Sur系统安装包 【免费下载链接】DownloadFullInstaller macOS application written in SwiftUI that downloads installer pkgs for the Install macOS Big Sur application. 项目地址: https://gitcode.com/gh_mirr…...

OpenHarmony FA启动机制全解析:从本地到远程的分布式能力实现

1. 项目概述&#xff1a;从“点击图标”到“界面呈现”的旅程当我们谈论一个应用在OpenHarmony上的启动&#xff0c;尤其是FA&#xff08;Feature Ability&#xff0c;特性能力&#xff09;的启动时&#xff0c;很多人脑海里浮现的可能是“用户点击图标&#xff0c;然后应用打开…...

重磅喜报!中国星坤入围东莞上规资助计划,政企携手共筑智造标杆

近日&#xff0c;东莞市工业和信息化局正式公布 2026 年支持工业企业上规发展做大做强项目拟资助计划&#xff0c;中国星坤&#xff08;XKB Connection&#xff09;凭借在电子连接器领域的技术实力与稳健发展&#xff0c;成功入选&#xff0c;成为东莞智造升级的标杆企业之一东…...

为 OpenClaw 配置 Taotoken 作为自定义 OpenAI 兼容供应商

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 为 OpenClaw 配置 Taotoken 作为自定义 OpenAI 兼容供应商 OpenClaw 是一个流行的开源 Agent 框架&#xff0c;它允许开发者通过配…...