質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

4134閲覧

DataGridViewの再描画が無限ループの理由を知りたい

meshkit

総合スコア72

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2018/03/19 02:49

前提・実現したいこと

C#でWindowsFormアプリケーションを開発しています。
DataGridViewに色をつけたいです。
CellPaintingイベントで、ForeColorで色を変更してみました。
1回だけなら問題なしですが、DefaultCellStyleを2回変更すると描画処理が終わりません。
どうして無限ループになるのか、いまいちわからずもやもやしています。
すっきりしたいです。

発生している問題・エラーメッセージ

再表示が無限ループする。

該当のソースコード

ループしない private void dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (e.RowIndex < 0) return; ((DataGridView)sender).DefaultCellStyle.ForeColor = Color.Turquoise; }
無限ループ private void dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (e.RowIndex < 0) return; ((DataGridView)sender).DefaultCellStyle.ForeColor = Color.Crimson; ((DataGridView)sender).DefaultCellStyle.ForeColor = Color.Turquoise; }
ループしない private void dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (e.RowIndex < 0) return; e.CellStyle.ForeColor = Color.Crimson; e.CellStyle.ForeColor = Color.Turquoise; }

補足情報(FW/ツールのバージョンなど)

Visual Studio 2015 Pro
Windows Form Applacation
C#

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/03/19 03:29

「無限ループ」のコードで両方とも ForeColor となってますが、間違いないですか? 片方は BackColor とか?
meshkit

2018/03/19 04:06

間違いないです。
退会済みユーザー

退会済みユーザー

2018/03/19 04:26 編集

ということは、想定外のことをして想定外のことが起こったということのような気がしますが・・・それは置いといて、2 回設定すると何故 CellPainting イベントが発生するか知りたいということですか? そうだとすると、自分は分かりませんし、それを追求する気力もないので、お役に立てずすみませんが、他の方の回答をお待ちください。
guest

回答1

0

ベストアンサー

コントロールのデフォルトの描画処理では達成できないような描画処理を実装する方法をオーナードローといったりすると思います。それは「今からコントロールを描画します!」というイベントに対するハンドラーを定義することで達成します。

さて「今から自分自身を描画します」というイベント(本件ではCellPaintingイベントということになりましょうか)がいつ発生するかというと、理屈からいって「コントロールの外観に変化がおきるような変更が起こった時」ということになります。

さて、DataGridViewのDefaultCellStyle.ForeColorはセルの前景色を意味するのでこの値が変化すると自動的にCellPaintingイベントが発生し再描画が行われるようになっています。

それを踏まえて・・・

  • (A)最初のコード

コントロールのDefaultCellStyle.ForeColorを変更しているためdataGridView_CellPaintingメソッドの実行中に再びCellPaintingイベントが起きてしまいます。ただ、一度Color.Turquoiseの値を設定した後ではCellPaintingイベント処理中に再度同じ色を設定しても「色の変化」は起きないため無限ループになりません。

  • (B)次のコード

外観に影響を与えるプロパティーを描画中に複数回変更するということは「再描画処理中に必ずCellPainingイベントを発火させる」ことになり、再描画がいつまでたっても完了しなくなります。

  • (C)最後のコード

外観に影響を与えるプロパティーを変更しておらず、単にイベントオブジェクトの中にある「今回の描画における前景色、背景色を変更しているだけ」のためCellPaintingイベントが発生しません。

一般にイベントハンドラーの中でそのイベントが発生する原因になるようなことを再度行うのは避けるべきです。無限ループになってしまいますから。描画に関しては「外観に影響を及ぼす情報を変更するのは描画処理以外の場所ですべきこと」だと思ってください。


追記:自分にも曖昧な点があるのでちょっと調べてみました。

C#

1private Dictionary<object, string> dict = new Dictionary<object, string>(); 2private int idCount = 1; 3 4private string id(object o) { 5 if (!dict.ContainsKey(o)) { 6 dict[o] = string.Format("#{0:X2}", idCount++); 7 } 8 return dict[o]; 9} 10bool dumped=false; 11 12private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { 13 if (!dumped) { 14 foreach (DataGridViewRow row in dataGridView1.Rows) { 15 string s = ""; 16 foreach (DataGridViewCell cc in row.Cells) { 17 s += $" [{cc.RowIndex,2:D},{cc.ColumnIndex,2:D}] Cell={id(cc)}/Cell.Style={id(cc.Style)}"; 18 } 19 Console.WriteLine(s); 20 } 21 dumped = true; 22 } 23 int ri = e.RowIndex; 24 int ci = e.ColumnIndex; 25 Console.WriteLine($"[{ri,2:D}, {ci,2:D}] : CellStyle={id(e.CellStyle)}"); 26 //e.CellStyle.ForeColor = Color.Black; //(D) 27 //e.CellStyle.BackColor = Color.Yellow; //(D) 28 //dataGridView1.DefaultCellStyle.ForeColor = Color.Red; //(E) 29 //dataGridView1.DefaultCellStyle.BackColor = Color.Blue;//(E) 30}

上記を2x2のDataGridViewに対して動かすと初期画面描画時には次のようになりました。
行・列のインデックスが-1になっているところは多分ヘッダー部分ではないかと想像します(ちゃんとわかってません)
着目してほしいのはDataGridViewCellのStyleプロパティオブジェクトの値はEventArgのCellStyleオブジェクトと同一ではないという点です。

[ 0, 0] Cell=#01/Cell.Style=#02 [ 0, 1] Cell=#03/Cell.Style=#02 [ 1, 0] Cell=#04/Cell.Style=#02 [ 1, 1] Cell=#05/Cell.Style=#02 [-1, -1] : CellStyle=#06 [-1, 0] : CellStyle=#06 [-1, 1] : CellStyle=#06 [ 0, -1] : CellStyle=#06 [ 0, 0] : CellStyle=#07 [ 0, 1] : CellStyle=#07 [ 1, -1] : CellStyle=#06 [ 1, 0] : CellStyle=#07 [ 1, 1] : CellStyle=#07

(D)をアンコメントする(CellPainingの中でEventArgのCellStyleを変更する)と、

[ 0, 0] Cell=#01/Cell.Style=#02 [ 0, 1] Cell=#03/Cell.Style=#02 [ 1, 0] Cell=#04/Cell.Style=#02 [ 1, 1] Cell=#05/Cell.Style=#02 [-1, -1] : CellStyle=#06 [-1, 0] : CellStyle=#07 <- [-1, 1] : CellStyle=#07 <- [ 0, -1] : CellStyle=#08 <- [ 0, 0] : CellStyle=#09 <- [ 0, 1] : CellStyle=#09 <- [ 1, -1] : CellStyle=#0A <- [ 1, 0] : CellStyle=#0B <- [ 1, 1] : CellStyle=#0B <-

さらに(E)をアンコメントする(CellPainingの中でDefaultCellStyleを変更する)と、

[ 0, 0] Cell=#01/Cell.Style=#02 [ 0, 1] Cell=#03/Cell.Style=#02 [ 1, 0] Cell=#04/Cell.Style=#02 [ 1, 1] Cell=#05/Cell.Style=#02 [-1, -1] : CellStyle=#06 [-1, 0] : CellStyle=#07 [-1, 1] : CellStyle=#07 [ 0, -1] : CellStyle=#08 [ 0, 0] : CellStyle=#09 [ 0, 1] : CellStyle=#09 [ 1, -1] : CellStyle=#0A [ 1, 0] : CellStyle=#0B [ 1, 1] : CellStyle=#0B [-1, -1] : CellStyle=#0C [-1, 0] : CellStyle=#0D [-1, 1] : CellStyle=#0D [ 0, -1] : CellStyle=#0E [ 0, 0] : CellStyle=#0F [ 0, 1] : CellStyle=#0F [ 1, -1] : CellStyle=#10 [ 1, 0] : CellStyle=#11 [ 1, 1] : CellStyle=#11

こんな具合になります。上記結果を元の回答コメントに照らして考えてみるといかがでしょう?

投稿2018/03/19 04:53

編集2018/03/19 08:30
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

meshkit

2018/03/19 05:36

コメントありがとうございます。 (1)DataGridViewのDefaultCellStyle.ForeColorはセルの前景色である。 (2)DataGridViewのDefaultCellStyle.ForeColorが変化するとCellPaintingイベントが発生する。 (3)CellStyle.ForeColorは今回の描画の前景色である。CellPaintingイベントは発生しない。 ということになりますか? 現象としては、 DefaultCellStyle.ForeColorの変更ではCellPaintingイベントが発生する。 CellStyle.ForeColorの変更ではCellPaintingイベントが発生しない。 なのだと思うのですが、なぜDefaultのときはCellPaintingイベントが発生し、単なるCellStyleのときは発生しないのでしょう。 そういうものだ、というのならそういうものだとは思いますが、まだすこしもやもやしています。 補足していただけたらうれしいです。
KSwordOfHaste

2018/03/19 06:13 編集

DefaultCellStyleはDataGridViewの(オブジェクト指向での一般的用語を用いるなら)属性です。DataGridViewの外観はそれ自身の属性によって決まるわけですね。一方CellStyleはDataGridViewCellPaintingEventArgsの属性です。このオブジェクトは「再描画の度に生成される(あるいはキャッシュされるものかも知れませんが)イベント用の情報であり、その描画の期間内での再描画に関する一時的な情報を保持するもの(オブジェクト指向でのオブジェクト)です。 それらオブジェクトの「役割」とそれによって決まってくる「生存期間」などを考えるとなぜそういう考え方になるか実感しやすいのではないでしょうか?
meshkit

2018/03/19 06:49

なるほど。少し頭を整理しました。まだ残る疑問点は次のようなことです。 (1)CellStyle.ForeColorでもCellPaintingイベントは発生する。->CellStyle.ForeColorは今回の描画の前景色である。CellPaintingイベントは発生しないという理解は間違い。 検証コード private void button1_Click(object sender, EventArgs e) { foreach (DataGridViewRow row in dataGridViewRow.Rows) { foreach (DataGridViewCell cell in row.Cells) { cell.Style.ForeColor = Color.Turquoise; break; } break; } } (2)(A)に相当する「CellPaintingイベントのなかでCellの描画を変更するサンプル」をWebで散見するが、そもそもCellPaintingイベントのなかでCellの描画を変更すればループするのではないか? Cellの描画を変更するのなら、別のイベントで処理すべきでは? それにもかかわらずCellPaintingイベントのなかでCellの描画を変更する例が多い理由はなに?
KSwordOfHaste

2018/03/19 08:37 編集

(1)(2)については回答に追記してみました。動きに疑問が生じたら「どんな仕組みなのかリファレンスを見たり、実際の動きを確認する」とだんだん見えてくると思います。 実験結果に対する自分の解釈は「DataGridView.DefaultCellStyleやDagaGridViewCell.StyleはGridViewや個々のセルの外観を左右する属性」でありデザインパターンでいうObserverパターンで「変更したら再描画する」という仕組みになっており、「EventArg.CellStyleはそうではない」というものです。
meshkit

2018/03/20 01:24

検証コードありがとうございます。こちらでも動作してみました。 わたしも同様の検証コードをつくってテストしてみました。 2018/03/19 15:49のとおり、Buttonのclickでcell.Style.ForeColorを変更してもCellPaintingは発火しているので、cell.Styleの変更でもCellPaintingは発生します。cell.Styleの変更ではループせず、DefaultCellStyleの変更ではループする理由の解釈は、KSwordOfHasteさんとは異なると予想します(わかってないです)。ともあれ現象(CellPaintingでDefultCellStyleを複数回変更するとループする)と(当面の)回避策(CellPaintingでDefultCellStyleは変更しない)はつかめました。 おつき合いいただきありがとうございました。
KSwordOfHaste

2018/03/20 01:37 編集

繰り返しになってしまいますがが、一応追記コードで「dataGridViewの各セルのStyleプロパティーオブジェクトとEventArg.CellStyleに格納されているオブジェクトは同じクラスではあるが別のオブジェクト」ということを述べたつもりです。 質問者さんがButton_clickでの検証で言えることはdataGridViewの各セルのStyleプロパティーの変更ではCellPaintingイベントが発火するということですが、それは直ちにEventArg.CellStyleを変更したときCellPaintingイベントが発火することにはならないと自分は考えました。
meshkit

2018/03/20 05:19

補足ありがとうございます。そうですね。いま全部を理解することは残念ながらできなかったのですが、DataGridViewは業務で頻繫に使うものですし、また次の機会にクリアな気持ちで見ると、また別の気づきがあるかと思います。そのときにはEurekaするかもしれません。
YAmaGNZ

2018/03/20 06:08

DataGridViewの場合、DataGridView.DefaultCellStyleプロパティやDataGridViewColumn.DefaultCellStyleプロパティ、DataGridViewRow.DefaultCellStyleプロパティなど、最終的なセルの描画に影響を与えるプロパティが多数あります。 ですので、描画する時にどのStyleで描画するのかがDataGridViewCellPaintingEventArgsで通知される形となります。 そして、DataGridViewCellPaintingEventArgsに入れられるDataGridViewCellStyleは、描画されるときにNewされて、描画すべき内容がセットされます。(このあたりは.NET Frameworkのソースを参照してください。) ですので、DataGridViewCellPaintingEventArgsのDataGridViewCellStyleを変更してもDataGridViewの設定には影響を与えません。 その他のDataGridViewのプロパティを変更する場合は、描画すべき内容が変化すれば再度描画する必要が出てきますので、CellPaintingイベントが発生することになります。 壁にペンキを塗るということに例えると 通常の処理は 1.工務店に赤に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 2.工務店は職人に赤のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 3.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) 4.持ってきた道具で塗る(実際の描画処理) といった感じでしょうか。 これが(A)の場合、 1.工務店に赤に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 2.工務店は職人に赤のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 3.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) 4.工務店に青に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 5.工務店は職人を呼び戻して青のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 6.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) 7.持ってきた道具で塗る(実際の描画処理) といった感じになるかと (B)の場合は 1.工務店に赤に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 2.工務店は職人に赤のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 3.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) 4.工務店に青に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 5.工務店は職人を呼び戻して青のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 6.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) 7.工務店に黄色に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 8.工務店は職人を呼び戻して黄色のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 9.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) 10.工務店にやっぱり青に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 11.工務店は職人を呼び戻して青のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 12.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) と延々と続くような感じ (C)の場合は 1.工務店に赤に塗ってくれと依頼する(プロパティなどでスタイルをセットする等) 2.工務店は職人に赤のペンキを渡して塗ってこいと現場に向かわせる(CellPaintingイベント) 3.職人が現場で図面や道具を広げる(DataGridViewCellPaintingEventArgsの内容) 4.やっぱり青で塗ってくれと職人に直接ペンキを渡す(DataGridViewCellPaintingEventArgsの内容を変更) 5.持ってきた道具で塗る(実際の描画処理) といった感じでしょうか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問