前回のプログラムを一括で大量の置換した場合、Undoするときも一括でできるようにしてみます。
SetSelectedTextを何回も使うことでひとつずつUndoすることができるわけですが、こんな場合はどうすればいいのでしょうか?
置換は一瞬、Undoはひとつずつ・・・。一括置換のUndoとRedoも一括でできるようにしたいものです。
そのためにはどうすればいいでしょうか? どこまでを一括で処理するべきかをはっきりさせる必要があります。
そこでUndobufのフィールド変数に以下を追加します。
1 2 3 4 |
public class Undobuf { internal long replaceTime = -1; } |
一括置換がおこなわれた時間を記録します。-1以外の値で同じ値が連続する部分が一括置換がおこなわれた部分です。
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 |
public partial class SyncRichTextBox : UserControl { public void Undo() { var buf = Data.GetUndobuf(); if (buf == null) return; buf.IsUndo = true; else if (buf.replaceTime > 0) { DoUndoReplaces(buf); return; } } public void Redo() { var buf = Data.GetRedobuf(); if (buf == null) return; if (buf.replaceTime > 0) { DoRedoReplaces(buf); return; } } } |
さて、問題はDoUndoReplacesをどう書くかです。
まず buf.replaceTime が同じものを探します。2つ以上みつかった場合は一括置換が行なわれたことを意味しています。
1 |
count = Data.GetUndobufs().TakeWhile(x => x.replaceTime == buf.replaceTime).Count(); |
次にイベントを発生させて、一括でUndoする、ひとつずつUndoする、処理をキャンセルするを選ぶことができるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var args = new UndoReplaceStringsArgs(count, buf.RemoveText, buf.InsertText); UndoReplaceStrings?.Invoke(this, args); public class UndoReplaceStringsArgs { public UndoReplaceStringsArgs(int count, string removeText, string insertText) { Count = count; RemoveText = removeText; InsertText = insertText; } public int Count { get; } public string RemoveText { get; } public string InsertText { get; } public act Action { get; set; } public enum act { Once = 0, All = 1, Cancel = 2, } } |
一括でUndoする場合は別のSyncRichTextBoxを生成してそこで処理をおこないます(カーソルがチカチカするのを防ぐため)。終わったらデータをコピーし、Undobufも移動させます。
またカーソルの位置も適切な場所になるように調整します。
UndoもRedoも似たような処理なので、まとめて書いています。
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 |
public partial class SyncRichTextBox : UserControl { public void DoUndoReplaces(Undobuf buf) { DoUndoReplaces0(buf, true); } public void DoRedoReplaces(Undobuf buf) { DoUndoReplaces0(buf, false); } public void DoUndoReplaces0(Undobuf buf, bool isUndo) { int count = 0; if(isUndo) count = Data.GetUndobufs().TakeWhile(x => x.replaceTime == buf.replaceTime).Count(); else count = Data.GetRedobufs().TakeWhile(x => x.replaceTime == buf.replaceTime).Count(); if (count > 1) { foreach (var rich in Data.SyncRichTextBoxes) Data.GetRichTextBoxInfo(rich).SelectionStart = rich.SelectionStart; var args = new UndoReplaceStringsArgs(count, buf.RemoveText, buf.InsertText); if (isUndo) UndoReplaceStrings?.Invoke(this, args); else RedoReplaceStrings?.Invoke(this, args); if (args.Action == UndoReplaceStringsArgs.act.Cancel) return; if (args.Action == UndoReplaceStringsArgs.act.All) { buf.IsUndo = false; SyncRichTextBox sync = new SyncRichTextBox(); sync.richTextBoxEx1.Rtf = this.richTextBoxEx1.Rtf; List<Undobuf> list = null; if(isUndo) list = Data.GetUndobufs().TakeWhile(x => x.replaceTime == buf.replaceTime).ToList(); else list = Data.GetRedobufs().TakeWhile(x => x.replaceTime == buf.replaceTime).ToList(); foreach (var buf0 in list) { if (isUndo) { sync.richTextBoxEx1.Select(buf0.insertStart, buf0.insertLength); sync.richTextBoxEx1.SelectedRtf = buf0.removeRtf; } else { sync.richTextBoxEx1.Select(buf0.removeStart, buf0.removeLength); sync.richTextBoxEx1.SelectedRtf = buf0.insertRtf; } } foreach (var rich in Data.SyncRichTextBoxes) { var info = Data.GetRichTextBoxInfo(rich); int start = info.SelectionStart; int sa = 0; sa = (buf.removeLength - buf.InsertLength) * (list.Where(x => x.insertStart < start).Count() - 1); if (!isUndo) sa *= -1; if (start + sa > 0) info.SelectionStart = start + sa; else info.SelectionStart = 0; if (rich.Data == Data) { rich.richTextBoxEx1.Rtf = sync.richTextBoxEx1.Rtf; rich.richTextBoxEx1.SelectionStart = info.SelectionStart; } } for (int i = 0; i < count; i++) { if (isUndo) Data.MoveToRedobufs(); else Data.MoveToUndobufs(); } return; } } if (isUndo) { buf.IsUndo = true; DoUndo(buf); Data.MoveToRedobufs(); buf.IsUndo = false; } else { buf.IsUndo = false; DoUndo(buf); Data.MoveToUndobufs(); } } } |
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 |
public partial class SyncRichTextBox : UserControl { public SyncRichTextBox() { this.RedoReplaceStrings += SyncRichTextBox_RedoReplaceStrings; this.UndoReplaceStrings += SyncRichTextBox_UndoReplaceStrings; } private void SyncRichTextBox_ReplaceStringsEvent(SyncRichTextBox sender, ReplaceStringsArgs e) { var dr = MessageBox.Show("一括置換をしますか?(全 "+ e.Count.ToString() + " 件)\n[Yes] 一括置換\n[No] ひとつずつ置換\n[Cancel] なにもしない", "一括置換の確認", MessageBoxButtons.YesNoCancel); if (dr == DialogResult.Yes) e.Action = ReplaceStringsArgs.act.All; if (dr == DialogResult.No) e.Action = ReplaceStringsArgs.act.Once; if (dr == DialogResult.Cancel) e.Action = ReplaceStringsArgs.act.Cancel; } private void SyncRichTextBox_UndoReplaceStrings(SyncRichTextBox sender, ReplaceStringsArgs e) { var dr = MessageBox.Show("一括置換をまとめてUndoしますか?\n[Yes] まとめてUndo\n[No] ひとつずつUndo\n[Cancel] なにもしない", "一括置換のUndo", MessageBoxButtons.YesNoCancel); if (dr == DialogResult.Yes) e.Action = ReplaceStringsArgs.act.All; if (dr == DialogResult.No) e.Action = ReplaceStringsArgs.act.Once; if (dr == DialogResult.Cancel) e.Action = ReplaceStringsArgs.act.Cancel; } } |