Undo/Redo

単純な Memorable オブジェクト

例によってLINQPadで動作を確認しつつ進めます。 LINQPadを起動し、下記の設定をします。

  • 参照設定に MoNo.dll, MoNo.Basics.dll を追加
  • namespace に MoNo を追加
  • Language を F# Expression に設定

次のコードを実行してみましょう。

// ActiveHistory を設定します
Core.History.ActiveHistory <- Core.History()

// Undo/Redo 可能なオブジェクト(整数値)を生成し、Historyの管理下に置きます
let a = CoreUT.Memorable 1
a.AddRef() // 参照カウンタをインクリメント → Undo/Redo対象となる
a.Value.Dump()  // 1 と出力される

// 値を 2 に変更しコミットします
a.Value <- 2
Core.History.CommitActiveHistory() |> ignore
a.Value.Dump()  // 2 と出力される

// Undo します(値が 1 に戻ります)
Core.History.UndoActiveHistory() |> ignore
a.Value.Dump()  // 1 と出力される

// Redo します(再び値が 2 に設定されます)
Core.History.RedoActiveHistory() |> ignore
a.Value.Dump()  // 2 と出力される

これが最もシンプルな Undo/Redo のサンプルコードです。

IMemorable インターフェイス

Undo/Redo の対象となるオブジェクトは、下記の IMemorable インターフェイスを実装しています。(C#)

// オブジェクトを Undo/Redo に対応するためにはこのインターフェイスを実装する必要があります。
public interface IMemorable
{
  void AddRef();  // ヒストリからの参照カウントをインクリメントします。
  void Release(); // ヒストリからの参照カウントをデクリメントします。
}

public interface IMemorable<T> : IMemorable
{
  T Value { get; set; }  // 保持している値。変更履歴の Undo/Redo が可能です。
}

冒頭のサンプルコードで用いた CoreUT.Memorable 関数は、入力された値を IMemorable<'T> オブジェクトに包んで返す関数です。

AddRef / Release はヒストリからの参照カウントをインクリメント/デクリメントする関数です。 この設計は、ある一つのオブジェクト(Aとします)が複数のオブジェクト(B,Cとします)から共有して参照されているケースで役に立ちます。 BやCがヒストリ管理下に入れられたときは、それらが参照するAもヒストリ管理下に入れる必要があります。 次にBのみがヒストリ管理下から外されたとき、Aは依然としてCを経由してヒストリ管理下に残らなければなりません。 このような管理を行うために参照カウンタが必要となります。

(書くこと)

  • MemorableReactive について
  • MoNo.Core.ObservableCollection について
  • WPF のコマンドとの連動について
  • 適切な設計について