何が書いてあるか?
- DataGridViewの縦スクロールの描画パフォーマンスを上げた方法
- 原因の特定方法(大したことではないが)
- リフレクションで無理やりSetプロパティの制約を回避した話
役に立つのか?
特殊な状況下なので、万人に役にたつかは微妙。
あ、たぶん。役に立たないです。
リフレクション使えば、例外回避して設定できるのかーぐらいかな。
(そもそも、リフレクション使う人は、それぐらい知ってると思うが)
特殊な状況について
DataGridViewを使いスプレッドシートの入出力画面を作成した。
カラムヘッダは独自に直接セル結合を行っていてFixed設定をしている。
※カラムヘッダは非表示(ColumnHeadersVisible = false)
ロウヘッダは行選択をするため残している。(RowHeadersVisible = true)
事象
DataGridViewの縦スクロール時に、上下端からスクロールする場合のスピードが顕著に遅い(カクカクする)
状況調査&原因特定?
DataGridViewはDataSourceを切り替えて使いまわしているが、遅いのは特定の場合で、すべてが遅いわけではない。
データ量の問題かと、表示レイアウトはそのままにDataSourceのみ変更したところ変化が無いことがわかった
⇒ つまり、データ量、DataSouceは関係ない
遅いところと、そうでないところの違いを調べたところ、
RowHeadersVisible の違いがあることがわかり、非表示にしたところパフォーマンスが改善した。
これが問題か?
対応方法について
RowHeadersVisible の設定でのパフォーマンス有無についてネットで調べたが特に有益な情報は得られず。
パフォーマンス改善について、ダブルバッファリングの手法があることがわかった。
以下、対応内容と採用可否である。
RowHeadersの非表示
時間があれば、独自実装して1カラム目をヘッダーのように扱うことは可能だが、ナイーブな(お察し下さい)実装のため残念ながら採用できない。ダブルバッファリング
ネットで多くみかけたのですが、実際に試してみると、
早くはなるものの、結合セルの部分の再描画が行われる黒くなり使い物にならない。
ここで手詰まりかと思っていたが、天からのひらめき。
RowHeadersVisibleのtrue/falseで、スピードが変わるならColumnHeadersも効果あるのでは?
ということで、第三の方法として
3. ColumnHeadersの「表示」
本来、非表示であるカラムヘッダを表示してみたところ、パフォーマンスが改善されたのである。
ちょっとの格闘と対応の完全版
結局原因自体は、よくわからんが、カラムヘッダを表示した場合、パフォーマンスが改善された。
ちなみに、検証結果は以下の通り。
ピンポイントの組み合わせで遅かったよ・・・(多くの人は、この事象に出会わないのでは?)
RowHeadersVisible | ColumnHeadersVisible | 結果 |
---|---|---|
true | true | OK |
true | false | 遅い |
false | true | OK |
false | false | OK |
というわけで、ヘッダの高さをゼロにして非表示扱いにしてみよう。
というのが今回のまとめになる。
dgv.ColumnHeadersHeight=0;//これは、うまくいかない
ところが、ゼロを実際に設定してみたところ例外が発生した。
MSDN(DataGridView.ColumnHeadersHeight プロパティ)を見ると3以下は設定できないようだ。
じゃあ、リフレクションを使ってみようと試してみるが、プロパティはあってもフィールドが見つからない。(NonPublicを指定しているのに)
プロパティ(PropertyInfo)のSetValueを使ってみると例外が発生する・・・
と、しばらく格闘していたが、わかってみればなんのことはない。
使っているクラスが、CommonFramework.Forms.DataGridViewでなく、それを継承したクラスだったからだ。
なまじpublicのプロパティが見えるので気づくのに時間がかかった。
というわけで、以下のコードで対応。
// dgv は、DataGridView(を継承したクラス)のインスタンスdgv.GetType().BaseType.GetField("columnHeadersHeight",BindingFlags.Instance|BindingFlags.NonPublic).SetValue(dgv,0);
なるほど。インスタンスからGetTypeするから混乱の元になったのか。
完全に動的に操作する必要ないから以下で大丈夫だった。
※とはいえ、フィールド名 columnHeadersHeight を特定するのが大変だったわけだが。
typeof(DataGridView).GetField("columnHeadersHeight",BindingFlags.Instance|BindingFlags.NonPublic).SetValue(dgv,0);
.Netのバージョンだったり、そもそも継承の有無で役に立つ可能性は、わからないがメモとして残しておく。