这是我的第一个C#应用程序,完全是自学成才,没有任何先前的软件编程背景。我做了一些关于撤销/重做的研究,但找不到任何有用的(或易于理解)。因此,我希望有人可以帮我设计我的程序的撤销/重做功能(winforms应用程序)。该应用程序由一个主窗体组成,其中将调用后续子窗体以在某些事件(按钮单击等)期间记录用户指定的值。处理完每个事件后,将在缓冲区中绘制位图,然后在主窗体的OnPaint事件期间将其加载到主窗体中的图片框中。每个输入都分成自定义类对象并添加到单独的List和BindingList中。 List中包含的对象用于图形(表示坐标等),而BindingList中的对象用于在DataGridView上显示一些重要的值。只是为了简要说明,代码看起来像这样:
public partial class MainForm : form
{
public class DataClass_1
{
public double a { get; set; }
public double b { get; set; }
public SubDataClass_1 { get; set; }
}
public class SubDataClass_1
{
public double x { get; set; }
public double y { get; set; }
public string SomeString { get; set; }
public CustomEnum Enum_SubDataClass_1 { get; set; }
}
public class DisplayDataClass
{
public string SomeString { get; set; }
public double e { get; set; }
public double f { get; set; }
}
public enum CustomEnum { Enum1, Enum2, Enum3 };
// Lists that contain objects which hold the necessary values to be drawn and displayed
public List<DataClass_1> List_1 = new List<DataClass_1>();
public List<DataClass_2> List_2 = new List<DataClass_2>(); // Object has similar data types as DataClass_1
public BindingList<DisplayDataClass> DisplayList = new BindingList<DisplayDataClass>();
Bitmap buffer;
public MainForm()
{
InitializeComponent();
dgv.DataSource = DisplayList;
}
private void DrawObject_1()
{
// some drawing codes here
}
private void DrawObject_2()
{
// some drawing codes here
}
protected override void OnPaint(PaintEventArgs e)
{
DrawObject_1();
DrawObject_2();
pictureBox1.Image = buffer;
}
// Event to get input
private void action_button_click(object sender, EventArgs e)
{
ChildForm form = new ChildForm(this);
form.ShowDialog();
Invalidate();
}
}
儿童表格'代码看起来像这样:
public partial class ChildForm : form
{
public ChildForm(MainForm MainForm)
{
InitializeComponent();
// Do something
}
private void ok_button_click(object sender, EventArgs e)
{
DataClass_1 Data_1 = new DataClass_1();
DisplayDataClass DisplayData = new DisplayDataClass();
// Parsing, calculations, set values to Data_1 and DisplayData
MainForm.List_1.Add(Data_1);
MainForm.DisplayList.Add(DisplayData);
this.DialogResult = System.Windows.Forms.DialogResult.OK;
this.Close();
}
}
由于所有必要的数据都存储在列表中,并且只有在触发某些事件(主要是按钮点击)后才会更改,因此我尝试使用这些列表来确定运行时的应用程序状态。我实现撤消/重做功能的方法是添加以下代码:
public partial class MainForm : form
{
public class State()
{
public List<DataClass_1> List_1 { get; set; }
public List<DataClass_2> List_2 { get; set; }
public BindingList<DisplayDataClass> DisplayList { get; set; }
// and so on
public State()
{
List_1 = new List<DataClass_1>();
List_2 = new List<DataClass_2>();
DisplayList = new BindingList<DisplayDataClass>();
}
}
State currentState = new State();
Stack<State> undoStack = new Stack<State>();
Stack<State> redoStack = new Stack<State>();
private void MainForm_Shown(object sender, EventArgs e)
{
// Saves original state as first item in undoStack
undoStack.Push(currentState);
}
protected override void OnPaint(PaintEventArgs e)
{
// Update lists from currentState before drawing
List_1 = new List<DataClass_1>(currentState.List_1);
List_2 = new List<DataClass_2>(currentState.List_2);
DisplayList = new BindingList<DisplayDataClass>(currentState.DisplayList);
}
// When undo button is clicked
private void undo_button_Click(object sender, EventArgs e)
{
if (undoStack.Count > 0)
{
redoStack.Push(currentState);
undoStack.Pop();
currentState = undoStack.Last();
Invalidate();
}
}
// When redo button is clicked
private void redo_button_Click(object sender, EventArgs e)
{
// Have not thought about this yet, trying to get undo done first
}
// Events that trigger changes to values held by data objects
private void action_button_Click(object sender, EventArgs e)
{
// Replace the following code with previously stated version
if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
ChildForm form = new ChildForm(this)
UpdateState();
undoStack.Push(currentState);
Invalidate();
}
}
// To update currentState to current values
private void UpdateState()
{
currentState.List_1 = new List<DataClass_1>(List_1);
currentState.List_2 = new List<DataClass_2>(List_2);
currentState.DisplayList = new BindingList<DisplayDataClass>(DisplayList);
// and so on
}
}
结果: 应用程序无法正确执行撤消功能。程序在正常条件下显示正确的输出,但是当触发撤消事件时,无论绘制了多少对象,应用程序都会恢复到初始状态(没有记录数据的状态)。我在更改堆栈的事件期间使用了System.Diagnostics.Debug.WriteLine()来检查undoStack中的计数数量,它似乎给出了正确的计数。我猜这些列表需要以不同的方式复制/克隆?或者我在这里做错了什么?有人可以指导我吗?不需要考虑性能,可读性,资源管理,未来的维护等。
答案 0 :(得分:3)
有很多方法可行,每种方法都有不同的优点和缺点,但我通常喜欢定义一个抽象的Action类,然后是一个单独的UndoRedoStack类。
Action类有两个方法(Do和Undo),每个子类Action都可以实现。您可以将任何可以“更改状态”的逻辑隔离到这些Action子类,从而保持整齐地封装逻辑。
除了三种核心方法外,UndoRedoStack就像一个常规堆栈。
通常我发现最大的挑战就是设计每个Action子类,以便为undo和redo本身维护足够的信息。但是能够将所有状态操作逻辑封装到单个Action子类中通常会使我最容易长期维护。
答案 1 :(得分:0)
您正在堆栈中存储引用对象。如果希望方法有效,则需要在状态对象中实现clone()方法,并且每次都存储一个新的克隆,否则,对堆栈的每个成员进行更改,因为它们都指向相同的参考对象。