元に戻す/やり直しができる他の機能

一見、元に戻す/やり直しは、ロールバックとやり直し以外には何もできません。 しかし、これは完全に真実ではありません。







XtraRichEditを実装するとき、ドキュメントが変更されたかどうかの質問に答えるプロパティを作成する必要が生じました。 どのように正確にそれを行うか、一見するとそれは非常に明白でした。 isModified変数を取得し、ドキュメントが変更されたときにtrueに設定する必要がありました。 その瞬間、ユーザーがドキュメントを保存したときに、それをfalseに設定する必要がありました。 もちろん、変数の元の値もfalseであったため、ドキュメントは変更されませんでした。



すべてがシンプルで明確であり、私たちは仕事に取り掛かりました。



ドキュメントはさまざまな方法で変更できることがすぐに明らかになりました。 ツールバーに配置できるボタンは100個以上ありました。 また、ドキュメントが変更されるたびに、isModifiedに正しい値を書き込むことを忘れないでください。



もちろん、私たちはこのように動くことを少しも望んでいませんでした。 ドキュメントに絶対に変更が加えられたときに実行されることが保証されている場所をコード内で見つける必要があることは明らかでした。 そして、この場所が元に戻す/やり直し機能であることがわかりました。



なぜ元に戻す/やり直すのですか? それは明らかでした。 ドキュメントの変更については、この変更をロールバックする機能が必要です。 つまり、変更を実行するコードは遅かれ早かれ、元に戻す/やり直し機能とやり取りし、元に戻すバッファーでロールバックとやり直しに必要な情報を使用して要素を登録しました。 したがって、isModified変数を開始する必要がありました。



前の記事からのUndo / Redoの実装を思い出させてください



int currentIndex = -1; public bool CanUndo { get { return currentIndex >= 0; } } public bool CanRedo { get { return items.Count > 0 && currentIndex < items.Count - 1; } } public void Undo() { if (!CanUndo) return; items[currentIndex].Undo(document); this.currentIndex--; } public void Redo() { if (!CanRedo) return; this.currentIndex++; items[currentIndex].Redo(document); } public void Add(HistoryItem item) { CutOffHistory(); items.Add(item); this.currentIndex++; }
      
      





そのため、ドキュメントの変更の兆候をここに追加する必要がありました。



 bool isModified; public bool IsModified { get { return isModified; } set { isModified = value; } } public void Add(HistoryItem item) { CutOffHistory(); items.Add(item); this.currentIndex++; this.isModified = true; }
      
      





Addメソッドの実装では、変数にtrueを割り当てます。 元に戻すバッファに情報を追加すると、ドキュメントの変更を示すだけです。 ドキュメントが保存される場所で、IsModifiedプロパティにfalseを割り当てるだけです。 それだけですか?



すべてがそれほど単純ではないことが判明しました。 ユーザーが空のドキュメントに1文字入力する状況を考えます。 当初、IsModified == false、なぜなら ドキュメントは変更されません。 文字を入力すると、元に戻すバッファに書き込まれ、IsModifiedがtrueになります。 これまでのところ良い。 次に、ロールバック(元に戻す)を実行します。 文字入力はキャンセルされますが、IsModifiedはtrueのままであり、ドキュメントが変更されたことを示します。 そして、これはすでに間違っています。



この場合もシステムが正しく動作するように、Undoメソッドを編集しています。



 public void Undo() { if (!CanUndo) return; items[currentIndex].Undo(document); this.currentIndex--; this.isModified = CanUndo(); }
      
      





動作します。



また、前のシナリオで、ユーザーがロールバック後にREDOを作成した場合はどうなりますか? また、Redoメソッドを編集する必要があるようです。



 public void Redo() { if (!CanRedo) return; this.currentIndex++; items[currentIndex].Redo(document); this.isModified = CanUndo(); }
      
      





そして...



ユーザーに3文字を入力させ、ドキュメントを保存します。 次に、さらに2文字を入力します。 1つのエントリが1つの元に戻すバッファに作成されると考えています。 そのような入力の後、元に戻す/やり直しを実行するとどうなりますか。



わかりやすくするためにテーブルを作成しましょう。







パラグラフ4、8、14に注意してください。パラグラフ4では、ドキュメントが保存されました。 文書の状態が最後の保存時の状態と変わらない場合、文書は変更されていないとみなされます。 ポイント8および14は、この条件に対応しています。



残念ながら、IsModifiedプロパティの実装を慎重に分析すると、一連のアクションに対して正しく機能しないことがわかります。 ただし、コンパイルされた表は、適切な実装を正しく機能させる方法を示しています。 パラグラフ4、8、および14では、CurrentIndex変数は同じ値2を取ることに注意してください。 CurrentIndex == 2の場合、IsModified == false。



ブール変数isModifiedを使用するアプローチは根本的に間違っていることがわかります。 すべてが正しく機能するためには、ドキュメントの保存時にCurrentIndexの値を別の変数に保存し、IsModifiedプロパティを次のように書き換える必要があります。



 const int ForceModifiedIndex = -2; int currentIndex = -1; int unmodifiedIndex = -1; public bool IsModified { get { return currentIndex != unmodifiedIndex; } set { if (value == IsModified) return; if (value) unmodifiedIndex = ForceModifiedIndex; else unmodifiedIndex = currentIndex; } } void CutOffHistory() { int index = currentIndex + 1; while (index < Count) { this[index].Dispose(); items.RemoveAt(index); } if (unmodifiedIndex > currentIndex) unmodifiedIndex = ForceModifiedIndex; }
      
      





unmodifiedIndexフィールドは、currentIndexフィールドの値に等しい-1に初期設定されます。 これは、IsModifiedプロパティが新しい未変更のドキュメントに対してfalseを返すように行われます。



現在のunmodifiedIndex値がcurrentIndexよりも大きい場合、ForceModifiedIndex値をunmodifiedIndex変数に割り当てるようにCutOffHistoryメソッドを変更しました。 なぜそう CutOffHistoryメソッドの条件unmodifiedIndex> currentIndexは、ドキュメントの最後の保存に対応するundo-buffer要素が変更履歴の削除された部分にあることを意味します。 つまり、REDOを使用すると、ドキュメントを最後の保存に対応する状態にすることはできなくなります。 また、ForceModifiedIndex == -2をunmodifiedIndex変数に割り当てたのはなぜですか、なぜ-1を使用できなかったのですか? 実際のところ、この場所に-1を割り当てて、元に戻す操作を「停止」すると、IsModified == falseになり、これは間違った動作になります。 currentIndexが同じ値を受け入れられないことが保証されている-1以外の値が必要です。 -2を選択しました。 ただし、コードを明確にするために、名前付き定数を取得することは理にかなっています。



これで、IsModified実装はどのような状況でも正しく機能します。



そのため、最後に保存してからドキュメントに変更があったかどうかの質問に答えるには、Undoバッファが最適であることがわかりました。



このテーマに関する以前の記事



同じトピックに関する最初の記事。



All Articles