2012-04-26 11 views
0

これは私の最初のC#アプリケーションです。私は元に戻す/やり直しに関するいくつかの調査をしましたが、役に立たなかった(または分かりやすい)ものは何も見つかりませんでした。したがって、私は誰かが私のプログラム(winformsアプリケーション)のアンドゥ/リドゥ機能を設計する際に助けてくれることを願っています。アプリケーションは、特定のイベント(ボタンクリックなど)中にユーザー指定の値を記録するために後続の子フォームが呼び出されるメインフォームで構成されます。すべてのイベントが処理されると、ビットマップがバッファに描画され、メインフォームのOnPaintイベント中にメインフォーム内のピクチャボックスにロードされます。各入力はカスタムクラスオブジェクトに分割され、別々のListとBindingListに追加されます。 Listに含まれるオブジェクトはグラフィックス(座標などを示すため)に使用され、BindingListのオブジェクトはDataGridViewにいくつかの重要な値を表示するために使用されます。必要なすべてのデータをリストに格納されているので簡単な元に戻す/やり直しに関するガイドが必要

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 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 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

+0

これはあまりにも多くのコードであることに加えて、大量のコードが埋め込まれた1つの大きなテキストのように読み込まれます。ここで段落区切りを使用できます大きなブロックではありません。読みやすく理解しやすくなります。 (あなたはあなたの記事をどこに書いているのかをWYSIWYGのファッションに書いているところでプレビューすることができるので、質問を出す前にそれがどのように表示されるか分かります)。読む。あなたの質問を編集し、段落を適切に書き、少なくとも*可能な内容にまで下げてください。)ありがとう。 –

+3

価値があるのは、通常、[コマンドパターン](http://en.wikipedia.org/wiki/Command_pattern)を使用して元に戻す/やり直しを実装することです。スタック全体に状態全体を格納するのではなく、この状態に至ったアクションを格納します。 –

答えて

3

それぞれに異なる長所と短所がありますが、抽象的なActionクラスを定義して別のUndoRedoStackクラスを定義するのが一般的です。

Actionクラスには、Actionを実装できる2つのメソッド(DoとUndo)があります。これらのActionサブクラスに「状態を変える」ことができるロジックを分離することにより、そのロジックがきちんとカプセル化された状態に保たれます。

UndoRedoStackは、3つのコアメソッドを除いて通常のスタックに似ています。

  • UndoAction(プッシュのような)
    1. ApplyAction(ポップのような、だけ切り詰めるか任意の 既存のアクション廃棄せずにポインタ/インデックスを移動 してください)。
    2. RedoAction(プッシュと同様ですが、 新しいものをプッシュ/挿入するのではなく、その下のスタック/リストに既に という値を使用しています)。

    通常、最大の課題は、元に戻すとやり直すために十分な情報を保持するような方法で各Actionサブクラスを設計することです。しかし、すべての状態操作ロジックを個々のActionサブクラスにカプセル化することは、通常、長期的には維持するのが最も簡単です。

  • +1

    デリゲートの使用は、さまざまなアクションごとにActionのサブクラスを実装するのに適しています([this article](http://tommulgrew.pixelati.com/2011/04/23/implementing-doundo-support-in-net-applications詳細については、-ambda-expressions /を参照) –

    0

    スタックに参照オブジェクトを格納しています。メソッドを動作させるには、状態オブジェクトにclone()メソッドを実装し、毎回新しいクローンを格納する必要があります。そうでない場合は、スタックの各メンバーが変更されます。参照オブジェクト。

    関連する問題