これまで複数のRichTextBoxでデータを同期するプログラムを作成してきました。今回は検索と置換機能を持たせることにします。
まず検索機能について。
検索するのであれば RichTextBox.Findメソッドが使えます。
1 2 3 4 5 |
RichTextBox.Find( string str, // なにで検索するか? int start, // どこから検索するか? RichTextBoxFinds.MatchCase } |
あと後ろから前に検索することもできます。
1 2 3 4 5 6 |
RichTextBox.Find( string str, // なにで検索するか? int start, // どこから検索するか? int end, // どこまで検索するか? RichTextBoxFinds.MatchCase| RichTextBoxFinds.Reverse } |
endからstartに向かって検索し、一番endに近い文字の位置を返します。
さて置換ですが、1回だけ置換するのであればこんなのはどうでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public SyncRichTextBox() { public void ReplaseOneString(string oldString, string newString) { if(oldString == "" || oldString == newString) return; string text = richTextBoxEx1.Text; int index = text.IndexOf(oldString, richTextBoxEx1.SelectionStart); if (index == -1) return; richTextBoxEx1.Select(index, oldString.Length); SetSelectedText(newString, "ReplaseOneString"); } } |
現在選択されている位置から一番近くにある文字列を置換します。問題は一括で置換する場合です。
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 |
public SyncRichTextBox() { public void ReplaseStrings(string oldString, string newString) { if(oldString == "" || oldString == newString) return; int start = richTextBoxEx1.SelectionStart; string text = richTextBoxEx1.Text; int i = 0; while (true) { int index1 = richTextBoxEx1.Find(oldString, start, RichTextBoxFinds.MatchCase); if (index1 == -1 || index1 < start) break; richTextBoxEx1.Select(index1, oldString.Length); SetSelectedText(newString, "ReplaseString"); start = index1 + newString.Length; richTextBoxEx1.Select(start, 1); i++; } } } |
一応これで動きますが、対象文字列が多いとフリーズしてしまったようになります。そこでプログレスバーを表示することにしました。
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 |
public SyncRichTextBox() { public void ReplaseStrings(string oldString, string newString) { if(oldString == "" || oldString == newString) return; System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(oldString); var rets = reg.Matches(richTextBoxEx1.Text); List<int> vs = new List<int>(); foreach (System.Text.RegularExpressions.Match m in rets) vs.Add(m.Index); int count = vs.Where(x => x >= richTextBoxEx1.SelectionStart).Count(); Form2 form2 = new Form2(); form2.progressBar1.Maximum = count; form2.Show(); int start = richTextBoxEx1.SelectionStart; string text = richTextBoxEx1.Text; int i = 0; while (true) { form2.progressBar1.Value = i; int index1 = richTextBoxEx1.Find(oldString, start, RichTextBoxFinds.MatchCase); if (index1 == -1 || index1 < start) break; richTextBoxEx1.Select(index1, oldString.Length); SetSelectedText(newString, "ReplaseString"); start = index1 + newString.Length; richTextBoxEx1.Select(start, 1); i++; } form2.Dispose(); } } |
ところがこれだと処理中にプログレスバーを表示しているダイアログを動かすことができません。
そこで
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 |
public SyncRichTextBox() { public void ReplaseStrings(string oldString, string newString) { if(oldString == "" || oldString == newString) return; System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(oldString); var rets = reg.Matches(richTextBoxEx1.Text); List<int> vs = new List<int>(); foreach (System.Text.RegularExpressions.Match m in rets) vs.Add(m.Index); int count = vs.Where(x => x >= richTextBoxEx1.SelectionStart).Count(); Form2 form2 = new Form2(); form2.FormClosing += Form2_FormClosing; // 処理中にダイアログを閉じるとクラッシュする // まちがって閉じないように閉じられないダイアログにする form2.progressBar1.Maximum = count; Task.Run(()=>{ form2.ShowDialog(); }); int start = richTextBoxEx1.SelectionStart; string text = richTextBoxEx1.Text; int i = 0; while (true) { form2.progressBar1.Value = i; int index1 = richTextBoxEx1.Find(oldString, start, RichTextBoxFinds.MatchCase); if (index1 == -1 || index1 < start) break; richTextBoxEx1.Select(index1, oldString.Length); SetSelectedText(newString, "ReplaseString"); start = index1 + newString.Length; richTextBoxEx1.Select(start, 1); i++; } if(!form2.IsDisposed) form2.Dispose(); void Form2_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = true; // 閉じようとしても閉じられない } } } |
一応、これで動いてくれます。ただ処理中、両方のRichTextBoxのカーソルが激しく動きます。また途中でキャンセルしたくなるかもしれません。そのようなものを作ってみましょう。
イベントを利用して、一括置換だけでなく逐次置換の選択もできるようにしています。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); syncRichTextBox1.Data = data; syncRichTextBox2.Data = data; syncRichTextBox2.ReplacingString += SyncRichTextBox2_ReplacingString; syncRichTextBox2.BeginReplaceStrings += syncRichTextBox1_BeginReplaceStrings; syncRichTextBox2.FinishedReplaceStrings += SyncRichTextBox2_FinishReplaceStrings; ; } Form2 _form2 = null; private void syncRichTextBox1_BeginReplaceStrings(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; if (e.Action == ReplaceStringsArgs.act.All) { _form2 = new Form2(); Task.Run(() => { _form2.progressBar1.Minimum = 0; _form2.progressBar1.Maximum = e.Count; _form2.ShowDialog(); }); } } private void SyncRichTextBox1_ReplacingString(SyncRichTextBox sender, ReplaceStringsArgs e) { if(_form2 != null && !_form2.IsDisposed) { _form2.progressBar1.Value = e.CurCount; } } private void SyncRichTextBox1_FinishReplaceStrings(SyncRichTextBox sender, ReplaceStringsArgs e) { if (_form2 != null && !_form2.IsDisposed) { _form2.Dispose(); _form2 = null; } MessageBox.Show(String.Format("{0}件の置換を行ないました", e.CurCount)); } } |
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 |
public SyncRichTextBox() { public bool ReplaseOneString(string oldString, string newString, int start) { if (oldString == "" || oldString == newString) return false; int start0 = richTextBoxEx1.SelectionStart; int index = richTextBoxEx1.Find(oldString, start, RichTextBoxFinds.MatchCase); if (index == -1) { richTextBoxEx1.SelectionStart = start0; return false; } richTextBoxEx1.Select(index, oldString.Length); return SetSelectedText(newString, "ReplaseOneString"); } public void ReplaceStrings(string oldString, string newString, int start) { if (oldString == "" || oldString == newString) return; System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(oldString); var rets = reg.Matches(richTextBoxEx1.Text); List<int> vs = new List<int>(); foreach (System.Text.RegularExpressions.Match m in rets) vs.Add(m.Index); int count = vs.Where(x => x >= start).ToList().Count(); if (count == 0) return; var args = new ReplaceStringsArgs(count, 0, oldString, newString); BeginReplaceStrings?.Invoke(this, args); if (args.Action == ReplaceStringsArgs.act.Cancel) return; if (args.Action == ReplaceStringsArgs.act.Once) { if (!ReplaceOneString(oldString, newString, start)) return; ReplaceStrings(oldString, newString, Data.GetUndobuf().RemoveStart + newString.Length); return; } SyncRichTextBox syncRich = new SyncRichTextBox(); syncRich.Data = new RichData(); syncRich.richTextBoxEx1.Rtf = richTextBoxEx1.Rtf; int i = 0; DateTime dt = DateTime.Now; long time = dt.Ticks; while (true) { int index1 = syncRich.richTextBoxEx1.Find(oldString, start, RichTextBoxFinds.MatchCase); if (index1 == -1 || index1 < start) break; var args1 = new ReplaceStringsArgs(count, i, oldString, newString); ReplacingString?.Invoke(this, args1); syncRich.richTextBoxEx1.Select(index1, oldString.Length); syncRich.SetSelectedText(newString, "ReplaseString"); start = index1 + newString.Length; i++; } string s = syncRich.richTextBoxEx1.Text; foreach (var rich in Data.SyncRichTextBoxes) { if (rich.Data == Data) { rich.richTextBoxEx1.Rtf = syncRich.richTextBoxEx1.Rtf; } } var undobufs = syncRich.Data.GetUndobufs(); undobufs.Reverse(); foreach (var buf in undobufs) { buf.replaceTime = time; this.Data.InsertUndobuf(buf); } syncRich.richTextBoxEx1.Dispose(); var args2 = new ReplaceStringsArgs(count, count, oldString, newString); FinishedReplaceStrings?.Invoke(this, args2); } } |
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 SyncRichTextBox() { public event ReplaceStringsHandler BeginReplaceStrings; public event ReplaceStringsHandler ReplacingString; public event ReplaceStringsHandler FinishedReplaceStrings; } public class ReplaceStringsArgs { public ReplaceStringsArgs(int count, int curCount, string removeText, string insertText) { Count = count; CurCount = curCount; RemoveText = removeText; InsertText = insertText; } public int Count { get; } public int CurCount { get; } public string RemoveText { get; } public string InsertText { get; } public act Action { get; set; } public enum act { Once = 0, All = 1, Cancel = 2, } } |
別のSyncRichTextBoxを作成して、そこにデータをコピーします。そしてそこで置換処理をおこなってできあがったデータをコピーします。またUndobufのリストもコピーします。
途中で[中止]ボタンを押すと処理が中止されます。