Pergunta

Se um usuário selecionar todos os itens em um ListView .NET 2.0, o ListView dispara um SelectedIndexChanged evento para cada item, em vez de disparar um evento para indicar que o seleção mudou.

Se o usuário clica para selecionar apenas um item na lista, o ListView dispara um SelectedIndexChanged evento para todas item que está ficando desmarcada, e em seguida, um < strong> SelectedIndexChanged evento para o único item recém-selecionado, em vez de disparar um evento para indicar que a seleção mudou.

Se você tem o código na SelectedIndexChanged manipulador de eventos, o programa vai se tornar muito de responder quando você começa a ter algumas centenas / milhares de itens na lista.

Já pensou em habitam temporizadores , etc.

Mas alguém tem uma solução boa para milhares Evitar de ListView desnecessária. SelectedIndexChange eventos, quando na verdade um evento irá fazer?

Foi útil?

Solução

Boa solução de Ian. Eu levei isso e é feita em uma classe reutilizável, certificando-se de dispor do temporizador corretamente. Eu também reduziu o intervalo para obter um aplicativo mais responsivo. Este controle também doublebuffers reduzir a cintilação.

  public class DoublebufferedListView : System.Windows.Forms.ListView
  {
     private Timer m_changeDelayTimer = null;
     public DoublebufferedListView()
        : base()
     {
        // Set common properties for our listviews
        if (!SystemInformation.TerminalServerSession)
        {
           DoubleBuffered = true;
           SetStyle(ControlStyles.ResizeRedraw, true);
        }
     }

     /// <summary>
     /// Make sure to properly dispose of the timer
     /// </summary>
     /// <param name="disposing"></param>
     protected override void Dispose(bool disposing)
     {
        if (disposing && m_changeDelayTimer != null)
        {
           m_changeDelayTimer.Tick -= ChangeDelayTimerTick;
           m_changeDelayTimer.Dispose();
        }
        base.Dispose(disposing);
     }

     /// <summary>
     /// Hack to avoid lots of unnecessary change events by marshaling with a timer:
     /// http://stackoverflow.com/questions/86793/how-to-avoid-thousands-of-needless-listview-selectedindexchanged-events
     /// </summary>
     /// <param name="e"></param>
     protected override void OnSelectedIndexChanged(EventArgs e)
     {
        if (m_changeDelayTimer == null)
        {
           m_changeDelayTimer = new Timer();
           m_changeDelayTimer.Tick += ChangeDelayTimerTick;
           m_changeDelayTimer.Interval = 40;
        }
        // When a new SelectedIndexChanged event arrives, disable, then enable the
        // timer, effectively resetting it, so that after the last one in a batch
        // arrives, there is at least 40 ms before we react, plenty of time 
        // to wait any other selection events in the same batch.
        m_changeDelayTimer.Enabled = false;
        m_changeDelayTimer.Enabled = true;
     }

     private void ChangeDelayTimerTick(object sender, EventArgs e)
     {
        m_changeDelayTimer.Enabled = false;
        base.OnSelectedIndexChanged(new EventArgs());
     }
  }

Do deixe-me saber se isso pode ser melhorado.

Outras dicas

Esta é a solução temporizador habitam eu estou usando agora (habitam apenas significa "esperar por um pouco"). Este código pode sofrer de uma condição de corrida, e talvez uma exceção de referência nula.

Timer changeDelayTimer = null;

private void lvResults_SelectedIndexChanged(object sender, EventArgs e)
{
        if (this.changeDelayTimer == null)
        {
            this.changeDelayTimer = new Timer();
            this.changeDelayTimer.Tick += ChangeDelayTimerTick;
            this.changeDelayTimer.Interval = 200; //200ms is what Explorer uses
        }
        this.changeDelayTimer.Enabled = false;
        this.changeDelayTimer.Enabled = true;
}

private void ChangeDelayTimerTick(object sender, EventArgs e)
{
    this.changeDelayTimer.Enabled = false;
    this.changeDelayTimer.Dispose();
    this.changeDelayTimer = null;

    //Add original SelectedIndexChanged event handler code here
    //todo
}

O temporizador é a melhor solução global.

Um problema com a sugestão de Jens é que uma vez que a lista tem um monte de itens selecionados (milhares ou mais), recebendo a lista de itens começa selecionados para levar um longo tempo.

Em vez de criar um objeto temporizador cada vez que um evento SelectedIndexChanged ocorre, é mais simples apenas para colocar um permanente no formulário com o designer, e tê-lo verificar uma variável booleana na classe para ver se é ou não deve chamar o atualização função.

Por exemplo:

bool timer_event_should_call_update_controls = false;

private void lvwMyListView_SelectedIndexChanged(object sender, EventArgs e) {

  timer_event_should_call_update_controls = true;
}

private void UpdateControlsTimer_Tick(object sender, EventArgs e) {

  if (timer_event_should_call_update_controls) {
    timer_event_should_call_update_controls = false;

    update_controls();
  }
}

Esta multa funciona se você estiver usando as informações simplesmente para fins de exibição, como atualizar uma barra de status para dizer "X de Y selecionado".

bandeira funciona para o evento OnLoad do formulário do Windows / formulário web / form móvel. Em um único Listview select, não multi-escolha, o seguinte código é simples de implementar, e impede que vários acionamento do evento.

Como o primeiro item ListView de-seleciona, o segundo item que o que você precisa ea coleta só deve conter um item.

O mesmo abaixo foi usado em uma aplicação móvel, portanto, alguns dos nomes de coleção pode ser diferente, uma vez que está usando a estrutura compacta, porém os mesmos princípios se aplicam.

Nota:. Certifique-se de OnLoad e preencher da listview você define o primeiro item a ser selecionado

// ################ CODE STARTS HERE ################
//Flag  to create at the form level
System.Boolean lsvLoadFlag = true;

//Make sure to set the flag to true at the begin of the form load and after
private void frmMain_Load(object sender, EventArgs e)
{
    //Prevent the listview from firing crazy in a single click NOT multislect environment
    lsvLoadFlag = true;

    //DO SOME CODE....

    //Enable the listview to process events
    lsvLoadFlag = false;
}

//Populate First then this line of code
lsvMain.Items[0].Selected = true;

//SelectedIndexChanged Event
 private void lsvMain_SelectedIndexChanged(object sender, EventArgs e)
{
    ListViewItem lvi = null;

    if (!lsvLoadFlag)
    {
        if (this.lsvMain.SelectedIndices != null)
        {
            if (this.lsvMain.SelectedIndices.Count == 1)
            {
                lvi = this.lsvMain.Items[this.lsvMain.SelectedIndices[0]];
            }
        }
    }
}
################ CODE END HERE    ################

Idealmente, este código deve ser colocado em um UserControl para fácil re-utilização e distrbution em um único ListView select. Este código não seria muito uso em um multi-selecionar, como o evento funciona como deveria para esse comportamento.

Espero que ajuda.

Atenciosamente,

Anthony N. Urwin http://www.manatix.com

questão velho eu sei, mas isso ainda parece ser um problema.

Aqui está a minha solução não usar temporizadores.

Ele aguarda o evento MouseUp ou KeyUp antes de disparar o evento SelectionChanged. Se você estiver alterando a seleção de programação, então isso não vai funcionar, o evento não fogo, mas você pode facilmente adicionar um evento FinishedChanging ou algo para disparar o evento.

(Ele também tem algumas coisas para cintilação parada que não é relevante para esta questão).

public class ListViewNF : ListView
{
    bool SelectedIndexChanging = false;

    public ListViewNF()
    {
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
        this.SetStyle(ControlStyles.EnableNotifyMessage, true);
    }

    protected override void OnNotifyMessage(Message m)
    {
        if(m.Msg != 0x14)
            base.OnNotifyMessage(m);
    }

    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        SelectedIndexChanging = true;
        //base.OnSelectedIndexChanged(e);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        if (SelectedIndexChanging)
        {
            base.OnSelectedIndexChanged(EventArgs.Empty);
            SelectedIndexChanging = false;
        }

        base.OnMouseUp(e);
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        if (SelectedIndexChanging)
        {
            base.OnSelectedIndexChanged(EventArgs.Empty);
            SelectedIndexChanging = false;
        }

        base.OnKeyUp(e);
    }
}

Você pode usar async & await:

private bool waitForUpdateControls = false;

private async void listView_SelectedIndexChanged(object sender, EventArgs e)
{
    // To avoid thousands of needless ListView.SelectedIndexChanged events.

    if (waitForUpdateControls)
    {
        return;
    }

    waitForUpdateControls = true;

    await Task.Delay(100);

    waitForUpdateControls = false;

    UpdateControls();

    return;
}

Eu quer tentar amarrar a postagem de um botão para permitir ao usuário a apresentarem as suas alterações e desenganchar o manipulador de eventos.

Eu estava apenas tentando resolver este problema muito ontem. Eu não sei exatamente o que você quer dizer com "permanência" timers, mas eu tentei implementar a minha própria versão de esperar até que todas as alterações são feitas. Infelizmente a única maneira que eu poderia pensar em fazer isso foi em um segmento separado e verifica-se que quando você cria um segmento separado, os seus elementos de interface do usuário são inacessíveis nesse segmento. NET lança uma exceção afirmando que os elementos de interface do usuário só pode ser acessado no segmento onde os elementos foram criados! Então, eu encontrei uma maneira de otimizar a minha resposta à SelectedIndexChanged e torná-lo rápido o suficiente para onde é suportável - não é uma solução escalável embora. Vamos esperar que alguém tem uma idéia inteligente para resolver este problema em um único segmento.

Talvez isso pode ajudá-lo a realizar o que você precisa sem o uso de temporizadores:

http://www.dotjem.com/archive/2009 /06/19/20.aspx

Eu não gosto do usuário de temporizadores ect. Como eu também estado no post ...

Hope isso ajuda ...

Ohh eu esqueci de dizer, é .NET 3.5, e eu estou usando algumas das características em LINQ to acomplish "Alterações Evaluation Selection" se você pode chamar assim o.o ...

De qualquer forma, se você estiver em uma versão mais antiga, esta avaliação tem de ser feito com um pouco mais de código ...>. <...

Eu recomendo virtualização lista de seu ponto de vista, se tiver algumas centenas ou milhares de itens.

Maylon >>>

O objetivo nunca foi trabalhar com lista acima umas centenas alguns itens, mas ... Eu testei a experiência geral do usuário com 10.000 itens e seleções de 1000-5000 itens de uma só vez (e mudanças de 1000-3000 itens em ambos ativada e desativada) ...

A duração total de não calcular excedeu 0,1 seg, algumas das mais altas medições foi de 0.04sec, descobri que perfeitamente aceitável com que muitos itens.

E a 10.000 itens, basta inicializar a lista tem mais de 10 segundos, por isso neste momento eu teria pensado outras coisas tinha vir a desempenhar, como a virtualização como Joe Chung ressalta.

Dito isto, deve ficar claro que o código não é uma solução ideal em como ele calcula a diferença na seleção, se necessário isso pode ser melhorado muito e de várias maneiras, eu me concentrei na compreensão do conceito com o código em vez do desempenho.

No entanto, se o seu experimentando degradação do desempenho que eu estou muito interessado em alguns dos seguintes:

  • Como muitos itens na lista?
  • Quantas selecionado / elementos desmarcadas em um momento?
  • Quanto tempo leva cerca de levar para o evento de aumento?
  • Plataforma de hardware?
  • mais sobre o caso de uso?
  • Outras informações relevantes que você pode pensar?

Caso contrário, não é fácil de ajuda para melhorar a solução.

Deixe o ListView e todos os antigos controles.

Faça DataGridView seu amigo, e tudo ficará bem:)

Raymond Chen tem um post de blog que (provavelmente) explica por , existem milhares de eventos de alteração , ao invés de apenas um:

Porque é que existe uma notificação LVN_ODSTATECHANGED quando já um perfeitamente bom notificação LVN_ITEMCHANGED?

...
A notificação LVN_ODSTATECHANGED diz-lhe que o estado de todos os itens na gama especificada mudou. É um atalho para enviar uma LVN_ITEMCHANGED individual para todos itens na [iFrom..iTo] gama. E se você tem uma visão de lista ownerdata com 500.000 itens e alguém faz uma select-tudo, você estará contente que você obter um único LVN_ODSTATECHANGED notificação com iFrom=0 e iTo=499999 em vez de um meio milhão LVN_ITEMCHANGED pouco indivíduo notificações.

i dizer provavelmente explica por que, porque não há nenhuma garantia de que a exibição da lista NET é um invólucro em torno do controle Common Listview - que é um detalhe de implementação que é livre para mudar a qualquer momento (embora quase certamente nunca irá).

A solução sugerida é usar o listview .NET no modo virtual, fazendo com que o controle de uma ordem de magnitude mais difícil de usar.

Eu posso ter uma solução melhor.

A minha situação:

  • Ver Selecione Lista única (em vez de multi-select)
  • Eu quero evitar o processamento do evento quando ele é acionado para desativação do item selecionado anteriormente.

A minha solução:

  • Gravar o item que o usuário clicou MouseDown
  • Ignorar o evento SelectedIndexChanged se este item não é nula e SelectedIndexes.Count == 0

Código:

ListViewItem ItemOnMouseDown = null;
private void lvTransactions_MouseDown(object sender, MouseEventArgs e)
{
    ItemOnMouseDown = lvTransactions.GetItemAt(e.X, e.Y);
}
private void lvTransactions_SelectedIndexChanged(object sender, EventArgs e)
{
    if (ItemOnMouseDown != null && lvTransactions.SelectedIndices.Count == 0)
        return;

    SelectedIndexDidReallyChange();

}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top