web-dev-qa-db-ja.com

元に戻す/やり直しの実装

元に戻す/やり直し機能を実装する方法について考えてみてください-テキストエディターのように。どのアルゴリズムを使用する必要があり、何を読むことができるか。ありがとう。

61
user414352

アンドゥの種類の2つの主要な区分について知っています

  • SAVE STATE:取り消しの1つのカテゴリは、実際に履歴状態を保存する場所です。この場合、発生するのは、メモリのある場所に状態を保存し続けるすべてのポイントです。元に戻すには、現在の状態をスワップアウトし、メモリ内にすでに存在する状態にスワップします。これは、たとえばAdobe Photoshopの履歴や、Google Chromeで閉じたタブを再度開く方法です。

alt text

  • GENERATE STATE:他のカテゴリーは、状態自体を維持する代わりに、アクションが何であったかを覚えているだけです。元に戻す必要がある場合は、その特定のアクションを論理的に逆にする必要があります。簡単な例では、 Ctrl+B 元に戻す操作をサポートするテキストエディタでは、太字アクションとして記憶されます。現在、各アクションには、その論理的な反転のマッピングがあります。だから、あなたがするとき Ctrl+Z、逆アクションテーブルから検索し、元に戻すアクションが Ctrl+B 再び。それが実行され、以前の状態が得られます。したがって、ここでは、以前の状態はメモリに保存されず、必要なときに生成されました。

テキストエディターの場合、この方法で状態を生成することは計算集約的ではありませんが、Adobe Photoshopのようなプログラムでは、計算集約的すぎるか、まったく不可能です。たとえば、-Blurアクションの場合、de-Blurアクションを指定しますが、データがすでに失われているため、元の状態に戻すことはできません。そのため、状況に応じて-論理的な逆アクションの可能性とその実現可能性に応じて、これら2つの広範なカテゴリから選択し、必要な方法で実装する必要があります。もちろん、あなたのために働くハイブリッド戦略を持つことは可能です。

また、Gmailのように、アクション(メールの送信)がそもそも行われないため、時間制限付きの取り消しが可能な場合があります。したがって、あなたはそこで「元に戻す」のではなく、単にアクション自体を「していない」のです。

72
Lazer

私は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つ減らします。デフォルトでは、UndoLevelUndoData配列の最後のメンバーのインデックスに等しくなります。再実行(Ctrl + YまたはShift + Ctrl + Z)で、エディターをUndoData[UndoLevel + 1]状態に復元し、UndoLevelを1つ増やします。もちろん、UndoLevelUndoData配列の長さ(マイナス1)に等しくないときに編集タイマーがトリガーされる場合、UndoLevelの後のこの配列のすべての項目をクリアします。 、Microsoft Windowsプラットフォームでよくあることですが(ただし、正しく思い出すとEmacsの方が良いです-Microsoft Windowsのアプローチの欠点は、多くの変更を取り消してから誤ってバッファーを編集すると、以前のコンテンツ( undid)は永久に失われます)。この配列の縮小をスキップすることもできます。

別のタイプのプログラム、たとえば画像エディターでは、同じ手法を適用できますが、もちろん、UndoDataItem構造がまったく異なります。メモリをそれほど必要としないより高度なアプローチは、元に戻すレベルの間でchangesのみを保存することです(つまり、「alpha\nbeta\gamma」と「alpha\nbeta\ngamma\ndelta」の場合、「alpha\nbeta\ngamma」と「ADD\ndelta」を保存できます(意味がわかる場合)。各変更がファイルサイズに比べて非常に小さい非常に大きなファイルでは、これにより元に戻すデータのメモリ使用量が大幅に減少しますが、実装が難しくなり、エラーが発生しやすくなります。

15

これを行うにはいくつかの方法がありますが、 コマンドパターン を見始めることができます。コマンドのリストを使用して、アクションを前後に移動(元に戻す)または前進(やり直し)します。 C#の例は here にあります。

12
Maurits Rijk

少し遅れますが、ここに行きます:あなたは特にテキストエディターを参照します。以下は、編集しているものに適応できるアルゴリズムを説明しています。関係する原則は、あなたが行った各変更を再作成するために自動化できるアクション/命令のリストを保持することです。元のファイルに変更を加えないで(空でない場合)、バックアップとして保管してください。

元のファイルに加えた変更の前後のリンクリストを保持します。このリストは、ユーザーが実際に変更するまで一時ファイルに断続的に保存されます保存変更:この場合、変更を新しいファイルに適用し、古いファイルをコピーし、同時に変更を適用します。元のファイルの名前をバックアップに変更し、新しいファイルの名前を正しい名前に変更します。 (保存された変更リストを保持するか、削除して後続の変更リストに置き換えることができます。)

リンクリストの各ノードには、次の情報が含まれています。

  • 変更のタイプ:データを挿入するか、データを削除します:データを「変更」するには、deleteの後にinsertが続くことを意味します
  • ファイル内の位置:オフセットまたは行/列ペアにすることができます
  • データバッファ:これは、アクションに関連するデータです。 insertの場合、挿入されたデータです。 deleteの場合、削除されたデータ。

Undoを実装するには、 'current-node'ポインターまたはインデックスを使用して、リンクリストの末尾から逆方向に作業します。変更がinsertであった場合は、リンクを更新せずに削除します-リスト;そして、それがdeleteだった場所に、リンクリストバッファのデータからデータを挿入します。ユーザーからの「元に戻す」コマンドごとにこれを行います。 Redoは、「現在のノード」ポインターを前方に移動し、ノードごとに変更を実行します。ユーザーが元に戻した後にコードに変更を加えた場合、「current-node」インジケーターの後のすべてのノードをテールに削除し、テールを「current-node」インジケーターに等しく設定します。次に、ユーザーの新しい変更がテールの後に挿入されます。そしてそれはそれについてです。

6
slashmais

私のたった2セントは、2つのスタックを使用して操作を追跡したいということです。ユーザーが何らかの操作を実行するたびに、プログラムはそれらの操作を「実行済み」スタックに配置する必要があります。ユーザーがこれらの操作を元に戻したい場合は、単に「実行済み」スタックから「リコール」スタックに操作をポップします。ユーザーがこれらの操作をやり直したい場合は、「リコール」スタックからアイテムをポップし、「実行済み」スタックにプッシュバックします。

それが役に立てば幸い。

5
user3555216

アクションが可逆的である場合。たとえば、1を追加すると、プレーヤーを動かすなど、 コマンドパターンを使用して元に戻す/やり直しを実装する方法 を参照してください。リンクをたどると、その方法の詳細な例が見つかります。

そうでない場合は、@ Lazerの説明に従って、Saved Stateを使用します。

2

既存の元に戻す/やり直しフレームワークの例を学習できます。最初のGoogleヒットは codeplex(for .NET) です。それが他のどのフレームワークよりも良いか悪いかはわかりませんが、たくさんあります。

アプリケーションで元に戻す/やり直し機能を使用することが目的の場合は、アプリケーションの種類に適した既存のフレームワークを選択することもできます。
独自のアンドゥ/リドゥの作成方法を学びたい場合は、ソースコードをダウンロードして、パターンと接続方法の詳細の両方を確認してください。

2
Albin Sunnanbo

このために Mementoパターン が作成されました。

これを自分で実装する前に、これは非常に一般的であり、コードが既に存在することに注意してください-たとえば、.Netでコーディングしている場合は、 IEditableObject を使用できます。

1
Vivek Maharajh