在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();
}

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

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

相关文章

MBox20网关助力汽车零部件企业实现与效率飞跃

背景概述 该企业为一家深耕汽车零部件制造的传统企业&#xff0c;拥有高效运作的自动化生产线体系&#xff0c;专注于汽车发动机零部件的生产制造。然而&#xff0c;面对市场需求的瞬息万变及消费者个性化需求的日益凸显&#xff0c;该企业正遭遇生产效率瓶颈、质量控制挑战、…

【Golang】Go语言编程思想(六):Channel,第六节,并发编程模式

并发模式 下例重新对 channel 的用法进行回顾&#xff1a; package mainimport ("fmt""math/rand""time" )func msgGen(name string) chan string {c : make(chan string)go func(name string) { // 在这个 goroutine 当中向外发送数据i : 0fo…

数造科技入选 2024 爱分析·数据要素 x 厂商全景报告两大场景

近日&#xff0c;爱分析正式发布《2024 爱分析数据要素厂商全景报告》。数造科技凭借在数据要素领域的卓越技术能力和长期深耕的行业大数据解决方案服务积淀&#xff0c;成功入选为协同制造以及区域协同治理两个细分领域的代表厂商。 一、数据要素引领新时代经济发展 在当今时…

Leecode刷题C语言之半有序排列

执行结果:通过 执行用时和内存消耗如下&#xff1a; 代码如下&#xff1a; int semiOrderedPermutation(int* nums, int numsSize) {int first 0, last 0;for (int i 0; i < numsSize; i) {if (nums[i] 1) {first i;}if (nums[i] numsSize) {last i;}}return firs…

Wend看源码-DataX

官方文档 GitHub - alibaba/DataX: DataX是阿里云DataWorks数据集成的开源版本。 简介 DataX 是由阿里巴巴开源的一款功能强大的 ETL 工具。所谓 ETL&#xff0c;具体涵盖了 Extract&#xff08;抽取&#xff09;、Transform&#xff08;转换&#xff09;以及 Load&#xff0…

Vue.js:代码架构组成与布局设置

前言&#xff1a;最近在弄一个开源的管理系统项目&#xff0c;前后端分离开发&#xff0c;这里对前端的Vue框架做一个总结&#xff0c;有遗漏和错误的地方欢迎大家指出~ &#x1f3e1;个人主页&#xff1a;謬熙&#xff0c;欢迎各位大佬到访❤️❤️❤️~ &#x1f472;个人简介…

静态路由与交换机配置实验

1.建立网络拓扑 添加2台计算机&#xff0c;标签名为PC0、PC1&#xff1b;添加2台二层交换机2960&#xff0c;标签名为S0、S1&#xff1b;添加2台路由器2811&#xff0c;标签名为R0、R1&#xff1b;交换机划分的VLAN及端口根据如下拓扑图&#xff0c;使用直通线、DCE串口线连接…

与 Cursor AI 对话编程:2小时开发报修维修微信小程序

本文记录了如何通过与 Cursor AI 对话&#xff0c;全程不写一行代码的情况下&#xff0c;完成一个完整的报修小程序。整个过程展示了 AI 如何帮助我们&#xff1a; 生成代码 、解决问题、优化实现、完善细节。 先看一下效果图&#xff1a; 一、项目配置 首先我是这样和 AI 对…

汽车车牌标记支持YOLO,COCO,VOC三种格式标记,4000张图片的数据集

本数据集支持YOLO&#xff0c;COCO&#xff0c;VOC三种格式标记汽车车牌&#xff0c;无论是新能源汽车还是油车都能识别标记&#xff0c;该数据集一共包含4000张图片 数据集分割 4000总图像数 训练组 70&#xff05; 2800图片 有效集 20&#xff05; 800图片 测…

共享无人系统,便捷生活触手可得

共享无人系统适用各种无人场景:共享麻将室、共享茶室、共享健身房、共享自习室、共享桌球室&#xff0c;实现线上预约&#xff0c;一键预约&#xff0c;自由组合时间&#xff0c;智能通断电&#xff0c;智能语音提醒。 优惠券是常用的营销工具&#xff0c;后台创建之后发放给会…

使用HTML获取商品详情:技术实现与最佳实践

1. 引言 在电子商务领域&#xff0c;获取商品详情是提升用户体验和增强网站功能性的关键。本文将探讨如何使用HTML结合其他技术手段获取商品详情&#xff0c;并展示如何将这些信息有效地呈现给用户。 2. 理解商品详情页面的结构 在开始编码之前&#xff0c;我们需要了解商品…

怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev

本文引用怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev 在 vscode 设置项中配置 gopls 的 ui.navigation.importShortcut 为 Definition 即可。 "gopls": {"ui.navigation.importShortcut": "Definition" }ui.navigation.i…

从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级

从 Zuul 迁移到 Spring Cloud Gateway&#xff1a;一步步实现服务网关的升级 迁移前的准备工作迁移步骤详解第一步&#xff1a;查看源码第二步&#xff1a;启动类迁移第三步&#xff1a;引入 Gateway 依赖第四步 编写bootstrap.yaml第五步&#xff1a;替换路由配置第六步&#…

【金融贷后】贷后运营精细化管理

文章目录 一、贷后专业术语讲解① 什么是贷后&#xff0c;贷后部是干什么的&#xff1f;② 贷后部门常见组织架构&#xff1f;③ 贷后专业术语有哪些&#xff1f; 二、贷后常用作业手段介绍① 贷后产品形态介绍&#xff1f;② 催收常用的方法&#xff1f; 三、贷后策略岗位介绍…

利用cnocr库完成中文扫描pdf文件的文字识别

很多pdf文件文字识别软件都会收费&#xff0c;免费的网页版可能会带来信息泄露&#xff0c;还有一些类似于腾讯AI和百度AI的接口都有调用次数限制&#xff0c;因此&#xff0c;利用识别正确率极高且免费的cnocr库来自己动手做个pdf文件文字识别程序就是一个很不错的选择。以下程…

论文阅读 -- IDENTIFYING THE RISKS OF LM AGENTS WITHAN LM-EMULATED SANDBOX, ICLR2024

论文链接&#xff1a;https://arxiv.org/pdf/2309.15817 目录 ABSTRACT 1 INTRODUCTION 2 BACKGROUND & PROBLEM STATEMENT 3 CONSTRUCTING TOOLEMU 3.1 EMULATING TOOL EXECUTIONS WITH LANGUAGE MODELS 3.2 DESIGNING AUTOMATIC EVALUATIONS WITH LANGUAGE MODEL…

期末复习-计算机网络篇SCAU

第一章&#xff1a;概述 1.计算机网络的特点&#xff0c;互联网发展的三个阶段 特点&#xff1a;连通性、资源共享 三个阶段&#xff1a; 1969-1990&#xff1a;从单个网络ARPANET向互联网发展 1985-1993&#xff1a;建成了三级结构的互联网 1993-现在&#xff1a;全球范…

TesseractOCR-GUI:基于WPF/C#构建TesseractOCR简单易用的用户界面

前言 前篇文章使用Tesseract进行图片文字识别介绍了如何安装TesseractOCR与TesseractOCR的命令行使用。但在日常使用过程中&#xff0c;命令行使用还是不太方便的&#xff0c;因此今天介绍一下如何使用WPF/C#构建TesseractOCR简单易用的用户界面。 普通用户使用 参照上一篇教…

openGauss开源数据库实战二十一

文章目录 任务二十一 使用JDBC访问openGauss数据库任务目标实施步骤一、准备工作 二、下载并安装JavaSE81 下载JavaSE8安装Java8SE并配置环境变量 三、下载并安装eclipse四、下载并安装openGauss的JDBC驱动包五、使用IDEA编写JDBC测试程序1 使用IDEA的SSH连接虚拟机2 创建项目并…

算法——前缀和

如果我们想要得到数组中一段区间的和最朴素的想法肯定是我们从区间的开始下标遍历到结束下标并累加&#xff0c;但是这显然存在一个问题&#xff0c;时间开销是O&#xff08;n&#xff09;的级别&#xff0c;并且有着大量的重复计算&#xff0c;求[n, m]的和后继续求[n…m…p]区…