Как избежать тысяч ненужных событий ListView.SelectedIndexChanged? - PullRequest
16 голосов
/ 17 сентября 2008

Если пользователь выбирает все элементы в .NET 2.0 ListView, ListView будет запускать событие SelectedIndexChanged для каждого элемента, а не запускать событие, чтобы указать, что selection имеет измененное.

Если пользователь затем щелкает, чтобы выбрать только один элемент в списке, ListView запустит событие SelectedIndexChanged для каждого элемента, который становится невыбранным, а затем Событие SelectedIndexChanged для отдельного вновь выбранного элемента, а не событие, указывающее, что выбор изменился.

Если у вас есть код в обработчике событий SelectedIndexChanged , программа перестает отвечать на запросы, когда в списке появляется несколько сотен / тысяч элементов.

Я думал о таймерах задержки и т. Д.

Но есть ли у кого-нибудь хорошее решение, чтобы избежать тысяч ненужных ListView. SelectedIndexChange событий, когда действительно одно событие будет делать?

Ответы [ 14 ]

12 голосов
/ 11 июня 2009

Хорошее решение от Яна. Я взял это и превратил в класс многоразового использования, убедившись, что таймер правильно настроен. Я также сократил интервал, чтобы получить более отзывчивое приложение. Этот элемент управления также создает двойной буфер для уменьшения мерцания.

  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:
     /// /46019/kak-izbezhat-tysyach-nenuzhnyh-sobytii-listview-selectedindexchanged
     /// </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());
     }
  }

Дайте мне знать, если это можно улучшить.

3 голосов
/ 18 сентября 2008

Это решение с таймером задержки, которое я сейчас использую (останавливаться просто означает «немного подождать»). Этот код может страдать от состояния гонки и, возможно, исключать нулевую ссылку.

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
}
1 голос
/ 21 февраля 2017

Вы можете использовать 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;
}
1 голос
/ 15 января 2014

Старый вопрос, который я знаю, но это все еще кажется проблемой.

Вот мое решение, не использующее таймеры.

Ожидает события MouseUp или KeyUp, прежде чем запустить событие SelectionChanged. Если вы изменяете выделение программно, то это не будет работать, событие не сработает, но вы можете легко добавить событие FinishedChanging или что-то другое, чтобы вызвать событие.

(В нем также есть кое-что, чтобы остановить мерцание, которое не имеет отношения к этому вопросу).

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);
    }
}
1 голос
/ 06 сентября 2010

Флаг работает для события OnLoad формы Windows / веб-формы / мобильной формы. В одном представлении Listview, а не в множественном выборе, следующий код прост в реализации и предотвращает многократное срабатывание события.

Поскольку ListView отменяет выбор первого элемента, второй элемент - это то, что вам нужно, и коллекция должна содержать только один элемент.

То же самое ниже использовалось в мобильном приложении, поэтому некоторые имена коллекций могут отличаться, так как используется компактная структура, однако применяются те же принципы.

Примечание: Убедитесь, что в OnLoad и заполнении списка вы выбрали первый выбранный элемент.

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

В идеале, этот код должен быть помещен в UserControl для легкого повторного использования и распределения в одном выбранном ListView. Этот код не слишком полезен для множественного выбора, поскольку событие работает так, как должно для этого поведения.

Надеюсь, это поможет.

С уважением,

Энтони Н. Урвин http://www.manatix.com

1 голос
/ 07 июля 2009

Таймер - лучшее общее решение.

Проблема с предложением Йенса заключается в том, что если в списке много выбранных элементов (тысяч и более), получение списка выбранных элементов начинает занимать много времени.

Вместо создания объекта таймера каждый раз, когда происходит событие SelectedIndexChanged, проще просто поместить постоянный объект в форму вместе с конструктором и заставить его проверить логическую переменную в классе, чтобы увидеть, следует ли ему вызывать функция обновления.

Например:

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

Это прекрасно работает, если вы используете информацию просто для отображения, например, для обновления строки состояния, чтобы сказать «X из Y выбран».

0 голосов
/ 10 августа 2011

У меня может быть лучшее решение.

Моя ситуация:

  • Один список выбора (а не множественный выбор)
  • Я хочу избежать обработки события при его срабатывании для отмены выбора ранее выбранного элемента.

Мое решение:

  • Запишите, какой элемент пользователь щелкнул по MouseDown
  • Игнорировать событие SelectedIndexChanged, если этот элемент не равен NULL, и SelectedIndexes.Count == 0

Код:

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

}
0 голосов
/ 28 октября 2010

Раймонд Чен имеет пост в блоге, который (вероятно) объясняет , почему существуют тысячи событий изменений , а не только одно:

Почему существует уведомление LVN_ODSTATECHANGED, когда уже есть очень хорошее уведомление LVN_ITEMCHANGED?

...
LVN_ODSTATECHANGED уведомление говорит вам, что состояние всех предметов в указанном диапазоне изменилось. Это сокращение для отправки индивидуальный LVN_ITEMCHANGED для всех предметы в ассортименте [iFrom..iTo]. Если у вас есть представление списка владельцев данных с 500 000 предметов и кто-то делает выберите все, вы будете рады, что вы получить один LVN_ODSTATECHANGED уведомление с iFrom=0 и iTo=499999 вместо полумиллиона индивидуальный маленький LVN_ITEMCHANGED уведомления.

я говорю вероятно объясняет почему, потому что нет гарантии, что представление списка .NET является оберткой вокруг общего элемента управления Listview - это деталь реализации, которую можно изменить в любое время (хотя почти наверняка никогда не будет).

Намеченным решением является использование списка .NET в виртуальном режиме, что затрудняет использование элемента управления на порядок.

0 голосов
/ 07 июля 2009

Оставьте ListView и все старые элементы управления.

Сделай DataGridView своим другом, и все будет хорошо :)

0 голосов
/ 07 июля 2009

Майлон >>>

Целью никогда не было работать со списком из нескольких сотен пунктов, но ... Я проверил общий пользовательский опыт с 10 000 элементов и выбором 1000-5000 элементов за один раз (и изменения 1000-3000 элементов как в выбранных, так и в невыбранных) ...

Общая продолжительность расчета никогда не превышала 0,1 с, некоторые из самых высоких измерений составляли 0,04 с. Я обнаружил, что это вполне приемлемо для такого количества элементов.

А из 10.000 предметов простая инициализация списка занимает более 10 секунд, поэтому в этот момент я бы подумал, что в игру вступят другие вещи, как отмечает Виртуализация, как отмечает Джо Чунг.

Тем не менее, должно быть ясно, что код не является оптимальным решением в том, как он вычисляет разницу в выборе, при необходимости это можно значительно улучшить, и различными способами я сосредоточился на понимании концепции с помощью код, а не производительность.

Однако, если вы испытываете ухудшение производительности, я очень заинтересован в некоторых из следующих действий:

  • Сколько предметов в списке?
  • Сколько выделенных / отмененных элементов одновременно?
  • Сколько приблизительно нужно времени, чтобы событие поднялось?
  • Аппаратная платформа?
  • Подробнее о случае использования?
  • Другая важная информация, о которой вы можете подумать?

В противном случае нелегко помочь улучшить решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...