2012-04-26 76 views
0

這是我的第一個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中的計數數量,並且它似乎給出了正確的計數。我猜這些列表需要以不同的方式複製/克隆?或者我在這裏做錯了什麼?任何人都可以請指導我?性能,可讀性,資源管理,未來維護等都不需要考慮。

+4

這是太多的代碼堆棧溢出後 – debracey 2012-04-26 23:29:48

+0

除了這是太多的代碼,它讀取像一個大塊的文本與一堆代碼塞入英寸你可以在這裏使用分段符,所以你的文本不是一個大塊 - 它使閱讀和理解變得更容易。 (你可以在下面的地方立即預覽你的帖子,在那裏你用幾乎所見即所得的方式來書寫它,所以你可以在提交問題之前看到它是如何出現的。)我試圖瀏覽大量的文本,但它們太難了讀書。請編輯您的問題,正確編寫段落,並將其縮減爲*最少*可能的內容。)謝謝。 – 2012-04-26 23:33:55

+3

對於它的價值,通常使用[Command pattern](http://en.wikipedia.org/wiki/Command_pattern)來實現撤銷/重做。不是將整個狀態存儲在撤消堆棧中,而是存儲導致此狀態的操作。 – 2012-04-26 23:57:17

回答

3

有很多方法可以工作,每種方法都有不同的長處和短處,但我通常喜歡定義一個抽象的Action類,然後是一個單獨的UndoRedoStack類。

Action類將有兩個方法(Do和Undo),每個子類Action可以實現。你將任何可以「改變狀態」的邏輯都分離到這些Action子類中,從而保持整個邏輯的封裝。

UndoRedoStack就像一個普通的堆棧,除了有三個核心方法。

  1. ApplyAction(如推送)
  2. UndoAction(喜歡流行音樂,但請務必只 移動指針/索引,而不截斷或丟棄任何 現有操作)。
  3. 重做動作(與推,但您使用下一個值 已在底層的堆棧/列表中,而不是推/插入一個新的 )。

通常我覺得有個最大的挑戰就是以這樣的方式設計每個Action的子類,它保持足夠的信息來都撤消和重做本身。但是能夠將所有狀態操作邏輯封裝到單個Action子類中通常會使我從長遠來看最容易維護。

+1

使用委託對於爲每個不同的動作實現Action的子類是一個很好的選擇(請參閱[本文]](http://tommulgrew.pixelati.com/2011/04/23/implementing-doundo-support-in-net-applications -with-lambda-expressions /)獲取詳細信息) – 2012-04-27 00:01:08

0

您正在將參考對象存儲在堆棧中。如果你想讓你的方法工作,你需要在你的狀態對象中實現一個clone()方法,並且每次都存儲一個新的克隆,否則,對每個堆棧成員進行更改,因爲它們都指向相同參考對象。