none
描画した DataGridView の ColumnHeader をちらつかせずに安定して表示したい RRS feed

  • 質問

  • - Windows Forms Data Controls and Databinding FAQ - MSDN Forums


    を参考に ColumnHeader が 2行の DataGridView を作ってみました。


    参考画像 (1)


     

    これをスクロールさせると、Paint で描画している 1行目のヘッダーが再描画
    されずに消えたり、描画しているセルで見えなくしている縦境界線が見えたり
    します。


    参考画像 (2) スクロール後に "A" が消えていたり、ヘッダー1行目で消しているはずの縦境界線が見えている


     

    これをどんなにスクロールしようが、安定して描画できるようにするにはどう
    したらよいのでしょう?


    ダウンロード : ソース

     

    p.s.

    ----

    DataGridView の右上がちょっと上書きされているのも気になります。

    2008年3月19日 18:05

回答

  • # custar さんのソースは見られていません。
    # 下記のコードは、custar さんのリンク先 FAQ のコードに対する変更になります。

     

    GetCellDisplayRectangle メソッドは「見えている領域」を戻しますので、項目の一部が隠れている場合、リンク先のコードでは正しく動作しないですね。
    例えば "A-a" の一部がスクロールアウトしている場合、「見えている狭い幅×2」の領域の中心に "A" が描画されるため、描画される位置が定まらず、右側には前に描画された文字列の残像が残ってしまいます。
    また、もし "A-a" がすべて隠れていれば GetCellDisplayRectangle は Empty を戻すため、この場合は全く描画されません。

     

    これへの対処として、GetCellDisplayRectangle の第3引数 cutOverflow を false にすれば、セルの右側が隠れていてもそれを含めて戻してくれるようになります。

    しかし false を指定した場合でも、隠れた左側の部分については含めてくれないため、今回の場合はこの指定だけでは不都合でした(これは引数名の通りなので仕様ですね)。
    そのため下記コードでは少し変なコードで領域全体を取得しています(もっとよい方法がありそうに思います)。
    # 見えている領域での中心に描画するという仕様にする手もありますね。

     

    それとリンク先のコードでは、マージする項目は等幅の2列と決めているため、幅を2倍する箇所がありますが、custar さんの例では3列をマージされているため、コード内の×2の部分を×3に変更する必要があります(見られていないので推測ですが。追記:参考画像1を見る限りこの点は問題なさそうですね)。
    等幅とは限らない場合は、幅の合計を求めると良いと思います(下記コードはそうしています)。

     

    最後に、右上の枠線が消えている件ですが、DataGridView では枠線が描画された後に Paint イベントが発生します。
    そのため、Paint では、枠線を消さないように描画する必要があります。
    下記コードでは、枠線の内側の領域にクリッピングすることで対処しています。

     

    リンク先 FAQ のコードは「たたき台」だと思いますし、下記コードもそうです(私のは手抜き)。
    マージする列の持ち方や、グリッドの各種プロパティの値(DividerWidth など)に対処した汎用的なものができたら、是非公開なさってください(^^;

     

    Code Snippet

    private void dataGridView1_Paint(object sender, PaintEventArgs e)
    {
        string[] monthes = { "January", "February", "March" };

     

        using (Region clip = e.Graphics.Clip)
        {
            // 外枠が消えないようにクリップ
            // (本来は BorderStyle を考慮する必要がある)
            Rectangle r = dataGridView1.ClientRectangle;
            r.Inflate(-1, -1);
            e.Graphics.Clip = new Region(r);

     

            for (int j = 0; j < 6; )
            {
                // [Win] と [Loss] の見えてる方の項目から描画する矩形を割り出す
                Rectangle r1 = dataGridView1.GetCellDisplayRectangle(j, -1, false);
                if (r1 != Rectangle.Empty)
                {
                    r1.X = r1.Right - dataGridView1.Columns[j].Width;
                }
                else
                {
                    r1 = dataGridView1.GetCellDisplayRectangle(j + 1, -1, false);
                    r1.X = r1.Right
                        - dataGridView1.Columns[j + 1].Width
                        - dataGridView1.Columns[j].Width;
                }
                r1.Width = dataGridView1.Columns[j].Width
                    + dataGridView1.Columns[j + 1].Width - 2;

     

                r1.Y += 2;  // ← ここも +2 に変更
                r1.Height = r1.Height / 2 - 2;

     

                e.Graphics.FillRectangle(
                    new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.BackColor), r1);

     

                // ヘッダの1行目の文字列を描画
                StringFormat format = new StringFormat();
                format.Alignment = StringAlignment.Center;
                format.LineAlignment = StringAlignment.Center;

     

                e.Graphics.DrawString(
                    monthes[j / 2],
                    dataGridView1.ColumnHeadersDefaultCellStyle.Font,
                    new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor),
                    r1,
                    format);

     

                j += 2;
            }

     

            e.Graphics.Clip = clip;
        }
    }

     

    private void dataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
    {
        // Form1_Load へ追加
        //dataGridView1.ColumnWidthChanged +=
        //    new DataGridViewColumnEventHandler(dataGridView1_ColumnWidthChanged);

     

        // 偶数列の幅変更時には、奇数列のヘッダも再描画させる
        if (e.Column.Index % 2 != 0)
        {
            Rectangle r = dataGridView1.GetCellDisplayRectangle(e.Column.Index - 1, -1, true);
            dataGridView1.Invalidate(r);
        }
    }

     

    2008年3月24日 5:15

すべての返信

  • # custar さんのソースは見られていません。
    # 下記のコードは、custar さんのリンク先 FAQ のコードに対する変更になります。

     

    GetCellDisplayRectangle メソッドは「見えている領域」を戻しますので、項目の一部が隠れている場合、リンク先のコードでは正しく動作しないですね。
    例えば "A-a" の一部がスクロールアウトしている場合、「見えている狭い幅×2」の領域の中心に "A" が描画されるため、描画される位置が定まらず、右側には前に描画された文字列の残像が残ってしまいます。
    また、もし "A-a" がすべて隠れていれば GetCellDisplayRectangle は Empty を戻すため、この場合は全く描画されません。

     

    これへの対処として、GetCellDisplayRectangle の第3引数 cutOverflow を false にすれば、セルの右側が隠れていてもそれを含めて戻してくれるようになります。

    しかし false を指定した場合でも、隠れた左側の部分については含めてくれないため、今回の場合はこの指定だけでは不都合でした(これは引数名の通りなので仕様ですね)。
    そのため下記コードでは少し変なコードで領域全体を取得しています(もっとよい方法がありそうに思います)。
    # 見えている領域での中心に描画するという仕様にする手もありますね。

     

    それとリンク先のコードでは、マージする項目は等幅の2列と決めているため、幅を2倍する箇所がありますが、custar さんの例では3列をマージされているため、コード内の×2の部分を×3に変更する必要があります(見られていないので推測ですが。追記:参考画像1を見る限りこの点は問題なさそうですね)。
    等幅とは限らない場合は、幅の合計を求めると良いと思います(下記コードはそうしています)。

     

    最後に、右上の枠線が消えている件ですが、DataGridView では枠線が描画された後に Paint イベントが発生します。
    そのため、Paint では、枠線を消さないように描画する必要があります。
    下記コードでは、枠線の内側の領域にクリッピングすることで対処しています。

     

    リンク先 FAQ のコードは「たたき台」だと思いますし、下記コードもそうです(私のは手抜き)。
    マージする列の持ち方や、グリッドの各種プロパティの値(DividerWidth など)に対処した汎用的なものができたら、是非公開なさってください(^^;

     

    Code Snippet

    private void dataGridView1_Paint(object sender, PaintEventArgs e)
    {
        string[] monthes = { "January", "February", "March" };

     

        using (Region clip = e.Graphics.Clip)
        {
            // 外枠が消えないようにクリップ
            // (本来は BorderStyle を考慮する必要がある)
            Rectangle r = dataGridView1.ClientRectangle;
            r.Inflate(-1, -1);
            e.Graphics.Clip = new Region(r);

     

            for (int j = 0; j < 6; )
            {
                // [Win] と [Loss] の見えてる方の項目から描画する矩形を割り出す
                Rectangle r1 = dataGridView1.GetCellDisplayRectangle(j, -1, false);
                if (r1 != Rectangle.Empty)
                {
                    r1.X = r1.Right - dataGridView1.Columns[j].Width;
                }
                else
                {
                    r1 = dataGridView1.GetCellDisplayRectangle(j + 1, -1, false);
                    r1.X = r1.Right
                        - dataGridView1.Columns[j + 1].Width
                        - dataGridView1.Columns[j].Width;
                }
                r1.Width = dataGridView1.Columns[j].Width
                    + dataGridView1.Columns[j + 1].Width - 2;

     

                r1.Y += 2;  // ← ここも +2 に変更
                r1.Height = r1.Height / 2 - 2;

     

                e.Graphics.FillRectangle(
                    new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.BackColor), r1);

     

                // ヘッダの1行目の文字列を描画
                StringFormat format = new StringFormat();
                format.Alignment = StringAlignment.Center;
                format.LineAlignment = StringAlignment.Center;

     

                e.Graphics.DrawString(
                    monthes[j / 2],
                    dataGridView1.ColumnHeadersDefaultCellStyle.Font,
                    new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor),
                    r1,
                    format);

     

                j += 2;
            }

     

            e.Graphics.Clip = clip;
        }
    }

     

    private void dataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
    {
        // Form1_Load へ追加
        //dataGridView1.ColumnWidthChanged +=
        //    new DataGridViewColumnEventHandler(dataGridView1_ColumnWidthChanged);

     

        // 偶数列の幅変更時には、奇数列のヘッダも再描画させる
        if (e.Column.Index % 2 != 0)
        {
            Rectangle r = dataGridView1.GetCellDisplayRectangle(e.Column.Index - 1, -1, true);
            dataGridView1.Invalidate(r);
        }
    }

     

    2008年3月24日 5:15
  • 動作確認しました。


    無茶なスクロールさせても消えませんね。へぇ。


    確認画像 (1) スクロールしても表示が乱れない


     

    確認画像 (2) 仮想画面 (VD) で画面を切り替えた後の乱れ


     

    私は上記仮想画面を使っているのでこうなりましたが、デバッグ時に、IDE と
    画面の前後を切り替えるとこうなります。まぁ、自力で解決してみます。

     

    まずは仕組みを確認します

     

    TH01 さん、サポートありがとうございます。

    2008年3月24日 14:08
  • 確かに私のコードには問題がありますね。
    仮想画面を使用しなくても、最小化してから元に戻したりしても表示はおかしくなります。

    「自力で」とは書かれていますが、おせっかいさせてください。
    # これも汚いコードですが…

     

    Code Snippet

    //Rectangle r = dataGridView1.ClientRectangle;
    //r.Inflate(-1, -1);
    //e.Graphics.Clip = new Region(r);

    // ↓に変更

    Rectangle r = Rectangle.Empty;
    for (int j = 0; j < dataGridView1.Columns.Count; j++)
    {
        Rectangle rh = dataGridView1.GetCellDisplayRectangle(j, -1, true);
        if (rh != Rectangle.Empty)
        {
            if (r == Rectangle.Empty)
                r = rh;
            else
                r = Rectangle.Union(r, rh);
        }
    }
    e.Graphics.Clip = new Region(r);

     

    それと、custar さんのスクリーンショットでは、Loss と Win の間の線が1ピクセル欠けてしまっているようですね。以下のコードを適切な位置に入れてみてください。

    # そういえば元コードも +1 してますね。こちらでは大丈夫なのですが。


    r1.X += 1; r1.Width -= 1;

    2008年3月28日 10:14