Pregunta

Si un usuario selecciona todos los elementos en un ListView .NET 2.0, ListView activará un Índice seleccionadoCambiado evento para cada elemento, en lugar de activar un evento para indicar que el selección ha cambiado.

Si el usuario luego hace clic para seleccionar solo un elemento en la lista, ListView activará un Índice seleccionadoCambiado evento para cada elemento que se está deseleccionando y luego aparece Índice seleccionadoCambiado evento para el único elemento recién seleccionado, en lugar de activar un evento para indicar que la selección ha cambiado.

Si tienes código en el Índice seleccionadoCambiado controlador de eventos, el programa dejará de responder cuando comience a tener unos cientos o miles de elementos en la lista.

he pensado en temporizadores de permanencia, etc.

Pero, ¿alguien tiene una buena solución para evitar miles de ListView innecesarios?Cambio de índice seleccionado eventos, cuando realmente un evento ¿servirá?

¿Fue útil?

Solución

Buena solución de Ian.Lo tomé y lo convertí en una clase reutilizable, asegurándome de desechar el temporizador correctamente.También reduje el intervalo para obtener una aplicación con mayor capacidad de respuesta.Este control también duplica el buffer para reducir el parpadeo.

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

Déjame saber si esto se puede mejorar.

Otros consejos

Esta es la solución del temporizador de permanencia que estoy usando por ahora (permanecer solo significa "esperar un poco").Este código podría sufrir una condición de carrera y quizás una excepción de referencia 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
}

El temporizador es la mejor solución general.

Un problema con la sugerencia de Jens es que una vez que la lista tiene muchos elementos seleccionados (miles o más), obtener la lista de elementos seleccionados comienza a llevar mucho tiempo.

En lugar de crear un objeto de temporizador cada vez que ocurre un evento SelectedIndexChanged, es más sencillo simplemente colocar uno permanente en el formulario con el diseñador y hacer que verifique una variable booleana en la clase para ver si debe llamar o no a la función de actualización.

Por ejemplo:

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

Esto funciona bien si utiliza la información simplemente con fines de visualización, como actualizar una barra de estado para que diga "X de Y seleccionados".

Una bandera funciona para el evento OnLoad del formulario de Windows/formulario web/formulario móvil.En una vista de lista de selección única, no en una selección múltiple, el siguiente código es fácil de implementar y evita la activación múltiple del evento.

A medida que ListView anula la selección del primer elemento, el segundo elemento es lo que necesita y la colección solo debe contener un elemento.

Lo mismo a continuación se usó en una aplicación móvil, por lo tanto, algunos de los nombres de las colecciones pueden ser diferentes ya que se usa el marco compacto; sin embargo, se aplican los mismos principios.

Nota:Asegúrese de que al cargar y completar la vista de lista configuró el primer elemento a seleccionar.

// ################ 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 debería colocarse en un UserControl para facilitar su reutilización y distribución en un único ListView seleccionado.Este código no sería de mucha utilidad en una selección múltiple, ya que el evento funciona como debería para ese comportamiento.

Espero que eso ayude.

Atentamente,

antonio n.Urwinhttp://www.manatix.com

Antigua pregunta, lo sé, pero todavía parece ser un problema.

Aquí está mi solución sin usar temporizadores.

Espera el evento MouseUp o KeyUp antes de activar el evento SelectionChanged.Si está cambiando la selección mediante programación, esto no funcionará, el evento no se activará, pero podría agregar fácilmente un evento FinishedChanging o algo para activar el evento.

(También tiene algunas cosas para dejar de parpadear que no son relevantes para esta pregunta).

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

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

Intentaría vincular la devolución de datos a un botón para permitir al usuario enviar sus cambios y desconectar el controlador de eventos.

Ayer estaba tratando de abordar este mismo problema.No sé exactamente a qué te refieres con temporizadores de "permanencia", pero intenté implementar mi propia versión de esperar hasta que se realicen todos los cambios.Desafortunadamente, la única forma que se me ocurrió de hacer esto fue en un hilo separado y resulta que cuando creas un hilo separado, los elementos de tu interfaz de usuario son inaccesibles en ese hilo..NET genera una excepción que indica que solo se puede acceder a los elementos de la interfaz de usuario en el hilo donde se crearon los elementos.Entonces, encontré una manera de optimizar mi respuesta a SelectedIndexChanged y hacerla lo suficientemente rápida como para que sea soportable; aunque no es una solución escalable.Esperemos que alguien tenga una idea inteligente para abordar este problema en un solo hilo.

Quizás esto pueda ayudarte a lograr lo que necesitas sin usar temporizadores:

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

No me gusta el usuario de temporizadores, etc.Como también digo en el post...

Espero eso ayude...

Oh, olvidé decirlo, es .NET 3.5 y estoy usando algunas de las funciones de linq para realizar la "Evaluación de cambios de selección", si se le puede llamar así o.O...

De todos modos, si tienes una versión anterior, esta evaluación debe hacerse con un poco más de código...>.<...

Recomiendo virtualizar su vista de lista si tiene unos cientos o miles de elementos.

Maylón >>>

El objetivo nunca fue trabajar con una lista de más de unos pocos cientos de elementos, pero...He probado la experiencia general del usuario con 10.000 elementos y selecciones de 1.000 a 5.000 elementos a la vez (y cambios de 1.000 a 3.000 elementos tanto en Seleccionados como en Deseleccionados)...

La duración total del cálculo nunca superó los 0,1 segundos, algunas de las mediciones más altas fueron de 0,04 segundos, lo cual me pareció perfectamente aceptable con tantos elementos.

Y con 10.000 elementos, simplemente inicializar la lista lleva más de 10 segundos, por lo que en este punto habría pensado que otras cosas habían entrado en juego, como la virtualización, como señala Joe Chung.

Dicho esto, debe quedar claro que el código no es una solución óptima en cómo calcula la diferencia en la selección, si es necesario esto se puede mejorar mucho y de varias maneras, me centré en la comprensión del concepto con el código más que que el rendimiento.

Sin embargo, si experimenta un rendimiento degradado, estoy muy interesado en algunos de los siguientes:

  • ¿Cuántos elementos hay en la lista?
  • ¿Cuántos elementos seleccionados/deseleccionados a la vez?
  • ¿Cuánto tiempo tarda aproximadamente en generarse el evento?
  • ¿Plataforma de hardware?
  • Más sobre ¿El caso de uso?
  • ¿Otra información relevante que se te ocurra?

De lo contrario, no será fácil ayudar a mejorar la solución.

Deja el ListView y todos los controles antiguos.

Hacer DataGridView tu amigo, y todo estará bien :)

Raymond Chen tiene una publicación de blog que (probablemente) explica por qué hay miles de eventos de cambio, en lugar de solo uno:

¿Por qué hay una notificación LVN_ODSTATECHANGED cuando ya hay una notificación LVN_ITEMCHANGED en perfecto estado?

...
El LVN_ODSTATECHANGED La notificación le dice que el estado de todos los elementos en el rango especificado ha cambiado.Es una taquigrafía para enviar a un individuo LVN_ITEMCHANGED Para todos los artículos en la gama [iFrom..iTo].Si tiene una vista de lista de datos de propietarios con 500,000 elementos y alguien hace una selección, se alegrará de obtener un solo LVN_ODSTATECHANGEDnotificación con iFrom=0 y iTo=499999 en lugar de medio millón de individuos pequeños LVN_ITEMCHANGEDnotificaciones.

yo digo probablemente explica por qué, porque no hay garantía de que la vista de lista .NET sea un contenedor del control común de Listview; ese es un detalle de implementación que se puede cambiar en cualquier momento (aunque es casi seguro que nunca lo hará).

La solución sugerida es utilizar la vista de lista .NET en modo virtual, lo que hace que el control sea mucho más difícil de usar.

Quizás tenga una mejor solución.

Mi situación:

  • Vista de lista de selección única (en lugar de selección múltiple)
  • Quiero evitar procesar el evento cuando se activa por anular la selección del elemento seleccionado previamente.

Mi solución:

  • Registre en qué elemento hizo clic el usuario con MouseDown
  • Ignore el evento SelectedIndexChanged si este elemento no es nulo y 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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top