webdevqa.jp.net

2つの画面の1つでのDataGridViewの恐ろしい再描画パフォーマンス

私は実際にこれを解決しましたが、後世のために投稿しています。

デュアルモニターシステムのDataGridViewで非常に奇妙な問題に遭遇しました。この問題は、コントロールの非常に遅い再描画(完全再描画の場合は30秒など)として現れますが、それは画面の1つにある場合のみです。反対に、再描画速度は問題ありません。

最新の非ベータドライバー(175.何か)を搭載したNvidia 8800 GTを使用しています。ドライバーのバグですか?この特定の構成で生活しなければならないので、それは空中に残しておきます。 (ただし、ATIカードでは発生しません...)

ペイント速度はセルの内容とは関係ありません。また、カスタム描画では、ソリッドな長方形をペイントするだけでもパフォーマンスはまったく向上しません。

後で、System.Windows.Forms.Integration名前空間からElementHostをフォームに配置すると、問題が修正されることがわかりました。それを台無しにする必要はありません。 DataGridViewが存在するフォームの子である必要があります。 Visibleプロパティがtrueである限り、(0、0)にサイズ変更できます。

アプリケーションに.NET 3/3.5依存関係を明示的に追加したくありません。リフレクションを使用して、実行時にこのコントロールを作成できるメソッドを作成します(可能な場合)。それは機能し、少なくとも必要なライブラリを持たないマシンでは正常に失敗します-遅くなります。

また、この方法を使用すると、アプリの実行中に修正を適用できるため、フォーム上でWPFライブラリが何を変更しているかを簡単に確認できます(Spy ++を使用)。

たくさんの試行錯誤の後、コントロールだけでダブルバッファリングを有効にすると(フォームだけでなく)問題が修正されることに気付きました!


したがって、DataGridViewに基づいてカスタムクラスを作成するだけで、そのDoubleBufferingを有効にできます。それでおしまい!

class CustomDataGridView: DataGridView
{
    public CustomDataGridView()
    {
        DoubleBuffered = true;
    }
}

グリッドのすべてのインスタンスがこのカスタムバージョンを使用している限り、すべてが順調です。これにより、サブクラスソリューションを使用できない状況に陥った場合(コードがない場合)、そのコントロールをフォームに挿入しようとすることができます:)(ただし、リフレクションを使用して、外部からDoubleBufferedプロパティを強制して、依存関係をもう一度回避することを試みる可能性が高くなります)。

こんなに簡単なことが私の時間のほとんどを食いつぶしたのは悲しいです...

81
Corey Ross

DataGridViewに基づいてカスタムクラスを作成するだけで、そのDoubleBufferingを有効にできます。それでおしまい!


class CustomDataGridView: DataGridView
{
    public CustomDataGridView()
    {
        DoubleBuffered = true;
    } 
}

グリッドのすべてのインスタンスがこのカスタムバージョンを使用している限り、すべてが順調です。これにより、サブクラスソリューションを使用できない状況に陥った場合(コードがない場合)、そのコントロールをフォームに挿入しようとすることができると思います:)(ただし、依存関係をもう一度回避するために、外部からDoubleBufferedプロパティを強制するためにリフレクションを使用する可能性が高くなります)。

こんなに簡単なことが私の時間のほとんどを食いつぶしたのは悲しい...

注:質問を回答済みとしてマークできるように、回答を回答にする

64
Benoit

以下に、Benoitが提案するサブクラス化を行わずに、リフレクションを使用してプロパティを設定するコードをいくつか示します。

typeof(DataGridView).InvokeMember(
   "DoubleBuffered", 
   BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
   null, 
   myDataGridViewObject, 
   new object[] { true });
59
Brian Ensink

VB.NETでそれを行う方法を探している人のために、コードを以下に示します。

DataGridView1.GetType.InvokeMember("DoubleBuffered", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.SetProperty, Nothing, DataGridView1, New Object() {True})
15
GELR

以前の投稿に加えて、Windows Formsアプリケーションの場合、これをDataGridViewコンポーネントで使用して高速化しています。クラスDrawingControlのコードは次のとおりです。

_DrawingControl.SetDoubleBuffered(control)
DrawingControl.SuspendDrawing(control)
DrawingControl.ResumeDrawing(control)
_

コンストラクターでInitializeComponent()の後にDrawingControl.SetDoubleBuffered(control)を呼び出します。

ビッグデータの更新を行う前に、DrawingControl.SuspendDrawing(control)を呼び出します。

ビッグデータの更新を行った後、DrawingControl.ResumeDrawing(control)を呼び出します。

最後の2つは、try/finallyブロックで行うのが最適です。 (または、クラスをIDisposableとして書き直し、コンストラクターでSuspendDrawing()を呼び出し、ResumeDrawing()Dispose()を呼び出します。)

_using System.Runtime.InteropServices;

public static class DrawingControl
{
    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);

    private const int WM_SETREDRAW = 11;

    /// <summary>
    /// Some controls, such as the DataGridView, do not allow setting the DoubleBuffered property.
    /// It is set as a protected property. This method is a work-around to allow setting it.
    /// Call this in the constructor just after InitializeComponent().
    /// </summary>
    /// <param name="control">The Control on which to set DoubleBuffered to true.</param>
    public static void SetDoubleBuffered(Control control)
    {
        // if not remote desktop session then enable double-buffering optimization
        if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
        {

            // set instance non-public property with name "DoubleBuffered" to true
            typeof(Control).InvokeMember("DoubleBuffered",
                                         System.Reflection.BindingFlags.SetProperty |
                                            System.Reflection.BindingFlags.Instance |
                                            System.Reflection.BindingFlags.NonPublic,
                                         null,
                                         control,
                                         new object[] { true });
        }
    }

    /// <summary>
    /// Suspend drawing updates for the specified control. After the control has been updated
    /// call DrawingControl.ResumeDrawing(Control control).
    /// </summary>
    /// <param name="control">The control to suspend draw updates on.</param>
    public static void SuspendDrawing(Control control)
    {
        SendMessage(control.Handle, WM_SETREDRAW, false, 0);
    }

    /// <summary>
    /// Resume drawing updates for the specified control.
    /// </summary>
    /// <param name="control">The control to resume draw updates on.</param>
    public static void ResumeDrawing(Control control)
    {
        SendMessage(control.Handle, WM_SETREDRAW, true, 0);
        control.Refresh();
    }
}
_
8
brtmckn

これに対する答えも私にとってはうまくいきました。私は、ソリューションを実装するすべての人にとって標準的なプラクティスであると思う洗練を追加すると考えました。

このソリューションは、UIがリモートデスクトップでクライアントセッションとして実行されている場合、特に利用可能なネットワーク帯域幅が低い場合を除き、うまく機能します。このような場合、ダブルバッファリングを使用するとパフォーマンスが低下する可能性があります。したがって、より完全な答えとして以下を提案します。

class CustomDataGridView: DataGridView
{
    public CustomDataGridView()
    {
        // if not remote desktop session then enable double-buffering optimization
        if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
            DoubleBuffered = true;
    } 
}

詳細については、「 リモートデスクトップ接続の検出 」を参照してください。

6
Kev

問題の解決策を見つけました。高度なディスプレイプロパティの[トラブルシューティング]タブに移動し、ハードウェアアクセラレーションスライダーを確認します。 ITから新しい会社のPCを入手したとき、フルから1ティックに設定され、データグリッドに問題はありませんでした。ビデオカードドライバーを更新してフルに設定すると、データグリッドコントロールの描画が非常に遅くなりました。そこで、それを元の位置にリセットすると、問題はなくなりました。

このトリックがあなたにも役立つことを願っています。

1
user185931

ベスト!:

Private Declare Function SendMessage Lib "user32" _
  Alias "SendMessageA" _
  (ByVal hWnd As Integer, ByVal wMsg As Integer, _
  ByVal wParam As Integer, ByRef lParam As Object) _
  As Integer

Const WM_SETREDRAW As Integer = &HB

Public Sub SuspendControl(this As Control)
    SendMessage(this.Handle, WM_SETREDRAW, 0, 0)
End Sub

Public Sub ResumeControl(this As Control)
    RedrawControl(this, True)
End Sub

Public Sub RedrawControl(this As Control, refresh As Boolean)
    SendMessage(this.Handle, WM_SETREDRAW, 1, 0)
    If refresh Then
        this.Refresh()
    End If
End Sub
1
user3727004

この問題を修正するために行ったことを追加するだけで、最新のNvidiaドライバーにアップグレードして問題を解決しました。コードを書き換える必要はありませんでした。

完全を期すため、このカードは2008年3月付けのドライバーを搭載したNvidia Quadro NVS 290(v。169)でした。最新(2009年2月、v。182)にアップグレードすると、すべてのコントロール、特にDataGridViewのPaintイベントが大幅に改善されました。

この問題は、ATIカード(開発が行われる)では見られませんでした。

1
Richard Morgan

デュアルモニターシステムで.NET 3.0とDataGridViewを使用すると、同様の問題が発生しました。

このアプリケーションでは、灰色の背景でグリッドを表示し、セルを変更できなかったことを示します。 「設定の変更」ボタンを選択すると、プログラムはセルの背景色を白に変更して、セルのテキストを変更できることをユーザーに示します。 「キャンセル」ボタンは、前述のセルの背景色をグレーに戻します。

背景色が変更されると、フリッカーが発生し、行と列の数が同じであるデフォルトのサイズのグリッドが一時的に表示されます。この問題はプライマリモニターでのみ発生し(セカンダリモニターでは発生しません)、単一のモニターシステムでは発生しません。

上記の例を使用してコントロールをダブルバッファリングすると、問題が解決しました。あなたの助けを大いに感謝します。

0
James