在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);
    else
        Pt2[MovingSegment] = new Point(x, y);

    // Redraw.
    picCanvas.Invalidate();
}

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

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

相关文章

快速学习selenium基础操作

全篇大概19000字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间1h 什么是Selenium&#xff1f; Selenium是一系列自动化工具集的统称&#xff0c;官方工具有 Selenium IDE、Selenium WebDriver、Selenium Grid&#xff0c; 主要用于桌面端Web应用程序的自动化。能够通…

说下JVM中一次完整的GC流程?

大家好&#xff0c;我是锋哥。今天分享关于【说下JVM中一次完整的GC流程&#xff1f;】面试题。希望对大家有帮助&#xff1b; 说下JVM中一次完整的GC流程&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在JVM中&#xff0c;垃圾回收&#xff08;GC&am…

Vue.createApp的对象参数

目录 template 属性 data 属性 methods 属性 疑问 function 函数的两种写法 methods 属性中 this 的指向 总结 Vue 实例是通过 Vue.createApp() 创建的&#xff0c;该函数需要接收一个对象作为参数&#xff0c;该对象可添加 template、data、methods 等属性。 template …

前端H5移动端基础框架模板 :Vue3 + Vite5 + Pinia + Vant4 + Sass + 附源码

技术栈选用 Vue3 Vite5 Pinia Vant4 Sass 源码地址&#xff1a; git clone https://gitee.com/gaiya001/h5-APP.git1. 1.vite.config.js文件配置 ** import { defineConfig } from vite // 导入 Vite 的配置函数 import vue from vitejs/plugin-vue // 导入 Vue 插件 i…

LabVIEW动态加载语言与VI调用 附件有程序

此LabVIEW设计通过动态加载语言资源和调用VI来实现多语言支持和模块化功能。它适用于需要灵活语言切换和动态VI管理的场景&#xff0c;但在开发和维护中有一些需要优化的地方。以下是详细的分析和改进建议。 优点&#xff1a; 灵活的语言切换&#xff1a; 用户可通过加载不同语…

分布式搜索引擎之elasticsearch基本使用2

分布式搜索引擎之elasticsearch基本使用2 在分布式搜索引擎之elasticsearch基本使用1中&#xff0c;我们已经导入了大量数据到elasticsearch中&#xff0c;实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。 所以j接下来&#xff0c;我们研究下…

使用webrtc-streamer查看实时监控

摄像头配置&#xff08;海康摄像头为例&#xff09; 摄像头视频编码应改成H264格式 webrtc-streamer下载 webrtc-streamer下载地址 下载后解压出来双击运行&#xff0c;端口默认8000 VUE2项目引入文件 在项目静态文件“public”中需引入两个js文件“webrtcstreamer.js”与“…

鸿蒙调试打包(非正式打包)

文章目录 前言第一步&#xff1a;生成.p12和.csr文件第二步&#xff1a;申请证书的前置步骤第三步&#xff1a;申请证书 前言 HarmonyOS 应用打包后的文件为.app 格式&#xff0c; android 打包后的文件为.apk&#xff0c;IOS 打包后的文件为.apa HarmonyOS通过数字证书&#…

如何利用DBeaver配置连接MongoDB和人大金仓数据库

最近根据国产化要求&#xff0c;需要使用国产数据库&#xff0c;但习惯使用DBeaver连接各种成熟的商业或开源数据库。因此&#xff0c;就想着如何继续基于该工具&#xff0c;连接MongoDB和人大金仓数据库&#xff0c;查了半天很多地方说法不统一&#xff0c;所以自己就简单整理…

手机租赁系统开发指南一站式服务流程解析

内容概要 手机租赁系统的开发是一个复杂但有趣的过程&#xff0c;像搭建乐高一样&#xff0c;只要找到合适的模块&#xff0c;就能打造出一个宾至如归的租赁平台。在这部分&#xff0c;我们将对开发流程的整体结构进行简要概述&#xff0c;并指出每个环节的重要性。 首先&…

Linux中vi和vim的区别详解

文章目录 Linux中vi和vim的区别详解一、引言二、vi和vim的起源与发展三、功能和特性1、语法高亮2、显示行号3、编辑模式4、可视化界面5、功能扩展6、插件支持 四、使用示例1、启动编辑器2、基本操作 五、总结 Linux中vi和vim的区别详解 一、引言 在Linux系统中&#xff0c;vi和…

信息安全管理——应急响应、灾备与恢复

安全管理 信息安全管理 信息安全管理是指通过维护信息的机密性、完整性和可用性来管理和保护信息资产&#xff0c;是对信息安全保障进行指导、规范和管理的一系列活 动和过程。 信息安全管理的意义在于他是组织或者公司管理体系的一个重要环节 &#xff0c;比如说一所大学就是…

数据库数据恢复—ORACLE常见故障有哪些?如何恢复数据?

Oracle数据库常见故障表现&#xff1a; 1、ORACLE数据库无法启动或无法正常工作。 2、ORACLE ASM存储破坏。 3、ORACLE数据文件丢失。 4、ORACLE数据文件部分损坏。 5、ORACLE DUMP文件损坏。 Oracle数据库数据恢复方案&#xff1a; 1、检测存放数据库的服务器/存储设备是否存…

使用 WebStorm 导入已有的 Vue 项目并运行的步骤与注意事项

目录 1. 引言2. WebStorm 环境准备 2.1 安装 WebStorm2.2 配置 Node.js 和 npm2.3 使用 nvm 管理 Node.js 和 npm 版本2.4 npm 版本与 Vue 版本对应关系 3. 导入已有的 Vue 项目 3.1 打开 Vue 项目3.2 安装项目依赖3.3 使用 nvm 控制 Node.js 和 npm 版本 4. 运行 Vue 项目 4.…

C++感受15-Hello Object 封装版 -下(上机)

一步步&#xff08;分解成九段小视频&#xff09;带你完成“选美大赛海选报名程序”&#xff0c;从需求到设计再到实现&#xff0c;完整体验&#xff0c;同时边学边用以下知识点&#xff1a; 转换构造、复制构造、转发构造特定成员函数默认实现成员数据初始化列表成员数据默认值…

深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解

目录 深入理解 JavaScript 中的 Array.find() 方法&#xff1a;原理、性能优势与实用案例详解 一、引言&#xff1a;为什么要使用Array.find() 二、Array.find()的使用与技巧 1、基础语法 2、返回值 3、使用技巧 三、Array.find()的优势与实际应用案例 1、利用返回引用…

探索 LeNet-5:卷积神经网络的先驱与手写数字识别传奇

一、引言 在当今深度学习技术蓬勃发展的时代&#xff0c;各种复杂而强大的神经网络架构不断涌现&#xff0c;如 ResNet、VGG、Transformer 等&#xff0c;它们在图像识别、自然语言处理、语音识别等众多领域都取得了令人瞩目的成果。然而&#xff0c;当我们回顾深度学习的发展历…

希迪智驾持续亏损8.2亿:毛利率下滑,冲刺“自动驾驶矿卡第一股”

《港湾商业观察》黄懿 近日&#xff0c;希迪智驾&#xff08;湖南&#xff09;股份有限公司&#xff08;下称“希迪智驾”&#xff09;向港交所主板递交上市申请&#xff0c;联席保荐人为中金公司、中信建投国际、中国平安资本&#xff08;香港&#xff09;。 资料显示&#…

Vue 提供了Transition,可以帮助你制作基于状态变化的过渡和动画

官方文档&#xff1a;https://cn.vuejs.org/guide/built-ins/transition.html Transition​ Vue 提供了两个内置组件&#xff0c;可以帮助你制作基于状态变化的过渡和动画&#xff1a; <Transition> 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用…

Ansible自动化运维(三)playbook剧本详解

Ansible自动化运维这部分我将会分为五个部分来为大家讲解 &#xff08;一&#xff09;介绍、无密钥登录、安装部署、设置主机清单 &#xff08;二&#xff09;Ansible 中的 ad-hoc 模式 模块详解&#xff08;15&#xff09;个 &#xff08;三&#xff09;Playbook 模式详解 …