I'm currently using the following code to update controls in my dialog depending on the rows selected in the list-view control:
void CMyDialog::OnLvnItemchangedListTasks(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// TODO: Add your control notification handler code here
ASSERT(pNMLV);
if(pNMLV->uChanged & LVIF_STATE)
{
if((pNMLV->uNewState ^ pNMLV->uOldState) & LVIS_SELECTED)
{
//List selection changed
updateDlgControls();
}
}
*pResult = 0;
}
This method works, except that it's REALLY slow!
For instance, if my list has about 100 items in it and then I click on the first item and Shift-click on the last item (to select all items) it locks up my app for about several seconds. This won't happen if I comment out my updateDlgControls
in the example above.
Is there a way to optimize the processing of LVN_ITEMCHANGED?
For instance, for 100 selected items, it's called for each and every one of them.
EDIT: Following Jonathan Potter's suggestion I changed it to this, which seems to do the job quite nicely:
void CMyDialog::OnLvnItemchangedListTasks(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// TODO: Add your control notification handler code here
ASSERT(pNMLV);
if(pNMLV->uChanged & LVIF_STATE)
{
if((pNMLV->uNewState ^ pNMLV->uOldState) & LVIS_SELECTED)
{
//Use the timer to optimize processing of multiple notifications
//'LVN_CHANGE_OPTIMIZ_TIMER_ID' = non-zero timer ID
//If SetTimer is called when timer was already set, it will be reset without firing it
::SetTimer(this->GetSafeHwnd(), LVN_CHANGE_OPTIMIZ_TIMER_ID, 1, OnLvnItemchangedListTasksTimerProc);
}
}
*pResult = 0;
}
static VOID CMyDialog::OnLvnItemchangedListTasksTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
VERIFY(::KillTimer(hwnd, idEvent));
ASSERT(hwnd);
::PostMessage(hwnd, WM_APP_PROCESS_LVN_ITEMCHANGED, 0, 0);
}
ON_MESSAGE(WM_APP_PROCESS_LVN_ITEMCHANGED, OnDelayed_updateDlgControls)
LRESULT CMyDialog::OnDelayed_updateDlgControls(WPARAM, LPARAM)
{
//List selection changed
updateDlgControls();
#ifdef _DEBUG
static int __n = 0;
TRACE(CStringA(EasyFormat(L"Updated list count=%d\n", __n++)));
#endif
}
There's only one caveat that I see in this approach. One needs to make sure to check for disabled conditions additionally in processing methods for UI controls that can be disabled by the updateDlgControls()
method, since having introduced a delay we can run into a problem when the processing method for a UI control can be called before the timer procedure had a chance to call updateDlgControls()
method that normally disables UI controls. (This may happen, for instance, if a user repeatedly clicks shortcut keys on the keyboard.) By doing secondary checks in processing methods we make sure that disabled methods don't cause a crash.
You should consider using the ListView in virtual mode instead (enabling the
LVS_OWNERDATA
style). It has vast improved performance over a non-virtual ListView, since you dont store the list item data in the ListView itself, it just displays data you provide from another source of your choosing. As such, it offers some additional notifications for optimizing item retrievals and updates. TheLVN_ITEMCHANGED
documentation states:I would do this using a flag and a timer. When you get a state change message, set a flag that indicates the state has changed, and use
SetTimer
to kick off a short (even 1ms would probably do) timer. Since timers are low priority it won't fire while other messages, likeWM_NOTIFY
are in your queue. When the timer expires, kill it and then update your UI state.(The flag is simply used so that you don't recreate the timer over and over again - once the timer has fired, use
KillTimer
to kill it and clear the flag ready for next time).