Frage

Wenn ein Benutzer alle Elemente in einem .NET 2.0 Listview wählt, wird die Listview-Feuer ein SelectedIndexChanged Ereignis für jeden Artikel, anstatt ein Ereignis feuern, um anzuzeigen, dass die Auswahl hat sich geändert.

Wenn der Benutzer klickt dann nur ein Element in der Liste auszuwählen, die Listview feuert ein SelectedIndexChanged Event für alle Element, das nicht ausgewählte wird immer, und dann ein < strong> SelectedIndexChanged Ereignis für die einzelnen Artikel neu ausgewählt, anstatt ein Ereignis feuern, um anzuzeigen, dass die Auswahl geändert hat.

Wenn Sie Code in der SelectedIndexChanged Event-Handler, wird das Programm wird ziemlich reagiert, wenn Sie beginnen in der Liste ein paar hundert / tausend Artikel haben.

Ich habe darüber nachgedacht, dwell Timer , etc.

Aber hat jemand eine gute Lösung hat, um Tausende von unnötigen Listview zu vermeiden. SelectedIndexChange Ereignisse, wenn wirklich ein Ereignis tun?

War es hilfreich?

Lösung

Gute Lösung von Ian. Ich nahm das und machte es in eine wiederverwendbare Klasse, um sicherzustellen, ordnungsgemäß des Timers zu entsorgen. Ich reduziert auch das Intervall eine reaktions App zu bekommen. Diese Steuerung auch doublebuffers Flimmern zu reduzieren.

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

Lassen Sie mich wissen, ob dies verbessert werden kann.

Andere Tipps

Dies ist die Verweilzeit Timer Lösung i jetzt bin mit (wohnen bedeutet nur „für ein wenig warten“). Dieser Code kann von einer Race-Bedingung leiden, und vielleicht ein NULL-Verweis Ausnahme.

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
}

Der Timer ist die beste Gesamtlösung.

Ein Problem mit Vorschlag Jens ist, dass, sobald die Liste eine Menge ausgewählter Elemente (in Tausend oder mehr) hat, die Liste der ausgewählten Elemente immer beginnt eine lange Zeit in Anspruch nehmen.

Stattdessen wird ein Timer-Objekt jedes Mal ein Ereignis SelectedIndexChanged schaffen auftritt, ist es einfacher, nur eine permanente mit dem Designer auf dem Formular zu setzen, und haben sie eine boolean Variable in der Klasse zu überprüfen, um zu sehen, ob oder nicht, sollten sie das nennen Aktualisierungsfunktion.

Zum Beispiel:

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

Das funktioniert gut, wenn Sie die Informationen einfach für die Anzeige verwenden, wie beispielsweise eine Statusleiste zu aktualisieren, zu sagen „X aus Y ausgewählt“.

Ein Flag arbeitet für das OnLoad-Ereignis der Windows-Form / Webformular / mobilen Form. In einer einzigen Auswahllistenansicht, nicht Multi-Select, ist der folgende Code einfach zu implementieren, und mehrere Abfeuern der Veranstaltung verhindert.

Wie der Listview das erste Element de-wählt, das zweite Element das, was Sie brauchen, und die Sammlung sollte immer nur ein Element enthalten.

Die gleiche unten in einer mobilen Anwendung verwendet wurde, daher könnten einige der Sammlungsnamen unterschiedlich sein, da es die Compact Framework verwendet, aber die gleichen Grundsätze gelten.

Hinweis: Stellen Sie sicher, dass OnLoad und bevöl der Listenansicht Sie das erste Element gesetzt ausgewählt werden

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

Idealerweise sollte dieser Code in ein Usercontrol für einfache Wiederverwendung und distrbution in einem einzigen Auswahllistview gesetzt werden. Dieser Code würde nicht viel Verwendung in einer Mehrfachauswahl, da das Ereignis funktioniert wie es sollte für dieses Verhalten.

Ich hoffe, das hilft.

Mit freundlichen Grüßen

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

Alte Frage, die ich weiß, aber dies scheint immer noch ein Problem zu sein.

Hier ist meine Lösung nicht Timer verwendet wird.

Es wartet auf die MouseUp oder KeyUp-Ereignis vor dem Ereignis Selection Brennen. Wenn Sie die Auswahl programmatisch ändern, dann wird dies nicht funktioniert, wird das Ereignis ausgelöst, aber man konnte ein FinishedChanging Ereignis oder etwas auszulösen, das Ereignis leicht hinzufügen.

(Es hat auch ein paar Sachen Flimmern zu stoppen, die auf diese Frage nicht relevant ist).

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

Sie können mit 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;
}

würde ich entweder versuchen, die Postbacks auf eine Schaltfläche zu binden, damit der Benutzer, um ihre Änderungen zu übernehmen und den Event-Handler aushängen.

Ich habe nur versucht, genau dieses Problem gestern in Angriff zu nehmen. Ich weiß nicht genau, was Sie von „wohnen“ Timer bedeuten, aber ich versuchte, meine eigene Version des Wartens Umsetzung, bis alle Änderungen erfolgt sind. Leider der einzige Weg, ich denke, könnte dies in einem separaten Thread zu tun war, und es stellt sich heraus, dass, wenn Sie einen separaten Thread erstellen, Ihre UI-Elemente in diesem Thread nicht zugänglich sind. .NET löst eine Ausnahme besagt, dass die UI-Elemente können nur in dem Thread zugegriffen werden, wo die Elemente erstellt wurden! So fand ich einen Weg, um meine Antwort auf die SelectedIndexChanged zu optimieren und schnell genug zu machen, wo es ist erträglich - es ist nicht eine skalierbare Lösung though. Hoffen wir, dass jemand eine clevere Idee, dieses Problem in einem einzigen Thread zu bekämpfen.

Vielleicht kann dies helfen Sie zu erreichen, was Sie brauchen, ohne Timer mit:

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

Ich weiß nicht wie die Benutzer von Timern ect. Wie ich auch in der Post angeben ...

Hoffe, es hilft ...

Ohh ich vergaß zu sagen, es ist .NET 3.5, und ich bin einige der Features in Linq mit „Selection Änderungen Evaluation“ zu erledigenden, wenn Sie es nennen können ò.ó ...

Wie auch immer, wenn Sie auf einer älteren Version sind, hat diese Bewertung mit ein bisschen mehr Code getan werden ...>. <...

Ich empfehle virtualisieren Ihre Listenansicht, wenn es ein paar hundert oder tausend Artikel hat.

Maylon >>>

Das Ziel war nie mit Liste über ein paar hundert Artikel zu arbeiten, aber ... Ich habe die allgemeine Benutzerfreundlichkeit mit 10.000 Produkten getestet und Auswahl von 1000-5000 Einzelteilen auf einmal (und Änderungen von 1000-3000 Artikeln sowohl in An- und Abwahl) ...

Die Gesamtdauer der Berechnung überschritten nie 0,1 sec, einige der höchsten Messungen war der 0.04sec, fand ich, dass durchaus akzeptabel mit, dass viele Elemente.

Und bei 10.000 Artikeln, nur die Liste der Initialisierung 10 Sekunden übernimmt, an dieser Stelle, damit ich andere Dinge in gekommen gedacht hätte, wie Virtualisierung zu spielen, wie Joe Chung weist darauf hin.

Das heißt, sollte es klar sein, dass der Code ist keine optimale Lösung, wie sie den Unterschied in der Auswahl berechnet, benötigt, wenn dies kann viel verbessert werden und auf verschiedene Weise, ich konzentrierte sich auf das Verständnis des Konzepts mit der Code statt der Leistung.

Wenn Sie jedoch verminderte Leistung erleben die ich bin sehr daran interessiert, in einigen folgenden:

  • Wie viele Elemente in der Liste?
  • Wie viele An- / Abwahl Elemente zu einer Zeit?
  • Wie lange dauert es etwa für den Fall, nehmen zu erhöhen?
  • Hardware-Plattform?
  • Mehr zum Fall der Verwendung?
  • Weitere relevante Informationen können Sie sich vorstellen?

Ansonsten ist es nicht leicht, zu helfen, die Lösung zu verbessern.

Lassen Sie die ListView und alle alten Kontrollen.

Machen Sie DataGridView Ihr Freund, und alles wird gut:)

Raymond Chen hat eine Blog-Post, dass (wahrscheinlich) erklärt warum gibt es tausende von Änderungsereignissen ist , anstatt nur ein:

  

Warum gibt es eine LVN_ODSTATECHANGED Benachrichtigung, wenn bereits ein ganz gute LVN_ITEMCHANGED Meldung?

     

...
  Die LVN_ODSTATECHANGED Benachrichtigung   Ihnen sagt, dass der Zustand aller Elemente   im angegebenen Bereich hat sich geändert.   Es ist eine Abkürzung für das Senden einer   individuelle LVN_ITEMCHANGED für alle   Artikel im Bereich [iFrom..iTo]. Wenn   Sie haben eine Listenansicht mit Ownerdata   500.000 Artikel und jemand tut ein   Wählen Sie alles, werden Sie froh, dass Sie   erhalten einen einzigen LVN_ODSTATECHANGED   Benachrichtigung mit iFrom=0 und   iTo=499999 statt einer halben Million   Einzel wenig LVN_ITEMCHANGED   Benachrichtigungen.

ich sage wahrscheinlich erklärt, warum, denn es gibt keine Garantie, dass die .NET-Listenansicht ist ein Wrapper um die Listenansicht Common Control - das ist eine Implementierung Detail, die jederzeit frei zu ändern ist (obwohl fast sicherlich nie werden).

Die angedeutete Lösung ist, die .NET Listview im virtuellen Modus zu verwenden, die eine Steuerung, die eine Größenordnung schwieriger zu verwenden.

ich kann eine bessere Lösung haben.

Meine Situation:

  • Single Auswahllistenansicht (statt Multi-Select)
  • Ich möchte die Verarbeitung des Ereignisses zu vermeiden, wenn es für die Abwahl des zuvor ausgewählten Elements abfeuert.

Meine Lösung:

  • Datensatz welche Artikel der Benutzer auf MouseDown- geklickt
  • Ignorieren Sie das SelectedIndexChanged-Ereignis, wenn dieser Punkt nicht null ist und 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();

}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top