none
DataGridViewの描画の乱れについて RRS feed

  • Question

  • Formに単純にDataGridViewを貼り付け、数行、数列の文字列を一覧として表示するように作成した、
    以下のようなサンプルプログラムを、デスクトップ外にはみ出すように移動させ、水平スクロールバーのつまみを動かすと、
    はみ出した側のDataGridViewの描画が乱れる現象が発生しています。
    どうやらAeroが有効なときのみこの現象が発生するようで、回避策が見つからず困っています。
    この現象を回避する方法をご存知の方がいらっしゃいましたらご教示ください。


      public Form1()
      {
       DataGridView dgv = new DataGridView();

       for (int i = 0; i < 10; i++)
       {
        DataGridViewTextBoxColumn columnN = new DataGridViewTextBoxColumn();
        columnN.HeaderText = i.ToString();
        dgv.Columns.Add(columnN);
       }


       for (int i = 0; i < 10; i++)
       {
        dgv.Rows.Add(i.ToString(), "aaaa","bbbb","cccc","dddd","eeee","ffff","gggg","hhhh","iii");
       }

       dgv.Dock = DockStyle.Fill;
       this.Controls.Add(dgv);
      }

    Tuesday, March 9, 2010 7:25 AM

Answers

  • また、ちらつきに目をつぶったとしても、DPIが100%のときには良好なのですが、150%などにすると、ご指摘の方法でも現象が再発してしまいます。もちろん水平スクロールバーのつまみを触れば再描画されるのですが・・・。
    まずこの問題から、、、
    DPI150%だと、スクロール後フォームを移動すると隠れていた部分がおかしくなることを確認しました。
    これはおかしいです…。
    (Visual Studio 画面の表示もおかしいです。
     MDI子画面の枠をつまんで動かすとおかしな位置に線が表示されました。)

    たんにスクロールするだけだと、大丈夫でした。(←これは現象が再現していないということでしょうか?)
    ↓とりあえず、フォーム移動に対応するだけならこちら。
    private DataGridView dgv; //宣言を Form1 コンストラクタから移動。
    public Form1()
    {
        dgv = new DataGridView(); //宣言は上でおこなっているので部分削除。
        (中略)
        dgv.Scroll += new ScrollEventHandler(this.dgv_Scroll);
    }
    private void dgv_Scroll(Object sender, ScrollEventArgs e)
    {
        ((DataGridView)sender).Invalidate();
    }
    private void Form1_Move(object sender, EventArgs e) //これを追加。イベントハンドラはGUIで指定しました。
    {
        dgv.Invalidate();
    }
    ご指摘の方法で改善されることは確認致しましたが、全体に再描画されるのでどうしてもちらついてしまいます。
    Invalidate は再描画範囲を引数で指定できます。
    再描画範囲を計算して渡してやることで改善すると思います。

    そもそも画面からはみ出させなければ問題も起きない…
    ということなら、画面移動時に DataGridView の幅を狭くしてしまう手もあるようにおもいます。
    • Edited by anningo Wednesday, March 10, 2010 5:53 AM ソースのコメント変更
    • Marked as answer by 高橋 春樹 Friday, March 12, 2010 2:45 AM
    Wednesday, March 10, 2010 5:51 AM
  • 必要な描画領域を計算するバージョンを作成してみました。

    private void dataGridView1_Scroll(object sender, ScrollEventArgs e)
    {
        if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll)
            FixGridPaint((DataGridView)sender, e.OldValue - e.NewValue);
    }
    
    private int oldFormLocationX = int.MinValue;
    
    private void Form1_Move(object sender, EventArgs e)
    {
        if (oldFormLocationX != int.MinValue)
            FixGridPaint(dataGridView1, this.Location.X - oldFormLocationX);
        oldFormLocationX = this.Location.X;
    }
    
    private void FixGridPaint(DataGridView grid, int dx)
    {
        var gridRect = grid.Bounds;
        Rectangle invalidateRect;
    
        if (dx > 0)
        {
            invalidateRect =
                new Rectangle(
                    grid.PointToClient(new Point(0, 0)).X,
                    gridRect.Y,
                    dx,
                    gridRect.Height);
        }
        else
        {
            dx = -dx;
            // プライマリのみ考慮。
            var screenRight = SystemInformation.PrimaryMonitorSize.Width;
            invalidateRect =
                new Rectangle(
                    grid.PointToClient(new Point(screenRight, 0)).X,
                    gridRect.Y,
                    dx,
                    gridRect.Height);
            invalidateRect.Offset(-dx, 0);
        }
    
        invalidateRect.Intersect(gridRect);
        if (!invalidateRect.IsEmpty) grid.Invalidate(invalidateRect);
    }


    DataGridView のソースコードのどこかに、画面サイズにクリッピングしている箇所があるんでしょうかね。
    それと、anningo さんも書かれた通り、DPI150% だと Visual Studio もいろいろおかしくなりますね…。
    (テスト後に DPI を元に戻したところ、Windows メニューなどの間隔が変になってしまいました。念のために復元ポイントを直前に作成しておいたので後で戻そう…(^^;)

    Wednesday, March 10, 2010 8:23 AM

All replies

  • 現象を確認しました。
    ↓回避というか、対処療法です。
    public Form1()
    {
        (中略)
        dgv.Scroll += new ScrollEventHandler(this.dgv_Scroll);
    }

    private void dgv_Scroll(Object sender, ScrollEventArgs e)
    {
        ((DataGridView)sender).Invalidate();
    }

    Wednesday, March 10, 2010 1:40 AM
  • ご返信ありがとうございます。

    ご指摘の方法で改善されることは確認致しましたが、全体に再描画されるのでどうしてもちらついてしまいます。
    また、ちらつきに目をつぶったとしても、DPIが100%のときには良好なのですが、150%などにすると、ご指摘の方法でも現象が再発してしまいます。もちろん水平スクロールバーのつまみを触れば再描画されるのですが・・・。

    全体の再描画ではなく、なんとか乱れる部分だけに再描画をしたいのと、こちらが解決できたとしても、上記DPI150%のケースを救える手段はございますでしょうか?
    Wednesday, March 10, 2010 4:22 AM
  • また、ちらつきに目をつぶったとしても、DPIが100%のときには良好なのですが、150%などにすると、ご指摘の方法でも現象が再発してしまいます。もちろん水平スクロールバーのつまみを触れば再描画されるのですが・・・。
    まずこの問題から、、、
    DPI150%だと、スクロール後フォームを移動すると隠れていた部分がおかしくなることを確認しました。
    これはおかしいです…。
    (Visual Studio 画面の表示もおかしいです。
     MDI子画面の枠をつまんで動かすとおかしな位置に線が表示されました。)

    たんにスクロールするだけだと、大丈夫でした。(←これは現象が再現していないということでしょうか?)
    ↓とりあえず、フォーム移動に対応するだけならこちら。
    private DataGridView dgv; //宣言を Form1 コンストラクタから移動。
    public Form1()
    {
        dgv = new DataGridView(); //宣言は上でおこなっているので部分削除。
        (中略)
        dgv.Scroll += new ScrollEventHandler(this.dgv_Scroll);
    }
    private void dgv_Scroll(Object sender, ScrollEventArgs e)
    {
        ((DataGridView)sender).Invalidate();
    }
    private void Form1_Move(object sender, EventArgs e) //これを追加。イベントハンドラはGUIで指定しました。
    {
        dgv.Invalidate();
    }
    ご指摘の方法で改善されることは確認致しましたが、全体に再描画されるのでどうしてもちらついてしまいます。
    Invalidate は再描画範囲を引数で指定できます。
    再描画範囲を計算して渡してやることで改善すると思います。

    そもそも画面からはみ出させなければ問題も起きない…
    ということなら、画面移動時に DataGridView の幅を狭くしてしまう手もあるようにおもいます。
    • Edited by anningo Wednesday, March 10, 2010 5:53 AM ソースのコメント変更
    • Marked as answer by 高橋 春樹 Friday, March 12, 2010 2:45 AM
    Wednesday, March 10, 2010 5:51 AM
  • 必要な描画領域を計算するバージョンを作成してみました。

    private void dataGridView1_Scroll(object sender, ScrollEventArgs e)
    {
        if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll)
            FixGridPaint((DataGridView)sender, e.OldValue - e.NewValue);
    }
    
    private int oldFormLocationX = int.MinValue;
    
    private void Form1_Move(object sender, EventArgs e)
    {
        if (oldFormLocationX != int.MinValue)
            FixGridPaint(dataGridView1, this.Location.X - oldFormLocationX);
        oldFormLocationX = this.Location.X;
    }
    
    private void FixGridPaint(DataGridView grid, int dx)
    {
        var gridRect = grid.Bounds;
        Rectangle invalidateRect;
    
        if (dx > 0)
        {
            invalidateRect =
                new Rectangle(
                    grid.PointToClient(new Point(0, 0)).X,
                    gridRect.Y,
                    dx,
                    gridRect.Height);
        }
        else
        {
            dx = -dx;
            // プライマリのみ考慮。
            var screenRight = SystemInformation.PrimaryMonitorSize.Width;
            invalidateRect =
                new Rectangle(
                    grid.PointToClient(new Point(screenRight, 0)).X,
                    gridRect.Y,
                    dx,
                    gridRect.Height);
            invalidateRect.Offset(-dx, 0);
        }
    
        invalidateRect.Intersect(gridRect);
        if (!invalidateRect.IsEmpty) grid.Invalidate(invalidateRect);
    }


    DataGridView のソースコードのどこかに、画面サイズにクリッピングしている箇所があるんでしょうかね。
    それと、anningo さんも書かれた通り、DPI150% だと Visual Studio もいろいろおかしくなりますね…。
    (テスト後に DPI を元に戻したところ、Windows メニューなどの間隔が変になってしまいました。念のために復元ポイントを直前に作成しておいたので後で戻そう…(^^;)

    Wednesday, March 10, 2010 8:23 AM
  • annigoさんご返信ありがとうございます。
    やはりDPI150%だとおかしいですよね。

    たんにスクロールするだけだと、大丈夫でした。(←これは現象が再現していないということでしょうか?)
    ご指摘の通りスクロール後、フォーム移動するとよろしくないことを再現と表現してしまいました。
    前述のスクロールのみと、スクロールと移動とで症状が若干違いましたね。

    ご指摘の方法でフォーム移動でも改善できました。
    Thursday, March 11, 2010 9:49 AM
  • TH01さんご返信ありがとうございます。

    実は画面サイズ外でかつForm外のDataGridViewの再描画がよろしくないように見える気がしてきました。。。

    ですがご教示頂いた方法で、そんなことも気にならず、ちらつかずに再描画できました。
    ありがとうございました。

    最近はデュアルディスプレイも多いので、プライマリのみだけではなく、
    とりあえずマウス位置のカレントディスプレイで判断してみようと思います。

    実はこの現象は垂直スクロールでも同様なようです。なかなか一筋縄には行きませんね。。。
    ご教示頂いた方法を参考に垂直も実装してみます。

    # DPI変更後の間隔は戻りましたでしょうか?申し訳ないです。
    Thursday, March 11, 2010 10:00 AM
  • > # DPI変更後の間隔は戻りましたでしょうか?申し訳ないです。

    1回再起動しても直らなかったのですが、試しにもう一度再起動したら直りました。
    なので復元は使わなくて済みました。
    ご心配おかけしました。

    上の話のご報告をしたかったのですが、それだけではしづらかったので、自己満足を兼ねて縦方向とマルチモニタ対応を作りました。
    ただ、マルチモニタの環境がないので、動作確認はできていません。画面をまたがった場合って、途中の描画も必要なんでしょうか…。(追記:確認しました!必要ありませんでした。)

    それと、画面サイズを取得する方法として前のコードでは SystemInformation を使いましたが、Screen という便利なクラスがあるんですね。

    private int _oldVerticalScrollingOffset;
    
    private void dataGridView1_Scroll(object sender, ScrollEventArgs e)
    {
        var grid = (DataGridView)sender;
        if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll)
        {
            FixGridPaint(grid, e.OldValue - e.NewValue, 0);
        }
        else
        {
            FixGridPaint(grid, 0, _oldVerticalScrollingOffset - grid.VerticalScrollingOffset);
            _oldVerticalScrollingOffset = grid.VerticalScrollingOffset;
        }
    }
    
    private int _oldFormLocationX = int.MinValue;
    private int _oldFormLocationY = int.MinValue;
    
    private void Form1_Move(object sender, EventArgs e)
    {
        if (_oldFormLocationX != int.MinValue)
        {
            FixGridPaint(
                dataGridView1,
                this.Location.X - _oldFormLocationX,
                this.Location.Y - _oldFormLocationY);
        }
        _oldFormLocationX = this.Location.X;
        _oldFormLocationY = this.Location.Y;
    }
    
    private void FixGridPaint(DataGridView grid, int dx, int dy)
    {
        //this.BeginInvoke(new Action(() => {
        using (var allScreenRegion = new Region(Rectangle.Empty))
        {
            foreach (var screen in Screen.AllScreens)
                allScreenRegion.Union(screen.Bounds);
    
            using (var invalidateRegion = allScreenRegion.Clone())
            {
                invalidateRegion.Translate(dx, dy);
                invalidateRegion.Complement(allScreenRegion);
    
                var gridRect = grid.Bounds;
                var gridScreenPoint = grid.PointToScreen(Point.Empty);
                gridRect.Offset(gridScreenPoint);
    
                invalidateRegion.Intersect(gridRect);
                invalidateRegion.Translate(-gridScreenPoint.X, -gridScreenPoint.Y);
    
                grid.Invalidate(invalidateRegion);
    
                //using (var g = grid.CreateGraphics())
                //    g.FillRegion(Brushes.Red, invalidateRegion);
            }
        }
        //}));
    }



    追記:バグを修正する前のコードをアップしてしまっていましたので訂正しました。
    追記:2画面の環境で試すことができました。正常に動作しました。
    さらに追記:2画面目が斜め位置に配置されたりサイズが異なるとだめでしたが、これは作っているときに想像してた通りだったりします。

    さらにさらに追記:FixGridPaint を全面的に修正しました。これで完ペキかもしれないです。グリッド全体の描画コストより低くければいいですけど。(^^;  とても余計な返信しちゃったような気がしてます。

    • Edited by TH01 Friday, March 12, 2010 6:42 AM さらにさらに追記
    Friday, March 12, 2010 4:08 AM
  •  私も、タブコントロール内に配置したDataGridViewの、表示乱れに遭遇しました。

     私の場合、タブコントロール内に、Anchor= Top,Bottom,Left,Right 状態で、タブコントロールもまた、フォームにアンカーを打ってあり、フォームのサイズ変更に追従せず、DataGridViewが適切に再描画されませんでした。

     form/tabControl/tabPage/DataGridViewそれぞれの、Paint/Resize/SizeChanged等のイベントを追ってみたところ、DataGridVeiwだけイベントが発生しないのを発見しました。

     本来は、Form_Resize -> ・・・tabPage_Resize -> tabPage_SizeChanged -> dataGridView_Resize -> dataGridView_SizeChanged と、イベントが連鎖する筈ですが、dataGridViewは、イベントが拾えませんでした。

     tabPage_SizeChangedイベント内で、dataGridView.Refresh()や、dataGridView.Update()も試してみましたが、効果はありまませんでした。

     ところが、乱れた状態のdataGridViewをあれこれ弄っていたところ、どれでも良いから列の幅を変更すると、dataGridView自体が適切なサイズで再描画されることに気づきました。

     ですから、

            private void tabPage1_SizeChanged( object sender, EventArgs e ) {
                if( dataGridView.Columns.Count > 0 ) {
                    dataGridView.Columns[0].Width += 1;
                    dataGridView.Columns[0].Width -= 1;
                }

    このように、列幅を変更してやれば、Formのサイズ変更に追従して、dataGridViewも適切に再描画されるようになります。

    if文があるのは、フォームの初期化時に、dataGridViewには列が無いからです。

    例え、フォーム初期化時でなくとも、列が無い状態では、表示が乱れるも何もあったものではありませんし。

    複数のタブやdataGridViewで構成されたフォームであれば、これほど単純には行きませんけどね。

    Thursday, February 4, 2021 3:14 AM