C#でUndo機能が気に入らないので力技で自作した(RichTextBox)
C#でどうしても気に入らないのがUndo機能。特にRichTextBoxで使うと、Undo履歴が粗いのとテキストハイライトもUndoしてしまうので厄介です。プログラミング用のテキストエディタなんか作る場合は色までUndoしてほしくないですよね。
自分の好きなところでUndo履歴を残してほしいし、記憶数も自由に設定したいしで、どうにか自作できないものか考えていました。
「c# undo 実装」でググると出てこなくもないですがピンとくるものがないです。あとは結構難しそう。
なので自分で作ろうと思った次第です。100%自作。要素要素でネット情報を参考にしたところはありますが(WindowsAPIとかね)、基本的な考え方は自分で考えたものです。
各要素ごと解説した後、最後にソースコート全文を掲載します。
[ad#top-1]
保持するUndo情報はテキスト、キャレット位置、スクロール位置
RichTextBoxやTextBoxでUndo制御を実装する場合、履歴として残しておくデータはテキストだけではありません。
復元しなければならないのは、テキストの他に、キャレット位置、スクロール位置も復元しなければならないのです。
なので、以下のように3つのデータを保持できるようにデータクラスを作成しました。
public class UndoData{ public UndoData(string text, Point scroll, int caret){ this.Text = text; this.Scroll = scroll; this.Caret = caret; } public string Text{ get; set; } public Point Scroll{ get; set; } public int Caret{ get; set; } }
履歴はLinkedListで残した
先ほど作ったUndoデータを保持するクラスを以下のようにLinkedListでまとめました。UndoとRedoそれぞれにコレクションを用意しています。Stack<T>の方が使いやすそうですが後述しますが訳あって使えません。
Undo履歴に溜める際、Undo履歴(LinkedList<UndoData>)にデータを追加します。この時、設定した履歴数MAXを超えたら、古いデータを除外するようにします。無限に溜めるのはメモリの無駄なので。(この機能を必要とするためStack<T>は使えない。Stackは後ろのデータにアクセスできないので。)
下図がUndoを実行した際の模式図です。新しいデータから値を出して(且つ履歴から削除して)テキストボックスを復元します。この時、Undo前のTextBoxのデータをRedo履歴の方に追加していきます。
今度はRedoの実行例。Undo実行と逆のルートとなります。Redoデータからテキストボックスを復元しつつ、Undo前のTextBoxのデータをUndo履歴に追加していくわけです。
以上が大まかな仕組みです。
活用した技術
スクロール位置の復元
スクロール位置をデータとして残しておかないと、Undo/Redoの時に勝手にスクロールが動いてしまいます。これはカーソル位置に最適なスクロール位置に調整されてしまうからですね。
勝手にスクロールが変わるとカーソル位置を一瞬見失うので防止する必要があります。以下の方法を使ってカーソル位置を取得しUndoデータに保持させます。
テキストボックスをロックした際の色変更防止
Undo/Redo実行(「Ctrl」+「Z」、「Ctrl」+「Z」)の際に画面チラつきを抑えるために、TextBoxを一旦ロック(Enable=false)にする必要があります。
が、TextBoxはロックするとグレー色になるため結局チラついてしまします。グレー色へに変化しないよう以下の対処が必要になります。
勝手にフォントが変わってしまう対処
今回と直接関係ありませんが、RichTextBoxの場合、設定したはずのフォントが勝手に変わってしまう場合があるので、それを防止するために以下の対処をしました。
動画(実行例)
出来たプログラムの実行例を動画にしてみました。文字を打った後に、Undo実行して、その後Redo実行しています。画面チラつきもなく、うまくできているかと思います。(音声ありません)
ソースコード全文
こちらがソースコード全文です。
テキストボックスにはRichTextBoxを使用しています。
これをコピペしてコンパイルすれば以上で説明した動作が確認できるかと思います。
[ad#ad-1]
スポンサーリンク