ti-enxame.com

Como evitar cintilação no ListView ao atualizar um único texto ListViewItem?

Tudo que eu quero é atualizar o texto de um ListViewItem sem ver qualquer cintilação.

Este é o meu código para atualização (chamado várias vezes):

listView.BeginUpdate();
listViewItem.SubItems[0].Text = state.ToString();    // update the state
listViewItem.SubItems[1].Text = progress.ToString(); // update the progress
listView.EndUpdate();

Eu vi algumas soluções que envolvem substituir a WndProc(): do componente

protected override void WndProc(ref Message m)
{
    if (m.Msg == (int)WM.WM_ERASEBKGND)
    {
        m.Msg = (int)IntPtr.Zero;
    }
    base.WndProc(ref m);
}

Dizem que resolve o problema, mas no meu caso, não . Eu acredito que isso é porque eu estou usando ícones em todos os itens.

46
Jonas

Para finalizar essa questão, aqui está uma classe auxiliar que deve ser chamada quando o formulário estiver sendo carregado para cada ListView ou qualquer outro controle derivado de ListView em seu formulário. Obrigado a "Brian Gillespie" por dar a solução.

public enum ListViewExtendedStyles
{
    /// <summary>
    /// LVS_EX_GRIDLINES
    /// </summary>
    GridLines = 0x00000001,
    /// <summary>
    /// LVS_EX_SUBITEMIMAGES
    /// </summary>
    SubItemImages = 0x00000002,
    /// <summary>
    /// LVS_EX_CHECKBOXES
    /// </summary>
    CheckBoxes = 0x00000004,
    /// <summary>
    /// LVS_EX_TRACKSELECT
    /// </summary>
    TrackSelect = 0x00000008,
    /// <summary>
    /// LVS_EX_HEADERDRAGDROP
    /// </summary>
    HeaderDragDrop = 0x00000010,
    /// <summary>
    /// LVS_EX_FULLROWSELECT
    /// </summary>
    FullRowSelect = 0x00000020,
    /// <summary>
    /// LVS_EX_ONECLICKACTIVATE
    /// </summary>
    OneClickActivate = 0x00000040,
    /// <summary>
    /// LVS_EX_TWOCLICKACTIVATE
    /// </summary>
    TwoClickActivate = 0x00000080,
    /// <summary>
    /// LVS_EX_FLATSB
    /// </summary>
    FlatsB = 0x00000100,
    /// <summary>
    /// LVS_EX_REGIONAL
    /// </summary>
    Regional = 0x00000200,
    /// <summary>
    /// LVS_EX_INFOTIP
    /// </summary>
    InfoTip = 0x00000400,
    /// <summary>
    /// LVS_EX_UNDERLINEHOT
    /// </summary>
    UnderlineHot = 0x00000800,
    /// <summary>
    /// LVS_EX_UNDERLINECOLD
    /// </summary>
    UnderlineCold = 0x00001000,
    /// <summary>
    /// LVS_EX_MULTIWORKAREAS
    /// </summary>
    MultilWorkAreas = 0x00002000,
    /// <summary>
    /// LVS_EX_LABELTIP
    /// </summary>
    LabelTip = 0x00004000,
    /// <summary>
    /// LVS_EX_BORDERSELECT
    /// </summary>
    BorderSelect = 0x00008000,
    /// <summary>
    /// LVS_EX_DOUBLEBUFFER
    /// </summary>
    DoubleBuffer = 0x00010000,
    /// <summary>
    /// LVS_EX_HIDELABELS
    /// </summary>
    HideLabels = 0x00020000,
    /// <summary>
    /// LVS_EX_SINGLEROW
    /// </summary>
    SingleRow = 0x00040000,
    /// <summary>
    /// LVS_EX_SNAPTOGRID
    /// </summary>
    SnapToGrid = 0x00080000,
    /// <summary>
    /// LVS_EX_SIMPLESELECT
    /// </summary>
    SimpleSelect = 0x00100000
}

public enum ListViewMessages
{
    First = 0x1000,
    SetExtendedStyle = (First + 54),
    GetExtendedStyle = (First + 55),
}

/// <summary>
/// Contains helper methods to change extended styles on ListView, including enabling double buffering.
/// Based on Giovanni Montrone's article on <see cref="http://www.codeproject.com/KB/list/listviewxp.aspx"/>
/// </summary>
public class ListViewHelper
{
    private ListViewHelper()
    {
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern int SendMessage(IntPtr handle, int messg, int wparam, int lparam);

    public static void SetExtendedStyle(Control control, ListViewExtendedStyles exStyle)
    {
        ListViewExtendedStyles styles;
        styles = (ListViewExtendedStyles)SendMessage(control.Handle, (int)ListViewMessages.GetExtendedStyle, 0, 0);
        styles |= exStyle;
        SendMessage(control.Handle, (int)ListViewMessages.SetExtendedStyle, 0, (int)styles);
    }

    public static void EnableDoubleBuffer(Control control)
    {
        ListViewExtendedStyles styles;
        // read current style
        styles = (ListViewExtendedStyles)SendMessage(control.Handle, (int)ListViewMessages.GetExtendedStyle, 0, 0);
        // enable double buffer and border select
        styles |= ListViewExtendedStyles.DoubleBuffer | ListViewExtendedStyles.BorderSelect;
        // write new style
        SendMessage(control.Handle, (int)ListViewMessages.SetExtendedStyle, 0, (int)styles);
    }
    public static void DisableDoubleBuffer(Control control)
    {
        ListViewExtendedStyles styles;
        // read current style
        styles = (ListViewExtendedStyles)SendMessage(control.Handle, (int)ListViewMessages.GetExtendedStyle, 0, 0);
        // disable double buffer and border select
        styles -= styles & ListViewExtendedStyles.DoubleBuffer;
        styles -= styles & ListViewExtendedStyles.BorderSelect;
        // write new style
        SendMessage(control.Handle, (int)ListViewMessages.SetExtendedStyle, 0, (int)styles);
    }
}
51
Jonas

A resposta aceita funciona, mas é bastante longa, e derivando do controle (como mencionado nas outras respostas) apenas para permitir o buffer duplo também é um pouco exagerado. Mas, felizmente, temos reflexões e também podemos chamar métodos internos se quisermos (mas tenha certeza do que você faz!).

Encapsulando esta abordagem em um método de extensão, obteremos uma classe bem curta:

public static class ControlExtensions
{
    public static void DoubleBuffering(this Control control, bool enable)
    {
        var method = typeof(Control).GetMethod("SetStyle", BindingFlags.Instance | BindingFlags.NonPublic);
        method.Invoke(control, new object[] { ControlStyles.OptimizedDoubleBuffer, enable });
    }
}

Que pode ser facilmente chamado dentro do nosso código:

InitializeComponent();

myListView.DoubleBuffering(true); //after the InitializeComponent();

E todas as oscilações se foram.

Atualizar

Eu tropecei em esta questão e devido a este fato, o método de extensão deveria (talvez) ser melhor:

public static void DoubleBuffered(this Control control, bool enable)
{
    var doubleBufferPropertyInfo = control.GetType().GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
    doubleBufferPropertyInfo.SetValue(control, enable, null);
}
58
Oliver

O ListView em CommonControls 6 (XP ou mais recente) suporta buffer duplo. Felizmente, o .NET envolve os mais novos CommonControls no sistema. Para habilitar o buffer duplo, envie a mensagem apropriada do Windows para o controle ListView.

Aqui estão os detalhes: http://www.codeproject.com/KB/list/listviewxp.aspx

11
Brian Gillespie

No .NET Winforms 2.0, existe uma propriedade protegida chamada DoubleBuffered.

Ao herdar de ListView, pode-se definir essa propriedade protegida como true. Isso permitirá o buffer duplo sem precisar chamar o SendMessage.

Definir a propriedade DoubleBuffered é o mesmo que definir o seguinte estilo:

listview.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);

http://connect.Microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=94096

10
Rolf Kristensen

Eu sei que essa pergunta é bem antiga, mas como esse é um dos primeiros resultados de pesquisa no Google, eu queria compartilhar minha correção.

A única maneira que eu poderia remover 100% de cintilação era combinar a resposta de Oliver (classe de extensão com buffer duplo) e usar os métodos BeignUpdate() e EndUpdate().

Nenhum deles por si só poderia consertar piscando para mim. Concedido, eu uso uma lista muito complexa, que eu preciso para empurrar para a lista e também precisa atualizá-lo quase todos os segundos.

2
T4cC0re

isso vai ajudar:

class DoubleBufferedListView : System.Windows.Forms.ListView
{
    public DoubleBufferedListView()
        :base()
    {
        this.DoubleBuffered = true;
    }
}
1
Bjoern

Se você quiser apenas atualizar o texto, basta definir o texto do Subitem alterado diretamente, em vez de atualizar o ListViewItem inteiro (você não disse como está fazendo suas atualizações).

A substituição mostrada é equivalente a simplesmente substituir OnPaintBackground, que seria uma maneira gerenciada "mais correta" para executar essa tarefa, e não ajudará em um único item.

Se você ainda tiver problemas, precisaremos de esclarecimentos sobre o que você realmente tentou.

0
ctacke

Este é um tiro no escuro, mas você pode tentar o buffer duplo do controle.

SetStyle(
  ControlStyles.AllPaintingInWmPaint |
  ControlStyles.UserPaint |
  ControlStyles.DoubleBuffer, true)
0
Ed S.

Chame o método BeginUpdate () no ListView antes de definir qualquer um dos itens de exibição de lista e, em seguida, chame apenas EndUpdate () após todos os itens terem sido adicionados.

Isso vai parar a cintilação.

0
Mike

Solução simples é esta:

yourlistview.BeginUpdate ()

// Faça sua atualização de adicionar e remover itens da lista

yourlistview.EndUpdate ()

0
jaiveeru