前回の続きです。
複数のRichTextBoxを同期する OnKeyPress編です。KeyPress イベントは、実行中のフォームで、フォームまたはそのコントロールにフォーカスがあるときに、ユーザーが入力可能な文字を生成するキーが押されることで発生します。
ただちょっと注意することがあります。Ctrl+Zなどのキーを押したときもこのイベントは発生するようです。
ではどうするか? 処理が必要なのは、数字、アルファベット、記号などのキーが押されたときです。そこでどうするか? 文字コードをみてみましょう。
ORANGE-FACTORY (オレンジ工房) UTF8 1byte
これによると0x21から0x7eまでが必要であることがわかります。それ以外は無視してOKです。
選択されている文字(これから削除されようとしている文字)とこれから追加されようとしている文字をUndobufのなかに保存し、これを利用して他のコントロールのデータも同期させます。
ところでRichTextBoxにはテキストが変化したらTextChangedというイベントが発生します。だったらテキストが変化しようとしているときもイベントがあっていいのではないでしょうか。そこでTextChangingというイベントを作成しました。Undobufの内容を参照してデータを変更したくないときは処理をキャンセルすることもできるようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
public partial class SyncRichTextBox : UserControl { public delegate void TextChangingHandler(SyncRichTextBox sender, Undobuf e); public event TextChangingHandler TextChanging; protected void OnTextChanging(Undobuf e) { TextChanging?.Invoke(this, e); } protected override void OnKeyPress(KeyPressEventArgs e) { e.Handled = true; int i = (int)e.KeyChar; if (i < 0x20 || i > 0x80) return; SetSelectedText(e.KeyChar.ToString(), "KeyPress"); } public string SelectedText { get { return this.richTextBoxEx1.SelectedText; } } public void SetSelectedText(string str, string action) { foreach (var sync in Data.SyncRichTextBoxes) { var info = Data.GetRichTextBoxInfo(sync); if (info != null) { info.SelectionStart = sync.richTextBoxEx1.SelectionStart; } } Undobuf buf = new Undobuf(); buf.removeStart = richTextBoxEx1.SelectionStart; buf.removeLength = richTextBoxEx1.SelectionLength; buf.removeText = richTextBoxEx1.SelectedText; buf.removeRtf = richTextBoxEx1.SelectedRtf; buf.oldSelectionStart = richTextBoxEx1.SelectionStart; buf.oldSelectionLength = richTextBoxEx1.SelectionLength; buf.newSelectionStart = richTextBoxEx1.SelectionStart + str.Length; buf.newSelectionLength = 0; buf.insertStart = richTextBoxEx1.SelectionStart; buf.insertLength = str.Length; buf.insertText = str; buf.action = action; richTextBoxEx1.Select(buf.oldSelectionStart, 0); richTextBoxEx1.SelectedText = str; richTextBoxEx1.Select(buf.oldSelectionStart, str.Length); buf.insertRtf = richTextBoxEx1.SelectedRtf; richTextBoxEx1.SelectedRtf = ""; richTextBoxEx1.Select(buf.oldSelectionStart, buf.oldSelectionLength); OnTextChanging(buf); if (buf.IsCancel) return; foreach (var sync in Data.SyncRichTextBoxes) { var info = Data.GetRichTextBoxInfo(sync); int oldStart = info.SelectionStart; sync.richTextBoxEx1.Select(buf.removeStart, buf.removeLength); if (sync.Data == this.Data && sync != this) { sync.richTextBoxEx1.SelectedRtf = buf.insertRtf; int i = GetPositionAfterRemove(oldStart, buf.removeStart, buf.removeLength); i = GetPositionAfterInsert(i, buf.insertStart, buf.insertLength); sync.richTextBoxEx1.Select(i, 0); } else if (sync.Data == this.Data && sync == this) { sync.richTextBoxEx1.SelectedRtf = buf.insertRtf; sync.richTextBoxEx1.Select(buf.NewSelectionStart, buf.NewSelectionLength); } else if (sync.Data != this.Data) { int start = info.SelectionStart; int i = GetPositionAfterRemove(start, buf.removeStart, buf.removeLength); i = GetPositionAfterInsert(i, buf.insertStart, buf.insertLength); info.SelectionStart = i; } } Data.InsertUndobuf(buf); } int GetPositionAfterRemove(int curPos, int removeStart, int removeLength) { if (curPos <= removeStart) return curPos; else if (removeStart + removeLength < curPos) return curPos - removeLength; else return removeStart; } int GetPositionAfterInsert(int curPos, int insertStart, int insertLength) { if (curPos == 0) return 0; if (curPos < insertStart) return curPos; else return curPos + insertLength; } } |
文字が削除されたり追加されたときにカーソルが以前に選択されていたものと対応するものに移動できるようにしました。
あとUndo処理を考える必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
public partial class SyncRichTextBox : UserControl { protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Z && e.Control) { e.Handled = true; Undo(); return; } if (e.KeyCode == Keys.Y && e.Control) { e.Handled = true; Redo(); return; } base.OnKeyDown(e); } } public partial class Undobuf { // 以下をUndobufクラスに追加 public bool IsCancel{ get; set; } = false; public Undobuf() { IsUndo = false; } internal bool IsUndo { get; set; } public int RemoveStart { get { if (IsUndo) return insertStart; return removeStart; } } public int RemoveLength { get { if (IsUndo) return insertLength; return removeLength; } } public int InsertStart { get { if (IsUndo) return removeStart; return insertStart; } } public int InsertLength { get { if (IsUndo) return removeLength; return insertLength; } } public string InsertText { get { if (IsUndo) return removeText; return insertText; } } public string InsertRtf { get { if (IsUndo) return removeRtf; return insertRtf; } } public string RemoveText { get { if (IsUndo) return insertText; return removeText; } } public string RemoveRtf { get { if (IsUndo) return insertRtf; return removeRtf; } } public int OldSelectionStart { get { if (IsUndo) return newSelectionStart; return oldSelectionStart; } } public int OldSelectionLength { get { if (IsUndo) return newSelectionLength; return oldSelectionLength; } } public int NewSelectionStart { get { if (IsUndo) return oldSelectionStart; return newSelectionStart; } } public int NewSelectionLength { get { if (IsUndo) return oldSelectionLength; return newSelectionLength; } } public int OldSelectionStart { get { if (IsUndo) return newSelectionStart; return oldSelectionStart; } } public int OldSelectionLength { get { if (IsUndo) return newSelectionLength; return oldSelectionLength; } } public int NewSelectionStart { get { if (IsUndo) return oldSelectionStart; return newSelectionStart; } } public int NewSelectionLength { get { if (IsUndo) return oldSelectionLength; return newSelectionLength; } } } |
Undoは逆の処理をすればいいので、逆の結果を返しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
public partial class SyncRichTextBox : UserControl { public void Undo() { var buf = Data.GetUndobuf(); buf.IsUndo = true; OnTextChanging(buf); if (buf.IsCancel) return; DoUndo(buf); buf.IsUndo = false; Data.MoveToRedobufs(); } public void Redo() { var buf = Data.GetRedobuf(); OnTextChanging(buf); if (buf.IsCancel) return; DoUndo(buf); Data.MoveToUndobufs(); } void DoUndo(Undobuf buf) { if (buf != null) { foreach (var sync in Data.SyncRichTextBoxes) { int oldStart = sync.richTextBoxEx1.SelectionStart; int oldLen = sync.richTextBoxEx1.SelectionLength; sync.richTextBoxEx1.Select(buf.RemoveStart, buf.RemoveLength); if (sync.Data == this.Data && sync != this) { sync.richTextBoxEx1.SelectedRtf = buf.InsertRtf; int i = GetPositionAfterRemove(oldStart, buf.RemoveStart, buf.RemoveLength); i = GetPositionAfterInsert(i, buf.InsertStart, buf.InsertLength); sync.richTextBoxEx1.Select(i, 0); } else if (sync.Data == this.Data && sync == this) { sync.richTextBoxEx1.SelectedRtf = buf.InsertRtf; sync.richTextBoxEx1.SelectionStart = buf.NewSelectionStart; sync.richTextBoxEx1.SelectionLength = buf.NewSelectionLength; } else if (sync.Data != this.Data) { var info = Data.GetRichTextBoxInfo(sync); if (info != null) { int start = info.SelectionStart; int i = GetPositionAfterRemove(start, buf.RemoveStart, buf.RemoveLength); i = GetPositionAfterInsert(i, buf.InsertStart, buf.InsertLength); info.SelectionStart = i; } } } } } } |
これでKeyPressイベントに関しては、UndoとRedo処理ができるようになります。