元に戻す/やり直し機能を実装する方法について考えてみてください-テキストエディターのように。どのアルゴリズムを使用する必要があり、何を読むことができるか。ありがとう。
アンドゥの種類の2つの主要な区分について知っています
テキストエディターの場合、この方法で状態を生成することは計算集約的ではありませんが、Adobe Photoshopのようなプログラムでは、計算集約的すぎるか、まったく不可能です。たとえば、-Blurアクションの場合、de-Blurアクションを指定しますが、データがすでに失われているため、元の状態に戻すことはできません。そのため、状況に応じて-論理的な逆アクションの可能性とその実現可能性に応じて、これら2つの広範なカテゴリから選択し、必要な方法で実装する必要があります。もちろん、あなたのために働くハイブリッド戦略を持つことは可能です。
また、Gmailのように、アクション(メールの送信)がそもそも行われないため、時間制限付きの取り消しが可能な場合があります。したがって、あなたはそこで「元に戻す」のではなく、単にアクション自体を「していない」のです。
私は2つのテキストエディタをゼロから作成しましたが、どちらも非常に原始的な形式の元に戻す/やり直し機能を採用しています。 「プリミティブ」とは、機能を実装するのが非常に簡単だったことを意味しますが、非常に大きなファイル(たとえば>> 10 MB)では不経済です。ただし、システムは非常に柔軟です。たとえば、無制限のレベルの取り消しをサポートします。
基本的に、次のような構造を定義します
type
TUndoDataItem = record
text: /array of/ string;
selBegin: integer;
selEnd: integer;
scrollPos: TPoint;
end;
そして、配列を定義します
var
UndoData: array of TUndoDataItem;
次に、この配列の各メンバーは、テキストの保存状態を指定します。ここで、テキストの各編集(文字キーダウン、バックスペースダウン、キーダウンの削除、カット/貼り付け、マウスによる移動など)で、1秒のタイマーを(再)開始します。トリガーされると、タイマーはUndoData
配列の新しいメンバーとして現在の状態を保存します。
元に戻す(Ctrl + Z)で、エディターをUndoData[UndoLevel - 1]
状態に復元し、UndoLevel
を1つ減らします。デフォルトでは、UndoLevel
はUndoData
配列の最後のメンバーのインデックスに等しくなります。再実行(Ctrl + YまたはShift + Ctrl + Z)で、エディターをUndoData[UndoLevel + 1]
状態に復元し、UndoLevel
を1つ増やします。もちろん、UndoLevel
がUndoData
配列の長さ(マイナス1)に等しくないときに編集タイマーがトリガーされる場合、UndoLevel
の後のこの配列のすべての項目をクリアします。 、Microsoft Windowsプラットフォームでよくあることですが(ただし、正しく思い出すとEmacsの方が良いです-Microsoft Windowsのアプローチの欠点は、多くの変更を取り消してから誤ってバッファーを編集すると、以前のコンテンツ( undid)は永久に失われます)。この配列の縮小をスキップすることもできます。
別のタイプのプログラム、たとえば画像エディターでは、同じ手法を適用できますが、もちろん、UndoDataItem
構造がまったく異なります。メモリをそれほど必要としないより高度なアプローチは、元に戻すレベルの間でchangesのみを保存することです(つまり、「alpha\nbeta\gamma」と「alpha\nbeta\ngamma\ndelta」の場合、「alpha\nbeta\ngamma」と「ADD\ndelta」を保存できます(意味がわかる場合)。各変更がファイルサイズに比べて非常に小さい非常に大きなファイルでは、これにより元に戻すデータのメモリ使用量が大幅に減少しますが、実装が難しくなり、エラーが発生しやすくなります。
少し遅れますが、ここに行きます:あなたは特にテキストエディターを参照します。以下は、編集しているものに適応できるアルゴリズムを説明しています。関係する原則は、あなたが行った各変更を再作成するために自動化できるアクション/命令のリストを保持することです。元のファイルに変更を加えないで(空でない場合)、バックアップとして保管してください。
元のファイルに加えた変更の前後のリンクリストを保持します。このリストは、ユーザーが実際に変更するまで一時ファイルに断続的に保存されます保存変更:この場合、変更を新しいファイルに適用し、古いファイルをコピーし、同時に変更を適用します。元のファイルの名前をバックアップに変更し、新しいファイルの名前を正しい名前に変更します。 (保存された変更リストを保持するか、削除して後続の変更リストに置き換えることができます。)
リンクリストの各ノードには、次の情報が含まれています。
delete
の後にinsert
が続くことを意味しますinsert
の場合、挿入されたデータです。 delete
の場合、削除されたデータ。Undo
を実装するには、 'current-node'ポインターまたはインデックスを使用して、リンクリストの末尾から逆方向に作業します。変更がinsert
であった場合は、リンクを更新せずに削除します-リスト;そして、それがdelete
だった場所に、リンクリストバッファのデータからデータを挿入します。ユーザーからの「元に戻す」コマンドごとにこれを行います。 Redo
は、「現在のノード」ポインターを前方に移動し、ノードごとに変更を実行します。ユーザーが元に戻した後にコードに変更を加えた場合、「current-node」インジケーターの後のすべてのノードをテールに削除し、テールを「current-node」インジケーターに等しく設定します。次に、ユーザーの新しい変更がテールの後に挿入されます。そしてそれはそれについてです。
私のたった2セントは、2つのスタックを使用して操作を追跡したいということです。ユーザーが何らかの操作を実行するたびに、プログラムはそれらの操作を「実行済み」スタックに配置する必要があります。ユーザーがこれらの操作を元に戻したい場合は、単に「実行済み」スタックから「リコール」スタックに操作をポップします。ユーザーがこれらの操作をやり直したい場合は、「リコール」スタックからアイテムをポップし、「実行済み」スタックにプッシュバックします。
それが役に立てば幸い。
アクションが可逆的である場合。たとえば、1を追加すると、プレーヤーを動かすなど、 コマンドパターンを使用して元に戻す/やり直しを実装する方法 を参照してください。リンクをたどると、その方法の詳細な例が見つかります。
そうでない場合は、@ Lazerの説明に従って、Saved Stateを使用します。
既存の元に戻す/やり直しフレームワークの例を学習できます。最初のGoogleヒットは codeplex(for .NET) です。それが他のどのフレームワークよりも良いか悪いかはわかりませんが、たくさんあります。
アプリケーションで元に戻す/やり直し機能を使用することが目的の場合は、アプリケーションの種類に適した既存のフレームワークを選択することもできます。
独自のアンドゥ/リドゥの作成方法を学びたい場合は、ソースコードをダウンロードして、パターンと接続方法の詳細の両方を確認してください。
このために Mementoパターン が作成されました。
これを自分で実装する前に、これは非常に一般的であり、コードが既に存在することに注意してください-たとえば、.Netでコーディングしている場合は、 IEditableObject を使用できます。