Question

Si un utilisateur sélectionne tous les éléments d'un ListView .NET 2.0, celui-ci déclenchera un événement SelectedIndexChanged pour chaque élément, plutôt que de déclencher un événement pour indiquer que la sélection a changé.

Si l'utilisateur clique alors pour sélectionner un seul élément de la liste, ListView déclenche un événement SelectedIndexChanged pour tous les éléments non désélectionnés, puis << strong> SelectedIndexChanged pour le seul élément nouvellement sélectionné, plutôt que de déclencher un événement pour indiquer que la sélection a été modifiée.

Si vous avez du code dans le gestionnaire d'événements SelectedIndexChanged , le programme ne répondra plus du tout lorsque vous commencerez à avoir quelques centaines de milliers d'éléments dans la liste.

J'ai pensé aux minuteries , etc.

Mais quelqu'un a-t-il une bonne solution pour éviter des milliers d'événements ListView inutiles? SelectedIndexChange , alors qu'un événement suffirait?

Était-ce utile?

La solution

Bonne solution de Ian. J'ai pris cela et en ai fait une classe réutilisable, en veillant à bien disposer de la minuterie. J'ai également réduit l'intervalle pour obtenir une application plus réactive. Ce contrôle double également pour réduire le scintillement.

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

Faites-moi savoir si cela peut être amélioré.

Autres conseils

C’est la solution de temporisation que j’utilise pour l’instant (cela signifie simplement que & "attendez un peu &";). Ce code peut souffrir d'une condition de concurrence critique et peut-être d'une exception de référence nulle.

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
}

Le minuteur est la meilleure solution globale.

Un problème avec la suggestion de Jens est qu’une fois que la liste contient un grand nombre d’éléments sélectionnés (des milliers ou plus), l’obtention de la liste des éléments sélectionnés commence à prendre beaucoup de temps.

Au lieu de créer un objet timer chaque fois qu'un événement SelectedIndexChanged se produit, il est plus simple d'en placer un permanent dans le formulaire avec le concepteur et de le faire vérifier une variable booléenne dans la classe pour voir si elle doit appeler ou non fonction de mise à jour.

Par exemple:

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

Cela fonctionne très bien si vous utilisez les informations uniquement à des fins d'affichage, telles que la mise à jour d'une barre d'état pour indiquer & "X sur Y sélectionné &";.

.

Un indicateur fonctionne pour l'événement OnLoad du formulaire Windows / formulaire Web / formulaire mobile. Dans une sélection unique, dans la liste, et non dans la sélection multiple, le code suivant est simple à implémenter et empêche le déclenchement de plusieurs événements.

Lorsque ListView désélectionne le premier élément, le second élément répond à vos besoins et la collection ne doit contenir qu'un seul élément.

La même chose que ci-dessous a été utilisée dans une application mobile. Par conséquent, certains noms de collection peuvent être différents car ils utilisent le cadre compact, mais les mêmes principes s'appliquent.

Remarque: assurez-vous que le chargement du premier élément à sélectionner est effectué dans la liste et dans la liste.

// ################ 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    ################

Idéalement, ce code devrait être placé dans un UserControl pour une réutilisation et une distribution faciles dans un seul contrôle ListView. Ce code ne serait pas très utile dans une sélection multiple, car l'événement fonctionne comme il se doit pour ce comportement.

J'espère que cela aide.

Cordialement,

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

Vieille question que je connais, mais cela semble toujours être un problème.

Voici ma solution n'utilisant pas de minuteries.

Il attend l'événement MouseUp ou KeyUp avant de déclencher l'événement SelectionChanged. Si vous modifiez la sélection par programme, cela ne fonctionnera pas, l'événement ne se déclenchera pas, mais vous pourrez facilement ajouter un événement FinishedChanging ou quelque chose qui déclenchera l'événement.

(Il contient également des éléments pour arrêter le scintillement qui ne sont pas pertinents pour cette question).

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

Vous pouvez utiliser async & amp; 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;
}

Je voudrais soit attacher la publication à un bouton pour permettre à l'utilisateur de soumettre ses modifications et décrocher le gestionnaire d'événements.

J'essayais juste de régler ce problème hier. Je ne sais pas exactement ce que vous entendez par & "Habiter &"; timers, mais j’ai essayé d’implémenter ma propre version d’attendre que tous les changements soient apportés. Malheureusement, la seule façon dont je pouvais penser à faire cela était dans un thread séparé et il s'avère que lorsque vous créez un thread séparé, vos éléments d'interface utilisateur sont inaccessibles dans ce thread. .NET lève une exception indiquant que les éléments de l'interface utilisateur ne sont accessibles que dans le fil où ils ont été créés! J'ai donc trouvé un moyen d'optimiser ma réponse à SelectedIndexChanged et de la rendre assez rapide là où elle est supportable - ce n'est cependant pas une solution évolutive. Espérons que quelqu'un aura une idée intelligente pour s'attaquer à ce problème en un seul fil.

Peut-être que cela peut vous aider à accomplir ce dont vous avez besoin sans utiliser de minuteur:

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

Je n'aime pas l'utilisateur de minuteries ect. Comme je le dis aussi dans le post ...

J'espère que ça aide…

Ohh, j'ai oublié de dire qu'il s'agit de .NET 3.5 et que j'utilise certaines des fonctionnalités de linq pour mener à bien & "Evaluation des modifications de la sélection &"; si vous pouvez appeler ça comme ça ...

Quoi qu'il en soit, si vous utilisez une version plus ancienne, cette évaluation doit être effectuée avec un peu plus de code ... >. < ...

Je recommande de virtualiser l'affichage de votre liste s'il contient quelques centaines ou milliers d'éléments.

Maylon > > >

Le but n'était jamais de travailler avec une liste de quelques centaines d'articles, mais ... J'ai testé l'expérience utilisateur globale avec 10 000 articles et des sélections de 1 000 à 5 000 articles à la fois (et des modifications de 1 000 à 3 000 articles dans les éléments Sélectionné et Désélectionné) ...

La durée totale du calcul n’a jamais dépassé 0,1 s, certaines des mesures les plus élevées ont été de 0,04 s. J’ai trouvé que cela était parfaitement acceptable avec autant d’éléments.

Et à 10.000 éléments, l'initialisation de la liste prend plus de 10 secondes, donc à ce stade-ci, j'aurais pensé que d'autres choses étaient entrées en jeu, comme le souligne la virtualisation, comme le dit Joe Chung.

Cela étant dit, il devrait être clair que le code n'est pas une solution optimale pour calculer la différence de sélection. Si nécessaire, cela peut être beaucoup amélioré et de différentes manières, je me suis concentré sur la compréhension du concept avec le code plutôt que la performance.

Toutefois, si vos performances sont dégradées, les points suivants m'intéressent beaucoup:

  • Combien d'éléments dans la liste?
  • Combien d'éléments sélectionnés / désélectionnés à la fois?
  • Combien de temps faut-il pour que l'événement se déclenche?
  • Plate-forme matérielle?
  • En savoir plus sur le cas d'utilisation?
  • Autres informations pertinentes auxquelles vous pouvez penser?

Sinon, il n'est pas facile d'aider à améliorer la solution.

Laissez les ListView et tous les anciens contrôles.

Faites DataGridView votre ami, et tout ira bien:)

Raymond Chen publie un blog qui (probablement) explique pourquoi des milliers d'événements de changement , plutôt qu'un seul:

  

Pourquoi existe-t-il une notification LVN_ODSTATECHANGED alors qu'il y a déjà une notification parfaitement LVN_ITEMCHANGED?

     

...
  La LVN_ODSTATECHANGED notification   vous dit que l'état de tous les objets   dans la plage spécifiée a changé.   C'est un raccourci pour l'envoi d'un   individuel LVN_ITEMCHANGED pour tous   articles dans la gamme [iFrom..iTo]. Si   vous avez une vue de liste de données de propriétaire avec   500 000 articles et quelqu'un fait un   sélectionner tout, vous serez heureux que vous   obtenir un seul iFrom=0   notification avec iTo=499999 et   <=> au lieu d'un demi million   petit individuel <=>   notifications.

Je dis probablement explique pourquoi, car rien ne garantit que la vue liste .NET enveloppe le contrôle commun Listview - il s'agit d'un détail d'implémentation qui peut être modifié à tout moment (bien que presque ne le fera certainement jamais.

La solution suggérée consiste à utiliser la liste de diffusion .NET en mode virtuel, ce qui rend le contrôle plus difficile à utiliser.

J'ai peut-être une meilleure solution.

Ma situation:

  • Affichage de liste à sélection unique (plutôt que de sélection multiple)
  • Je veux éviter de traiter l'événement lorsqu'il se déclenche pour désélectionner l'élément sélectionné précédemment.

Ma solution:

  • Enregistrez l'élément sur lequel l'utilisateur a cliqué sur MouseDown
  • Ignorez l'événement SelectedIndexChanged si cet élément n'est pas null et SelectedIndexes.Count == 0

Code:

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

}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top