如何使用c#撤消绘制操作

我在picturebox中加载了一个图像。我通过鼠标点击事件对图像执行绘制操作。当在该点单击鼠标时,它绘制一个黑色的小矩形区域。现在我想实现撤消操作这个。当我点击一个按钮时,最后的绘画操作应该被撤消。这是我的绘画操作代码..

private void pictureBox1_MouseClick(object sender, MouseEventArgs e) { rect.Width = 0; rect.Height = 0; pictureBox1.Invalidate(); int radius = 10; //Set the number of pixel you want to use here //Calculate the numbers based on radius int x0 = Math.Max(eX - (radius / 2), 0), y0 = Math.Max(eY - (radius / 2), 0), x1 = Math.Min(eX + (radius / 2), pictureBox1.Width), y1 = Math.Min(eY + (radius / 2), pictureBox1.Height); Bitmap bm = pictureBox1.Image as Bitmap; //Get the bitmap (assuming it is stored that way) for (int ix = x0; ix < x1; ix++) { for (int iy = y0; iy < y1; iy++) { bm.SetPixel(ix, iy, Color.Black); //Change the pixel color, maybe should be relative to bitmap } } pictureBox1.Refresh(); //Force refresh } 

任何人请帮助我如何撤消上次执行的操作。

因为您在内存中使用光栅图像,所以不能只撤消操作。 可以有多种解决方案:

  1. 将原始图像保留在内存中,并为每个操作保留绘图参数:绘制的内容,位置,颜色。 当您需要撤消时,您只需要从头到尾重复所有操作(您可能还有一个存储中间图像的控制点)
  2. 在每次操作之后保留图像的快照 – 这将非常耗费内存。 在撤消时 – 恢复列表中的上一张图片。
  3. 保持更改的像素 – 在每个操作上分析前一个图像和新图像并保持像素更改。 您可以通过复制这些像素来恢复到之前的状态。

一种方法是在最后一次操作之前存储bitmap ,然后简单地将该bitmap重新绘制到图片picturebox 。 这可能不是最有效的,因为位图可能会变得非常大,这取决于它们的大小,但却是最简单和最快捷的方法之一。

另一种更有效的方法是以某种方式记录前后位图之间的差异,例如已经改变的像素和它们之前的颜色,并将这些像素回滚到它们的原始颜色。 这样只保存已更改的像素,但编码更复杂。

您可以声明一个私有的Image字段并将图像状态保存到它以便在撤消时使用,使用Memento Design Pattern来保存和加载对象“image”状态是最好的做法,对它进行掠夺。

但是,不是一次撤消/重做操作,更好的解决方案是实现多撤消/重做策略,如下所示:

  • 声明两个堆栈一个用于撤消,另一个堆栈用于重做:
  • 撤消从撤消堆栈弹出动作“图像”或“图像状态”并将其推送到重做堆栈时。
  • 重做从重做堆栈弹出动作“图像”或“图像状态”并将其推送到撤消堆栈时。

例:

 private Stack _undoStack = new Stack(); private Stack _redoStack = new Stack(); private readonly object _undoRedoLocker = new object(); private void Undo() { lock (_undoRedoLocker) { if (_undoStack.Count > 0) { _redoStack.Push(_undoStack.Pop()); //OnUndo(); pictureBox1.Image = _redoStack.Peek(); pictureBox1.Refresh(); } } } private void Redo() { lock (_undoRedoLocker) { if (_redoStack.Count > 0) { _undoStack.Push(_redoStack.Pop()); //OnRedo(); pictureBox1.Image = _undoStack.Peek(); pictureBox1.Refresh(); } } } //And whenever image need to be modified, add it to the undo stack first and then modify it private void UpdateImageData(Action updateImage) { lock (_undoRedoLocker) { _undoStack.Push(pictureBox1.Image);//image); try { //manipulate the image here. updateImage(); } catch { _undoStack.Pop();//because of exception remove the last added frame from stack throw; } } } private void pictureBox1_MouseClick(object sender, EventArgs e) { UpdateImageData(delegate() { rect.Width = 0; rect.Height = 0; pictureBox1.Invalidate(); int radius = 10; //Set the number of pixel you want to use here //Calculate the numbers based on radius int x0 = Math.Max(eX - (radius / 2), 0), y0 = Math.Max(eY - (radius / 2), 0), x1 = Math.Min(eX + (radius / 2), pictureBox1.Width), y1 = Math.Min(eY + (radius / 2), pictureBox1.Height); Bitmap bm = pictureBox1.Image as Bitmap; //Get the bitmap (assuming it is stored that way) for (int ix = x0; ix < x1; ix++) { for (int iy = y0; iy < y1; iy++) { bm.SetPixel(ix, iy, Color.Black); //Change the pixel color, maybe should be relative to bitmap } } pictureBox1.Refresh(); //Force refresh } } 
  • 每当用户想要撤消表格上的“例如按Ctrl + Z”时,只需调用Undo();
  • 每当用户想要重做表单时,只需调用Redo();

注意:我没有测试上面的代码,但它可能正常工作,如果你发现任何问题留下评论