Domanda

Se un utente seleziona tutti gli elementi in un ListView .NET 2.0, ListView attiverà a SelezionatoIndiceCambiato evento per ogni elemento, anziché attivare un evento per indicare che il file selezione è cambiato.

Se l'utente fa quindi clic per selezionare un solo elemento nell'elenco, ListView attiverà un file SelezionatoIndiceCambiato evento per ogni elemento che viene deselezionato, quindi un SelezionatoIndiceCambiato evento per il singolo elemento appena selezionato, anziché attivare un evento per indicare che la selezione è cambiata.

Se hai il codice nel file SelezionatoIndiceCambiato gestore eventi, il programma smetterà di rispondere quando inizierai ad avere alcune centinaia/migliaia di elementi nell'elenco.

Ci ho pensato timer di sosta, eccetera.

Ma qualcuno ha una buona soluzione per evitare migliaia di ListView inutili.Modifica indice selezionato eventi, quando davvero un evento andrà bene?

È stato utile?

Soluzione

Buona soluzione da Ian.L'ho preso e l'ho trasformato in una classe riutilizzabile, assicurandomi di smaltire correttamente il timer.Ho anche ridotto l'intervallo per ottenere un'app più reattiva.Questo controllo esegue anche il doppio buffer per ridurre lo sfarfallio.

  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());
     }
  }

Fammi sapere se questo può essere migliorato.

Altri suggerimenti

Questa è la soluzione del timer di permanenza che sto utilizzando per ora (sosta significa semplicemente "aspetta un po'").Questo codice potrebbe soffrire di una condizione di competizione e forse di un'eccezione di riferimento nullo.

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
}

Il timer è la migliore soluzione complessiva.

Un problema con il suggerimento di Jens è che una volta che l'elenco ha molti elementi selezionati (migliaia o più), ottenere l'elenco degli elementi selezionati inizia a richiedere molto tempo.

Invece di creare un oggetto timer ogni volta che si verifica un evento SelectedIndexChanged, è più semplice inserirne uno permanente nel modulo con il designer e fargli controllare una variabile booleana nella classe per vedere se deve chiamare o meno la funzione di aggiornamento.

Per esempio:

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();
  }
}

Funziona bene se stai utilizzando le informazioni semplicemente a scopo di visualizzazione, ad esempio aggiornando una barra di stato per dire "X su Y selezionato".

Un flag funziona per l'evento OnLoad del modulo Windows/modulo Web/modulo mobile.In una visualizzazione Listview a selezione singola, non a selezione multipla, il codice seguente è semplice da implementare e impedisce l'attivazione multipla dell'evento.

Poiché ListView deseleziona il primo elemento, il secondo elemento è ciò di cui hai bisogno e la raccolta dovrebbe contenere sempre e solo un elemento.

Lo stesso di seguito è stato utilizzato in un'applicazione mobile, pertanto alcuni nomi di raccolta potrebbero essere diversi poiché si utilizza il framework compatto, tuttavia si applicano gli stessi principi.

Nota:Assicurati che OnLoad e popola la visualizzazione elenco imposti il ​​primo elemento da selezionare.

// ################ 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, questo codice dovrebbe essere inserito in un UserControl per un facile riutilizzo e distribuzione in un singolo ListView selezionato.Questo codice non sarebbe molto utile in una selezione multipla, poiché l'evento funziona come dovrebbe per quel comportamento.

Spero che aiuti.

Cordiali saluti,

Antonio N.Urwinhttp://www.manatix.com

Vecchia domanda, lo so, ma questo sembra essere ancora un problema.

Ecco la mia soluzione senza utilizzare i timer.

Attende l'evento MouseUp o KeyUp prima di attivare l'evento SelectionChanged.Se stai modificando la selezione in modo programmatico, questo non funzionerà, l'evento non si attiverà, ma potresti facilmente aggiungere un evento FinishedChanging o qualcosa per attivare l'evento.

(Ha anche alcune cose per fermare lo sfarfallio che non sono rilevanti per questa domanda).

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);
    }
}

Puoi usare 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;
}

Proverei a collegare il postback a un pulsante per consentire all'utente di inviare le proprie modifiche e sganciare il gestore eventi.

Stavo proprio cercando di affrontare proprio questo problema ieri.Non so esattamente cosa intendi per timer di "sosta", ma ho provato a implementare la mia versione personale di attesa fino al completamento di tutte le modifiche.Sfortunatamente l'unico modo in cui mi veniva in mente per farlo era in un thread separato e si scopre che quando crei un thread separato, i tuoi elementi dell'interfaccia utente sono inaccessibili in quel thread..NET genera un'eccezione in cui si afferma che è possibile accedere agli elementi dell'interfaccia utente solo nel thread in cui sono stati creati gli elementi!Quindi, ho trovato un modo per ottimizzare la mia risposta a SelectedIndexChanged e renderla abbastanza veloce da raggiungere il punto in cui è sopportabile, tuttavia non è una soluzione scalabile.Speriamo che qualcuno abbia un'idea intelligente per affrontare questo problema in un unico thread.

Forse questo può aiutarti a realizzare ciò di cui hai bisogno senza utilizzare i timer:

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

Non mi piace l'utilizzo dei timer ecc.Come scrivo anche nel post...

Spero che sia d'aiuto...

Ohh, ho dimenticato di dirlo, è .NET 3.5 e sto utilizzando alcune delle funzionalità di linq per realizzare la "Valutazione delle modifiche di selezione", se così si può chiamare o.O...

Ad ogni modo, se utilizzi una versione precedente, questa valutazione deve essere eseguita con un po' più di codice...>.<...

Ti consiglio di virtualizzare la visualizzazione elenco se contiene poche centinaia o migliaia di elementi.

Maylon >>>

Lo scopo non è mai stato quello di lavorare con elenchi superiori a poche centinaia di elementi, ma...Ho testato l'esperienza utente complessiva con 10.000 elementi e selezioni di 1.000-5.000 elementi contemporaneamente (e modifiche di 1.000-3.000 elementi sia in Selezionato che in Deselezionato)...

La durata complessiva del calcolo non ha mai superato 0,1 secondi, alcune delle misurazioni più elevate sono state di 0,04 secondi, l'ho trovato perfettamente accettabile con così tanti elementi.

E con 10.000 elementi, la sola inizializzazione dell'elenco richiede più di 10 secondi, quindi a questo punto avrei pensato che fossero entrate in gioco altre cose, come sottolinea Virtualization come Joe Chung.

Detto questo, dovrebbe essere chiaro che il codice non è una soluzione ottimale nel modo in cui calcola la differenza nella selezione, se necessario questo può essere migliorato molto e in vari modi, mi sono concentrato sulla comprensione del concetto con il codice piuttosto rispetto alla prestazione.

Tuttavia, se riscontri prestazioni ridotte, sono molto interessato ad alcuni dei seguenti:

  • Quanti elementi nell'elenco?
  • Quanti elementi selezionati/deselezionati alla volta?
  • Quanto tempo ci vuole all'incirca perché l'evento venga rilanciato?
  • Piattaforma hardware?
  • Maggiori informazioni sul caso d'uso?
  • Altre informazioni rilevanti che ti vengono in mente?

Altrimenti non è facile contribuire a migliorare la soluzione.

Lasciare il ListView e tutti i vecchi controlli.

Fare DataGridView tuo amico, e tutto andrà bene :)

Raymond Chen ha un post sul blog che (probabilmente) spiega Perché ci sono migliaia di eventi di cambiamento, piuttosto che uno solo:

Perché è presente una notifica LVN_ODSTATECHANGED quando esiste già una notifica LVN_ITEMCHANGED perfettamente valida?

...
IL LVN_ODSTATECHANGED La notifica ti dice che lo stato di tutti gli articoli nell'intervallo specificato è cambiato.È una scorciatoia per l'invio di un individuo LVN_ITEMCHANGED Per tutti gli articoli nella gamma [iFrom..iTo].Se hai una visualizzazione elenco di proprietari con 500.000 articoli e qualcuno fa un selezione per tutti, sarai contento di ottenere un singolo LVN_ODSTATECHANGEDnotifica con iFrom=0 E iTo=499999 invece di mezzo milione di singoli piccoli LVN_ITEMCHANGEDnotifiche.

dico probabilmente spiega il motivo, poiché non vi è alcuna garanzia che la visualizzazione elenco .NET sia un wrapper attorno al controllo comune Listview: si tratta di un dettaglio di implementazione che è libero di modificare in qualsiasi momento (anche se quasi certamente non lo farà mai).

La soluzione suggerita consiste nell'usare la listview .NET in modalità virtuale, rendendo il controllo molto più difficile da usare.

Potrei avere una soluzione migliore.

La mia situazione:

  • Visualizzazione elenco a selezione singola (anziché selezione multipla)
  • Voglio evitare di elaborare l'evento quando si attiva per la deselezione dell'elemento precedentemente selezionato.

La mia soluzione:

  • Registra l'elemento su cui l'utente ha fatto clic su MouseDown
  • Ignora l'evento SelectedIndexChanged se questo elemento non è null e SelectedIndexes.Count == 0

Codice:

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();

}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top