かみやんの技術者ブログ

主にプログラムの話です

C#、EditBox高速化(ソース付き)

自律走行の試験を少しだけしてみたところ、LRF(Top-URG)の受信に遅延が起きたり、マイコン(SH7125)からの受信に遅延が起きたりした。いろいろなところの処理が重いからのようだ。
という訳で、コンソールログ用に.net framework標準のEditBoxが重いだろうと思い、行ベース・リングバッファの高速版EditBox(以下自作TextAreaと呼ぶ)を作ってみた。


上図、高速版EditBox(見た目は大して変わらない)

標準のEditBoxが重いのは、行ベースでの管理になってないためだ。1000行溜まったら古いのから数行消すというようなインターフェースが用意されていない。行を追加するのも実際には、

editBox.Text += newLine;

みたいなことをしなければならず、これは実際には2つのstringを結合している訳で、改行計算が1行追加するごとに発生する。

自作TextAreaは、改行計算なしでリングバッファとし描画範囲も見える範囲のみ描画にした(行追加ごとに改行計算をしても大して重くならないはずだが面倒なので未実装)。本当はベンチマークを取って何倍高速化したと報告したいところだが時間がないのでなし。というかまだ自律時に通信の遅延がこれで直ったかも試験していない。早いところ確認したいところだ。

//テキストエリア(EditBoxでコンソールログを表示すると重いため作成)
//Copyright: ibis inc.
//Author  : Eiji Kamiya 2009/10/27
//Licence : BSD
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System;
using System.Diagnostics;

namespace AibiUI.Library.UI
{
    class TextArea : Control
    {
        private int maxLine;//最大行数
        private string[] lines;//行
        private int start;//リングバッファの開始インデックス
        private int count;//要素数
        private VScrollBar vScrollBar;//垂直スクロールバー

        //コンストラクタ
        public TextArea()
        {
            maxLine=200;
            vScrollBar = new VScrollBar();
            vScrollBar.ValueChanged += new EventHandler(textArea_ValueChanged);
            Controls.Add(vScrollBar);
            Clear();
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.DoubleBuffer, true);//ダブルバッファ設定
            SetStyle(ControlStyles.UserPaint, true);//ダブルバッファ設定
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);//ダブルバッファ設定
        }

        //破棄
        protected override void Dispose(bool disposing)
        {
            if (vScrollBar != null) {
                vScrollBar.Dispose();
            }
        }

        //描画
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            try {
                Graphics g = e.Graphics;
                //背景の描画
                Brush br = new SolidBrush(BackColor);
                g.FillRectangle(br, ClientRectangle);
                br.Dispose();

                //文字の描画
                int num = ClientRectangle.Height / FontHeight + 1;
                num = Math.Min(num, count);
                if (vScrollBar.Value + num > count) {
                    num -= vScrollBar.Value + num - count;
                }
                lock (this) {
                    for (int i = 0; i < num; i++) {
                        int idx = (start + vScrollBar.Value + i) % maxLine;
                        g.DrawString(lines[idx], Font, Brushes.Black, 0.0f,
                            (float)(i * FontHeight));
                    }
                }
                //枠の描画
                if (Focused) {
                    g.DrawRectangle(Pens.Black, 0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
                } else {
                    g.DrawRectangle(Pens.Gray, 0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
                }
            } catch (Exception ex) {
                Debug.WriteLine(ex.Message);
            }
        }

        //サイズ変更イベント
        protected override void OnSizeChanged(EventArgs e)
        {
            vScrollBar.Left = ClientRectangle.Width - vScrollBar.Width - 1;
            vScrollBar.Top = 1;
            vScrollBar.Height = ClientRectangle.Height - 2;
            UpdateMaximum();
            Invalidate();
        }

        //行の追加
        public void AppendText(string line)
        {
            lock (this) {
                int idx = (start + count) % maxLine;
                lines[idx] = line;
                if (count < maxLine) {//リングバッファがいっぱいでないとき
                    count++;
                    UpdateMaximum();
                } else {//リングバッファがいっぱいのとき
                    start = (start + 1) % maxLine;
                }
            }
            Invalidate();
        }

        //スクロールバーの最大値の更新
        private void UpdateMaximum()
        {
            int num = ClientRectangle.Height / FontHeight + 1;
            if (count > num) {
                vScrollBar.Enabled = true;
                vScrollBar.LargeChange = num;
                vScrollBar.Maximum = count;
            } else {
                vScrollBar.LargeChange = count;
                vScrollBar.Maximum = count;
                vScrollBar.Enabled = false;
            }
        }

        //クリア
        public void Clear()
        {
            lock (this) {
                lines = new string[maxLine];
                start = 0;
                count = 0;
            }
            UpdateMaximum();
        }

        //最後の行を表示
        public void ScrollToEnd()
        {
            int num = ClientRectangle.Height / FontHeight + 1;
            if (count < num) {
                vScrollBar.Value = 0;
            } else {
                vScrollBar.Value = vScrollBar.Maximum - vScrollBar.LargeChange + 1;
            }
        }

        //スクロールバーの値変更
        public void textArea_ValueChanged(object sender,EventArgs arg)
        {
            Invalidate();
        }

        //クリックイベント
        protected override void OnClick(EventArgs e)
        {
            Focus();
            base.OnClick(e);
        }

        //ホイールイベント
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            int num = ClientRectangle.Height / FontHeight + 1;
            if (count < num) {
                vScrollBar.Value = 0;
            } else {
                int v = vScrollBar.Value - Math.Sign(e.Delta);
                if (v < vScrollBar.Minimum) {
                    v = vScrollBar.Minimum;
                }
                if (v > vScrollBar.Maximum - vScrollBar.LargeChange + 1) {
                    v = vScrollBar.Maximum - vScrollBar.LargeChange + 1;
                }
                vScrollBar.Value = v;
            }
            base.OnMouseWheel(e);
        }

        //マウスエンターイベント
        protected override void OnMouseEnter(EventArgs e)
        {
            Focus();
            base.OnMouseEnter(e);
        }

        //フォーカス取得イベント
        protected override void OnGotFocus(EventArgs e)
        {
            Invalidate();
            base.OnGotFocus(e);
        }

        //フォーカス消失イベント
        protected override void OnLostFocus(EventArgs e)
        {
            Invalidate();
            base.OnLostFocus(e);
        }
    }
}

#スクロールバーの配置を自力で行っているが本当はFramework側に任せる方法があると思われる。

あと、昨日のエントリ「グローバルビューとローカルビュー」で書いたように自律時もローカルビューに対応した。

つくばチャレンジ2009まで、あと22日!時間がない!