绘制多条自由折线或曲线——添加撤销功能

3

我正在尝试创建一个带有撤销和重做功能的简单绘图应用程序。我认为可以将您正在绘制的内容添加到列表中,然后调用该列表来绘制所有内容。然后,撤消应该只会删除最后添加的项目并重新绘制所有内容。问题是,我如何将我所绘制的内容添加到列表中,并使用该列表进行撤消操作?

我正在使用位图重绘方法。 这是我绘制的方法:

    Point start, end;
    bool painting;
    private List<PointF> myPoints = new List<PointF>();

    private void pnlMain_MouseDown(object sender, MouseEventArgs e)
    {
        start = e.Location;
        painting = true;
    }

    private void pnlMain_MouseUp(object sender, MouseEventArgs e)
    {
        painting = false;
    }

    private void pnlMain_MouseMove(object sender, MouseEventArgs e)
    {
        if (painting == true)
        {
            end = e.Location;
            g.DrawLine(p, start, end);
            myPoints.Add(e.Location);
            pnlMain.Refresh();
            start = end;
        }
    }

    private void btnUndo_Click(object sender, EventArgs e)
    {
        g.Clear(cldFill.Color);
        if (myPoints.Count > 2)
        {
            myPoints.RemoveAt(myPoints.Count - 1);
            g.DrawCurve(p, myPoints.ToArray());
        }
        pnlMain.Refresh();
        //This works but you have to spam it to get rid of
        //a line and does some weird connections.
    }

你需要在 List<T> 中收集你所绘制的坐标。有关如何收集它们的代码,请参见我的这里的评论!另外:你的绘图方式不正确。你似乎缓存了一个 Graphics 对象。这是错误的!请在 Paint 事件中绘制所有内容! - TaW
没问题。如果你实现了我在(所有)我链接到的评论中给出的代码,你只需要添加一个按钮点击事件,其中包含curves.Remove(curves.Last)); pnlMain.Invalidate(); - TaW
1个回答

7
你需要将线条存储在一个 List<List<Point>> 中。列表的每个元素包含绘图时使用 down、move 和 up 绘制的点。下一条线将存储在列表的下一个元素中。每次撤销都会删除上次的绘制。
在你的窗体上放置这个控件的实例,它将为你处理绘图。要执行撤销,请调用其 Undo 方法。
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

public class DrawingSurface : Control
{
    public DrawingSurface() { this.DoubleBuffered = true; }
    List<List<Point>> Lines = new List<List<Point>>();
    bool drawing = false;
    protected override void OnMouseDown(MouseEventArgs e) {
        Lines.Add(new List<Point>());
        Lines.Last().Add(e.Location);
        drawing = true;
        base.OnMouseDown(e);
    }
    protected override void OnMouseMove(MouseEventArgs e) {
        if (drawing) { Lines.Last().Add(e.Location); this.Invalidate(); }
        base.OnMouseMove(e);
    }
    protected override void OnMouseUp(MouseEventArgs e) {
        if (drawing) {
            this.drawing = false;
            Lines.Last().Add(e.Location);
            this.Invalidate();
        }
        base.OnMouseUp(e);
    }
    protected override void OnPaint(PaintEventArgs e) {
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        foreach (var item in Lines)
            e.Graphics.DrawLines(Pens.Black, item.ToArray()); /*or DrawCurve*/
    }
    public void Undo() {
        if (Lines.Count > 0) { this.Lines.RemoveAt(Lines.Count - 1); this.Invalidate(); }
    }
}

注意

  • 使用这个逻辑,你可以简单地使用另一个 List<List<Point>> 实现 redo。只需要在 undo 前将最后一项复制到 redo 列表中,使用 RedoBuffer.Add(Lines.Last());。然后对于每个 redo 命令,只需将 redo 缓冲区的最后一项添加到 Lines 中并从 redo 缓冲区中删除它。每次鼠标按下后还应清除 redo 缓冲区。
  • 根据您的需求,您可以使用 DrawLinesDrawCurve 中的任何一个。 DrawLines 会画出折线,而 DrawCurve 会画出更平滑的曲线。

  • 我喜欢将 Lines.Count > 0 封装在属性中,例如 bool CanUndo,并使其可以从控件外部访问。

  • 这只是一个示例,您可以轻松扩展解决方案。例如,您可以创建一个包含 List<Point>LineWidthLineColor 等的 Shape 类,并使用 List<Shape> 执行任务。


这是一个非常好的类!您能否向我展示如何与该类进行交互并告诉它绘制矩形或圆形的示例? - Alex Diamond
为了扩展这个例子,您可以创建一个包含抽象Draw方法和一些派生类(如RectangleCircle)的Shape类,实现该方法并包含所需的坐标属性。然后,您可以使用List<Shape>来存储要绘制的对象。为了绘制形状,您应该有一个指示您正在绘制的形状的标志,并基于此标志将所需的形状添加到该列表中。在OnPaint方法中,调用每个形状的Draw方法来绘制它们。 - Reza Aghaei
这几乎是全部的想法。它很简单,但需要更多的代码。此外,它可以用不同的方式实现,所以如果你开始编写应用程序,请随时问与你的实现有关的问题。希望这个答案和评论能帮助到你 :) - Reza Aghaei
显而易见:这是一个__无限制__的撤销 :-) - 请参阅此处以获取保存工作的示例 - TaW

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