C#中用鼠标绘制直线的正确方法是什么?

10

这是我用鼠标绘制自定义线条的绘图代码,你能帮我找到正确的做法吗?

namespace Grafi
    {
        public partial class Form1 : Form
        {

            bool isDrawing = false;
            Point prevPoint;

            public Form1()
            {
                InitializeComponent();
            }

            private void chartTemperature_MouseDown(object sender, MouseEventArgs e)
            {
                isDrawing = true;
                prevPoint = e.Location;
            }

            private void chartTemperature_MouseMove(object sender, MouseEventArgs e)
            {
                Pen p = new Pen(Color.Red, 2); 
                if (isDrawing)
                {
                    Graphics g = chartTemperature.CreateGraphics();    
                    g.DrawLine(p, prevPoint, e.Location);
                    prevPoint = e.Location;

                    numOfMouseEvents = 0;              
                }
                p.Dispose();
            }

            private void chartTemperature_MouseUp(object sender, MouseEventArgs e)
            {
                isDrawing = false;
            }
        }
    }

问题是当我调整表单大小时,我的线条会消失。每当触发 onPaint 事件时,它就会消失。


1
你能解释一下,什么是“properly”吗?无论如何,你都需要处理“Mouse Up/Down/Move”事件。 - KMån
澄清一下,这个问题是为了回答之前一个问题中提出的问题而提出的:https://dev59.com/CFHTa4cB1Zd3GeqPU9ba#4164625。他想知道修改现有代码以在“Paint”事件中绘制而不是使用“CreateGraphics”的最佳方法。 - Cody Gray
CodeProject上有各种各样的良好的绘画示例。它们从非常简单到相当复杂不等。看看其中一些。你会看到可以正确完成这个任务的不同方法,尽管所有的方法都涉及将鼠标移动点保存在集合中,并在绘画事件处理程序中重绘它们。 - Paul Sasik
刚决定根据绘画记录笔划的想法创建一个示例。请查看下面我的答案中的代码。(我实际测试过它了。) - Paul Sasik
4个回答

10

试试这个...这是一种描绘笔画的方法,实现非常简单,尽可能接近你自己的代码。 笔画是鼠标移动的单独集合。在down和up之间的每个鼠标移动都记录为笔画,所有笔画都被收集,然后在触发paint事件时重新绘制。这个例子很简单,但可能是一个很好的起点。

请注意,您必须为您的图表对象添加paint处理程序。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace Grafi
{
    public partial class Form1 : Form
    {
        bool isDrawing;
        // our collection of strokes for drawing
        List<List<Point>> _strokes = new List<List<Point>>();
        // the current stroke being drawn
        List<Point> _currStroke;
        // our pen
        Pen _pen = new Pen(Color.Red, 2); 

        public Form1()
        {
            InitializeComponent();
        }

        private void chartTemperature_MouseDown(object sender, MouseEventArgs e)
        {
            isDrawing = true;
            // mouse is down, starting new stroke
            _currStroke = new List<Point>();
            // add the initial point to the new stroke
            _currStroke.Add(e.Location);
            // add the new stroke collection to our strokes collection
            _strokes.Add(_currStroke);
        }

        private void chartTemperature_MouseMove(object sender, MouseEventArgs e)
        {
            if (isDrawing)
            {
                // record stroke point if we're in drawing mode
                _currStroke.Add(e.Location);
                Refresh(); // refresh the drawing to see the latest section
            }
        }

        private void chartTemperature_MouseUp(object sender, MouseEventArgs e)
        {
            isDrawing = false;
        }

        private void chartTemperature_Paint(object sender, PaintEventArgs e)
        {
            // now handle and redraw our strokes on the paint event
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            foreach (List<Point> stroke in _strokes.Where(x => x.Count > 1))
                e.Graphics.DrawLines(_pen, stroke.ToArray());
        }
    }
}

我尝试了你的方法,它确实有效,但是当我绘制时,我的面板会“闪烁”。这可能是由于连续的刷新调用(我使用Invalidate(false)方法)引起的。你知道有什么解决办法吗? - Nick
1
@Nick:是的,有的。这其实是一个单独的问题,但你需要将控件的DoubleBuffered属性设置为true。双缓冲可以防止闪烁。在这里查看:http://msdn.microsoft.com/en-us/library/system.windows.forms.control.doublebuffered(v=vs.110).aspx 但要注意一点:我上次进行自定义绘制是在.NET 2.0版本中。在那个版本中,对于某些控件,设置DoubleBuffer属性并不像Form.DoubleBuffered那么简单。有些控件需要不同的调用方式,所以要小心。 - Paul Sasik

1
你当前的实现有什么问题吗?它能正常工作吗,还是你只想为一个已经工作的函数改进代码呢?
我觉得你的逻辑看起来挺好的。不过,我建议给Pen添加一个using语句,像这样:
private void chartTemperature_MouseMove(object sender, MouseEventArgs e)
{
  using( Pen p = new Pen(Color.Red, 2)){
    if (isDrawing)
    {
      Graphics g = chartTemperature.CreateGraphics();    
      g.DrawLine(p, prevPoint, e.Location);
      prevPoint = e.Location;

      numOfMouseEvents = 0;              
    }
  }
}

这样,即使在创建后出现任何异常并调用Dispose之后,您的笔也将被处理。

但是,您还可以考虑将Pen作为类变量,这样每次移动鼠标时就不必创建和处理它。


问题在于当我调整窗体大小时,我的线条消失了。它会在每次触发onPaint事件时消失。 - Primoz
@Primoz - 这是因为您没有在所有地方存储点。创建一个StartPoints和EndPoints的集合,在MouseUp事件中将当前起始点和终点添加到集合中。然后重写OnPaint事件,当窗口重新绘制时,通过图表的图形对象循环遍历集合并绘制线条。 - Øyvind Bråthen

1

你需要将你的线条存储在某个地方。

你需要执行以下步骤:

  1. 在主类中创建一个存储点的地方,例如一个 ArrayList<ArrayList<Point>> - 每个 ArrayList<Point> 包含一条线上的点列表。
  2. 等待鼠标按下事件,并在线条列表末尾创建一个新线条的数组(例如一个 new ArrayList<Point>)。
  3. 等待鼠标移动事件,并在鼠标拖动时将一个点添加到列表中的最后一条线条中。在此处请求更新窗口。
  4. 在你的 paint 中,遍历所有线条,并绘制数组中每条线条的每个点。
  5. 要清除绘图,请简单地用空列表替换数组,并更新窗口。

如果你不将你的线条存储在某个地方,它们将会丢失。这有意义吗?

另一种存储线条的方式是使用Canvas对象,它会记住所绘制内容的像素地图并自动绘制。如果您不介意将您的线条数据作为矢量点,而且还想使用图像或颜色,则这可能是更好的方法。

0

我之前发布了一个关于如何使用鼠标移动绘制一条线的解决方案。这对你应该有效。

  Point mAnchorPoint = new Point(200, 200);
  Point mPreviousPoint = Point.Empty;

  private void panel1_MouseMove(object sender, MouseEventArgs e)
  {
     if (mPreviousPoint != Point.Empty)
     {
        // Clear last line drawn
        ControlPaint.DrawReversibleLine(PointToScreen(mAnchorPoint), PointToScreen(mPreviousPoint), Color.Pink);
     }

     // Update previous point
     mPreviousPoint = e.Location;
     mPreviousPoint.Offset(myPanel1.Location);

     // Draw the new line
     ControlPaint.DrawReversibleLine(PointToScreen(mAnchorPoint), PointToScreen(mPreviousPoint), Color.Pink);
  }

基本上,您可以在鼠标移动时每次绘制一条线。如果有先前的线并且您仍在移动鼠标,则擦除该线并绘制新线。请注意,此示例基于特定的Panel(在此示例中为myPanel1)进行偏移。请相应地进行调整。如果调整控件大小,则需要使用锚点前一个点重新绘制线条。

请发布一些代码片段和简要说明,以及您的链接。回复@CRoss,我会点赞。 - C. Ross
@CRoss - 已更新。如果您需要更多细节,请告诉我。 - SwDevMan81

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接