none
TextBoxの高速表示 RRS feed

  • 質問

  • 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>などでバインディングし、循環バッファとして扱うようなことができないでしょうか。

    ご存じの方がいらっしゃいましたらご教授ください。

    2019年8月31日 16:16

回答

  • ListBoxtとかでも、LockWindowUpdate APIで内容編集中の再描画を抑止できた記憶があります。操作の前にLockしておけばよかったかと。
    まあ、自分で描画する、ユーザコントロールなログウインドウを建てた方が良くない?という気もします。


    jzkey

    2019年9月1日 11:35

すべての返信

  • TextBox.Textプロパティは更新しますと、Windowsは画面更新しようと努力します。しかし実際には60fps、つまり16ms単位でしか画面更新されません。環境によってはそれ以上の更新速度が得られるかもしれませんが、どのみち人間の目では追えないはずです。

    という背景があるため、表示したい文字列は一旦List<string>に保存し、定期的なタイマーでList<string>からTextBox.AppendTextへ書き込んではどうでしょうか。なお、TextBox.Textプロパティへの代入はTextBox全体を再描画してしまうためTextBox.AppendTextの方が速いです。
    # テキストを部分的に削除する機能はありません。あるいはEM_REPLACESELを直接呼び出す。

    • 回答の候補に設定 huahi11112 2019年9月2日 7:18
    2019年8月31日 22:42
  • ご回答ありがとうございます。

    質問内容がわかりにくく恐縮ですが、ご指摘頂いたTimerを利用してList<string>のバッファをAppendTextで書き込むことはしております。

    ですが、削除時に時間がかかってしまう問題が残っております。部分的な削除が無いためテキスト全体を再設定すると遅く、SendMessageで選択範囲を部分削除→スクロール位置を戻すなどをするとちらつき等がおきてしまいます。

    そのため、削除ではなく予め用意した配列を循環して使用する(DataGridViewの仮想化のような)ことができないかと考えております。

    2019年9月1日 3:41
  • ListBoxtとかでも、LockWindowUpdate APIで内容編集中の再描画を抑止できた記憶があります。操作の前にLockしておけばよかったかと。
    まあ、自分で描画する、ユーザコントロールなログウインドウを建てた方が良くない?という気もします。


    jzkey

    2019年9月1日 11:35
  • ご回答ありがとうございます。

    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} ##");
    

    2019年9月1日 13:10
  • 部分的な削除が無いためテキスト全体を再設定すると遅く、

    Lines プロパティや Text プロパティで再設定するのではなく、部分編集のために SelectedText プロパティを書き換えては如何でしょうか? これは AppendText 同様、内部的には、EM_REPLACESEL メッセージに相当する処理となりますので、直接 EM_REPLACESEL メッセージを投げることでも同じことができます。

    この場合、EM_REPLACESEL にしろ SelectedText にせよ、事前にどの部分を置き換えるのかを「選択」しておく必要があるため、事前に Select(start, length) メソッドもしくは EM_SETSEL メッセージを呼び出す必要があります。

    2019年9月2日 0:06
  • SelectedTextプロパティで実現できるんですね。参考になりました。私の投稿は誤っているので削除しておきます。
    2019年9月2日 2:02