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

2 に答える 2

3

機能するアプローチは多数あり、それぞれに長所と短所がありますが、私は通常、抽象的な Action クラスを定義してから、別の UndoRedoStack クラスを定義することを好みます。

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

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

  1. ApplyAction (プッシュなど)
  2. UndoAction (Pop と同様ですが、既存のアクションを切り捨てたり破棄したりせずに、ポインター/インデックスのみを移動してください)。
  3. RedoAction (プッシュと同様ですが、新しい値をプッシュ/挿入する代わりに、基礎となるスタック/リストに既にある次の値を使用します)。

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

于 2012-04-26T23:58:37.507 に答える
0

You are storing reference objects in your stacks. If you want your method to work, you need to implement a clone() method in your state object, and store a new clone each time, otherwise, changes made are made to each member of the stack, as they all point to the same reference object.

于 2012-09-26T22:50:06.263 に答える