Desempenho de redesenho horrível do DataGridView em uma das minhas duas canvass

Eu realmente resolvi isso, mas estou postando para a posteridade.

Eu me deparei com um problema muito estranho com o DataGridView no meu sistema de monitor duplo. O problema se manifesta como uma repetição EXTREMAMENTE lenta do controle ( como 30 segundos para uma repintura completa ), mas apenas quando está em uma das minhas canvass. Quando, por outro lado, a velocidade de repintura é boa.

Eu tenho uma Nvidia 8800 GT com os mais recentes drivers não-beta (175. alguma coisa). É um bug de driver? Vou deixar isso no ar, já que tenho que conviver com essa configuração particular. (Isso não acontece nas placas da ATI, embora …)

A velocidade de pintura não tem nada a ver com o conteúdo da célula, e o desenho personalizado não melhora o desempenho – mesmo quando apenas pinta um retângulo sólido.

Mais tarde, descubro que colocar um ElementHost (do namespace System.Windows.Forms.Integration) no formulário corrige o problema. Não precisa ser confundido; ele só precisa ser filho da forma em que o DataGridView também está. Pode ser redimensionado para (0, 0), desde que a propriedade Visible seja verdadeira.

Eu não quero adicionar explicitamente a dependência do .NET 3 / 3.5 ao meu aplicativo; Eu faço um método para criar esse controle em tempo de execução (se puder) usando reflection. Ele funciona, e pelo menos falha graciosamente em máquinas que não possuem a biblioteca necessária – ele volta a ser lento.

Este método também me permite aplicar para corrigir enquanto o aplicativo está sendo executado, tornando mais fácil ver o que as bibliotecas do WPF estão mudando no meu formulário (usando o Spy ++).

Depois de muita tentativa e erro, percebo que a ativação do buffer duplo no próprio controle (em oposição a apenas a forma) corrige o problema!


Portanto, você só precisa criar uma class personalizada com base no DataGridView para ativar o DoubleBuffering. É isso aí!

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

Contanto que todas as minhas instâncias da grade estejam usando essa versão personalizada, tudo está bem. Se alguma vez me deparo com uma situação causada por esta situação em que não consigo utilizar a solução de subclass (se não tiver o código), suponho que poderia tentar injetar esse controlo no formulário 🙂 ( apesar de eu É mais provável que você tente usar o reflexo para forçar a propriedade DoubleBuffered a partir do exterior para mais uma vez evitar a dependência ).

É triste que uma coisa tão simples tenha consumido muito do meu tempo …

Você só precisa criar uma class personalizada baseada em DataGridView para ativar seu DoubleBuffering. É isso aí!

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

Contanto que todas as minhas instâncias da grade estejam usando essa versão personalizada, tudo está bem. Se alguma vez me deparo com uma situação causada por esta situação em que não consigo utilizar a solução de subclass (se não tiver o código), suponho que poderia tentar injetar esse controlo no formulário 🙂 (apesar de eu É mais provável que você tente usar o reflexo para forçar a propriedade DoubleBuffered a partir do exterior para mais uma vez evitar a dependência).

É triste que uma coisa tão simples tenha consumido muito do meu tempo …

Nota: Tornar a resposta uma resposta para que a questão possa ser marcada como respondida

Aqui está algum código que define a propriedade usando reflection, sem subclasss, como sugere Benoit.

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

Para as pessoas que pesquisam como fazer isso no VB.NET, aqui está o código:

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

Adicionando a posts anteriores, para aplicativos Windows Forms, é isso que eu uso para os componentes DataGridView para torná-los rápidos. O código para a class DrawingControl está abaixo.

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

Chame DrawingControl.SetDoubleBuffered (control) após InitializeComponent () no construtor.

Chame DrawingControl.SuspendDrawing (control) antes de fazer grandes atualizações de dados.

Chame DrawingControl.ResumeDrawing (control) depois de fazer grandes atualizações de dados.

Estes dois últimos são feitos com um bloco try / finally. (ou melhor ainda, reescreva a class como IDisposable e chame SuspendDrawing() no construtor e ResumeDrawing() em 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; ///  /// 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(). ///  /// The Control on which to set DoubleBuffered to true. 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 }); } } ///  /// Suspend drawing updates for the specified control. After the control has been updated /// call DrawingControl.ResumeDrawing(Control control). ///  /// The control to suspend draw updates on. public static void SuspendDrawing(Control control) { SendMessage(control.Handle, WM_SETREDRAW, false, 0); } ///  /// Resume drawing updates for the specified control. ///  /// The control to resume draw updates on. public static void ResumeDrawing(Control control) { SendMessage(control.Handle, WM_SETREDRAW, true, 0); control.Refresh(); } } 

A resposta para isso funcionou para mim também. Eu pensei em adicionar um refinamento que eu acho que deveria ser uma prática padrão para qualquer um que implementasse a solução.

A solução funciona bem, exceto quando a interface do usuário está sendo executada como uma session de cliente na área de trabalho remota, especialmente onde a largura de banda de rede disponível é baixa. Nesse caso, o desempenho pode ser agravado pelo uso de buffer duplo. Portanto, sugiro o seguinte como uma resposta mais completa:

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

Para mais detalhes, consulte Detectando a conexão da área de trabalho remota

Eu encontrei uma solução para o problema. Vá para a guia de solução de problemas nas propriedades de exibição avançadas e verifique o controle deslizante de aceleração de hardware. Quando recebi o novo PC da empresa da TI, ele estava configurado para um tick cheio e eu não tive nenhum problema com os datagrids. Depois de atualizar o driver da placa de vídeo e configurá-lo como completo, a pintura dos controles de datagrid ficou muito lenta. Então eu redefini-lo de volta para onde estava e o problema foi embora.

Espero que este truque funcione para você também.

Só para adicionar o que fizemos para corrigir esse problema: Nós atualizamos para os drivers mais recentes da Nvidia resolveram o problema. Nenhum código teve que ser reescrito.

Para completar, o cartão era uma Nvidia Quadro NVS 290 com drivers datados de março de 2008 (v. 169). A atualização para o mais recente (v. 182 de fevereiro de 2009) melhorou significativamente os events de pintura para todos os meus controles, especialmente para o DataGridView.

Esse problema não foi visto em nenhuma placa ATI (onde ocorre o desenvolvimento).

Melhor!:

 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 

Nós experimentamos um problema semelhante usando o .NET 3.0 e DataGridView em um sistema de monitor duplo.

Nosso aplicativo exibia a grade com um plano de fundo cinza, indicando que as células não puderam ser alteradas. Ao selecionar um botão “alterar configurações”, o programa mudaria a cor de fundo das células em branco para indicar ao usuário que o texto da célula poderia ser alterado. Um botão “Cancelar” mudaria a cor de fundo das células acima mencionadas para cinza.

Como a cor do plano de fundo mudava, haveria uma cintilação, uma breve impressão de uma grade de tamanho padrão com o mesmo número de linhas e colunas. Esse problema só ocorreria no monitor primário (nunca no secundário) e não ocorreria em um único sistema de monitoramento.

O buffer duplo do controle, usando o exemplo acima, resolveu nosso problema. Nós apreciamos muito a sua ajuda.