DataGridView の描画が遅いときに気をつけること

DataGridView で、

のプロパティが、自動に設定されているとき、

  • 行や列の追加
  • セルに値を設定

すると、描画が遅くなる。

以下の例では、50 x 50 で 9.2 秒かかった。

f:id:tt195361:20150612082645p:plain

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace DataGridViewSizeModeAndSpeed
{
    public partial class Form1 : Form
    {
        private const int ColumnCount = 50;
        private const int RowCount = 50;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var sw = new Stopwatch();
            sw.Start();

            // サイズ設定が自動のとき、行や列を追加したり、セルに値を設定すると、実行が遅くなる。
            dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
            dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;

            AddColumns();
            AddRows();
            SetValues();

            sw.Stop();
            label1.Text = String.Format("所要時間: {0}", sw.Elapsed);
        }

        private void AddColumns()
        {
            for (int index = 0; index < ColumnCount; ++index)
            {
                AddColumn(index + 1);
            }
        }

        private void AddColumn(int columnNumber)
        {
            var column = new DataGridViewColumn();
            column.Name = columnNumber.ToString();
            column.CellTemplate = new DataGridViewTextBoxCell();
            dataGridView1.Columns.Add(column);
        }

        private void AddRows()
        {
            for (int index = 0; index < RowCount; ++index)
            {
                dataGridView1.Rows.Add();
            }
        }

        private void SetValues()
        {
            for (int columnIndex = 0; columnIndex < ColumnCount; ++columnIndex)
            {
                for (int rowIndex = 0; rowIndex < RowCount; ++rowIndex)
                {
                    String value = String.Format("{0}-{1}", columnIndex + 1, rowIndex + 1);
                    dataGridView1[columnIndex, rowIndex].Value = value;
                }
            }
        }
    }
}

順番を、

  1. 自動サイズ設定しない。
  2. 行や列を追加、セルに値を設定。
  3. 自動サイズ設定する。

に変更すると、描画時間は 0.15 秒になった。

f:id:tt195361:20150612083603p:plain

        private void Form1_Load(object sender, EventArgs e)
        {
            var sw = new Stopwatch();
            sw.Start();

            // 行や列を追加したり、セルに値を設定するときは、自動サイズ設定しない。
            dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
            dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
            dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;

            AddColumns();
            AddRows();
            SetValues();

            // 自動でサイズを設定するのは、行や列を追加したり、セルに値を設定した後にする。
            dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
            dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;

            sw.Stop();
            label1.Text = String.Format("所要時間: {0}", sw.Elapsed);
        }

Emacs での長方形の範囲の取り扱い

Emacs では長方形の範囲を取り扱える。

たとえば以下のようなテキストで、左にある日付の部分を、右に移動したいとすると、

f:id:tt195361:20150609180859p:plain

'1. C-@ や C-SPC (set-mark-command) で角にマークを付けて、
'2. 長方形の対角にカーソルを移動し、
'3. M-x copy-rectangle-to-register で、長方形の範囲の内容を、たとえばレジスタ a にコピーする。

f:id:tt195361:20150609180923p:plain

'4. M-x delete-rectangle で、長方形の範囲を削除し、

f:id:tt195361:20150609181101p:plain

'5. 長方形を挿入する位置へカーソルを移動し、
'6. M-x insert-register で、'3 で内容を保存したレジスタ a を指定する。

f:id:tt195361:20150609181114p:plain

Emacs のキーボードマクロ

テキスト編集で同じ操作を繰り返すなら、Emacsキーボードマクロ が便利。

例えば、以下のような内容の各行をダブルクォートで囲むとすると、

f:id:tt195361:20150609085310p:plain

  1. C-x ( → キーボードマクロ定義開始。ステータス行に "Defining kbd macro..." と表示される。
  2. 繰り返す操作を入力。ここでは、
    1. " → ダブルクォート入力
    2. C-e → 行の最後に移動
    3. " → ダブルクォート入力
    4. C-n → 次の行に移動
    5. C-a → 行の最初に移動
  3. C-x ) → キーボードマクロ定義終了。ステータス行に "Keybord macro defined" と表示される。

f:id:tt195361:20150609085319p:plain

定義したキーボードマクロを実行するには C-x e を入力。繰り返し実行するには C-u (universal-argument) を使う。C-u 1 0 C-x e で 10 回実行してみるとこうなる。

f:id:tt195361:20150609085326p:plain

ドメイン駆動設計のユビキタス言語とモデル駆動設計

エリック・エヴァンスのドメイン駆動設計 の中心となる二つの概念のまとめ。

ユビキタス言語

  • プロジェクト関係者がモデルの記述に使用する共通言語。
  • この言語をコミュニケーションからコードまで利用する。

モデル駆動設計

  • 解決する問題の理解とソフトウェアの実装の両方に使えるモデルを作成する。
  • そのモデルをコードに反映させる。

マウスやキーボードでの範囲選択

マウスやキーボードで範囲を選択する方法。

  • マウスでドラッグする。

のが普通だが、ボタンを押したままマウスを動かすのは大変なので、

  • シフトキーを押しながら、
    • 範囲の終わりをマウスでクリック
    • 矢印キー・Homeキー・Endキーで範囲を拡げる

という方法もある。選択されている範囲の修正もできる。

メモ帳などでの文書編集だけでなく、エクスプローラやエクセルなどでも同様です。

f:id:tt195361:20150605132819p:plain

Enum ではなく Class を使う

Enum を使うと switch ... case ... することになる。

たとえば、プログラム言語を Enum で定義して、その名前を表示する例。

using System;

namespace UseClassInsteadOfEnum
{
    public enum ProgrammingLanguageEnum
    {
        CSharp,
        Java,
    }
    
    public class ByEnum
    {
        public static void PrintName(ProgrammingLanguageEnum progLang)
        {
            switch (progLang)
            {
                case ProgrammingLanguageEnum.CSharp:
                    Console.WriteLine("C#");
                    break;
                case ProgrammingLanguageEnum.Java:
                    Console.WriteLine("Java");
                    break;
                default:
                    throw new ArgumentException("progLang");
            }
        }
    }
}

Enum だと、

  • なにかするには switch ... case ... を書かないと、、、
  • 一応 default も書いておかないと心配だし、、、
  • 将来 Enum のメンバを変更したら、全部調べて修正しないと、、、

と、悩むことになる。

クラスにすると、スッキリする。

using System;

namespace UseClassInsteadOfEnum
{
    public class ProgrammingLanguageClass
    {
        // 各項目を表わすインスタンスは static にする。
        private static ProgrammingLanguageClass _cSharp = new ProgrammingLanguageClass("C#");

        public static ProgrammingLanguageClass CSharp
        {
            get { return _cSharp; }
        }

        private static ProgrammingLanguageClass _java = new ProgrammingLanguageClass("Java");

        public static ProgrammingLanguageClass Java
        {
            get { return _java; }
        }

        private String _name;

        // コンストラクタは private にして、クラス外からインスタンスが作れないようにする。
        private ProgrammingLanguageClass(String name)
        {
            _name = name;
        }

        public String Name
        {
            get { return _name; }
        }
    }

    public class ByClass
    {
        public static void PrintName(ProgrammingLanguageClass progLang)
        {
            Console.WriteLine(progLang.Name);
        }
    }
}

使う側からは、同じように見える。

namespace UseClassInsteadOfEnum
{
    class Program
    {
        static void Main(string[] args)
        {
            ByEnum.PrintName(ProgrammingLanguageEnum.CSharp);
            ByClass.PrintName(ProgrammingLanguageClass.CSharp);
        }
    }
}