トップ回答者
TextBoxの高速表示

質問
-
Windows Forms アプリケーションでVisual Stduioの出力ウィンドウや、teratermのようなリアルタイムのログを出力するものを作成したいと考えています。
TextBoxのMaxLengthを0にしてAppendTextするだけであれば1ms未満で1行ずつ出力されるのですが、メモリを大量に使用してしまうため一定量で削除する必要があり、行単位で削除が行えないのでLinesからSkip/Takeで切り取り、再度Textに設定などを行っていました。しかしながら、Textへの再設定で数百msかかってしまいます。
次にListBoxを使用して1行ずつの追加/削除を試してみましたが、そもそも行追加がTextBoxとは比較にならないくらい遅い(3msほど)ため、あまりよい方法ではないと思いました。
TextBoxのTextをList<string>などでバインディングし、循環バッファとして扱うようなことができないでしょうか。
ご存じの方がいらっしゃいましたらご教授ください。
回答
-
ListBoxtとかでも、LockWindowUpdate APIで内容編集中の再描画を抑止できた記憶があります。操作の前にLockしておけばよかったかと。
まあ、自分で描画する、ユーザコントロールなログウインドウを建てた方が良くない?という気もします。
jzkey
- 回答としてマーク しん11111111 2019年9月1日 16:20
すべての返信
-
TextBox.Textプロパティは更新しますと、Windowsは画面更新しようと努力します。しかし実際には60fps、つまり16ms単位でしか画面更新されません。環境によってはそれ以上の更新速度が得られるかもしれませんが、どのみち人間の目では追えないはずです。
という背景があるため、表示したい文字列は一旦List<string>に保存し、定期的なタイマーでList<string>からTextBox.AppendTextへ書き込んではどうでしょうか。なお、TextBox.Textプロパティへの代入はTextBox全体を再描画してしまうためTextBox.AppendTextの方が速いです。
# テキストを部分的に削除する機能はありません。あるいはEM_REPLACESELを直接呼び出す。- 回答の候補に設定 huahi11112 2019年9月2日 7:18
-
-
ListBoxtとかでも、LockWindowUpdate APIで内容編集中の再描画を抑止できた記憶があります。操作の前にLockしておけばよかったかと。
まあ、自分で描画する、ユーザコントロールなログウインドウを建てた方が良くない?という気もします。
jzkey
- 回答としてマーク しん11111111 2019年9月1日 16:20
-
ご回答ありがとうございます。
ControlにGraphicsでDrawStringをする方法も試しましたが、Label同様に選択することができないため断念しました。
ご助言頂いたListBoxの再描画抑止を調べてBeginUpdate/EndUpdateがわかりました。以下のように試したみたところ200ms前後でしたので十分利用できる速度と思いますのでこちらで作成してみようと思います。
ありがとうございました。
stopwatch.Restart(); listBox1.BeginUpdate(); for (int i = 0; i < 1000; i++) { int index = listBox1.Items.Add($"{DateTime.Now.ToString()}"); listBox1.TopIndex = index; if (listBox1.Items.Count > 500) { listBox1.Items.RemoveAt(0); } } listBox1.EndUpdate(); Debug.WriteLine($"## {stopwatch.Elapsed} ##");
-
部分的な削除が無いためテキスト全体を再設定すると遅く、
Lines プロパティや Text プロパティで再設定するのではなく、部分編集のために SelectedText プロパティを書き換えては如何でしょうか? これは AppendText 同様、内部的には、EM_REPLACESEL メッセージに相当する処理となりますので、直接 EM_REPLACESEL メッセージを投げることでも同じことができます。
この場合、EM_REPLACESEL にしろ SelectedText にせよ、事前にどの部分を置き換えるのかを「選択」しておく必要があるため、事前に Select(start, length) メソッドもしくは EM_SETSEL メッセージを呼び出す必要があります。