Date: | June 10, 2004 / year-entry #229 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20040610-00/?p=38933 |
Comments: | 33 |
Summary: | Just a little tip: If you're going to be adding a lot of items to a listbox or combobox, there are a few little things you can do to improve the performance significantly. (Note: The improvements work only if you have a lot of items, like hundreds. Of course, the usability of a listbox with... |
Just a little tip: If you're going to be adding a lot of items to a listbox or combobox, there are a few little things you can do to improve the performance significantly. (Note: The improvements work only if you have a lot of items, like hundreds. Of course, the usability of a listbox with a hundred items is questionable, but I'm assuming you have a really good reason for doing this.) First, you can disable redraw while you add the items. (This tip works for all controls, actually.) SetWindowRedraw(hwnd, FALSE); ... add the items ... SetWindowRedraw(hwnd, TRUE); InvalidateRect(hwnd, NULL, TRUE);
This suppresses the control redrawing itself each time you add an item. But there is still something else you can do: SendMessage(hwndCombo, CB_INITSTORAGE, cItems, cbStrings); ... add the items ... (For listbox controls, use the LB_INITSTORAGE message.)
It's okay if these values are merely estimates. If you are too low, then there will still be some reallocation for the extra items. If you are too high, then some memory will be allocated but remain unused. Some people will recommend using LockWindowUpdate, but this is wrong. LockWindowUpdate disables drawing in the window you specify, but suppressing flickering during updates is not what it was designed for. One clear limitation limitation of LockWindowUpdate is that only one window can be locked for update at a time. So if two windows each try the LockWindowUpdate trick at the same time, only one of them will succeed. That's not particularly reliable, now, is it. The purpose of LockWindowUpdate is to assist code that is drawing drag/drop feedback. If you are drawing the cursor for a drag/drop operation, you don't want the window beneath the cursor to start drawing (and thereby overwrite your beautiful cursor). So you lock the window while you draw the cursor and unlock it when the cursor leaves the window. That's why there is only one locked window at a time: There can be only one drag/drop operation active at a time, since there is only one mouse. |
Comments (33)
Comments are closed. |
Ok, snarky history question time: Why is there only one mouse (or rather, why is there only one mouse cursor – you can have multiple mice with a single cursor, my laptop has 3 mice on it)?
I’m actually dead serious – why does windows support only one mouse and only one keyboard (although I’m not sure how what more than one keyboard would look like programatically).
Now if only Windows Explorer would implement this nice feature :-) Just yesterday I deleted 10,000 small files from a single directory and Explorer hung for a couple of minutes. When I had to do the same thing again, I just left the directy openend a command shell and used "del.". This took 5 seconds.
You insensitive clod! I only have one arm.
It may also be worth mentioning that you can similarly improve the performance of many Windows Forms controls by calling BeginUpdate() before you start adding lots of items and then calling EndUpdate() when you’re finished.
I wish explorer would call LockWindowUpdate. You don’t know how many times I’ve had a selection rectangle blitted to the desktop or in a file listing window just taunting me to dirty their update rectangles.
Would it be possible to add the second half of this post to the documentation for LockWindowUpdate (although it mentioned that only one window can be locked, it didn’t have all the other useful notes).
Jeroen-bart: Now you’re changing the subject. Explorer does use this trick to insert the original 10,000 files into the listview.
Deleting a file from Explorer is boatloads more than just calling DeleteFile. (Just look at that horrific feature set of SHFileOperation.)
Explorer does use it when deleting – notice that after you deleted the items from the command line, the Explorer window refreshed quickly.
The one-by-one you see is not the refresh cost. It’s the SHFileOperation cost.
Raymond: Okay, I updated the subject (at least of my comment) ;-). So Explorer uses it when adding, but does it also work when removing items? And does Explorer use it then as well? I see this in all kinds of applications where there are a lot of items in a listview/box. When you remove them all, it takes ages and you can actually see the position-bar (can’t recall it’s correct name) on the scrollbar grow larger. But when you minimze the window, the items are removed almost instantly. So it looks to me like it’s removing-redrawing-removing-redrawing-etc just like when adding.
Larry- I also want multiple pointers if I have multiple mice. But it would probably confuse and break a lot of Windows. Think of everything that calls GetCursorPos() today– which value would you return if you start supporting multiple cursors? App compat would be difficult.
Now, how does this work in .NET WinForms land? Do we P/Invoke?
Check out "Raw Input" in the Platform SDK. You actually can receive separate input from multiple mice if you want to.
It was mentioned BeginUpdate should be used on .net controls. Also one should not forget the AddRange method. In my experience, the perf boost of AddRange is very noticable.
Since SetWindowRedraw is a macro wrapping WM_SETREDRAW, for .NET WinForms you can do this like so:
– DllImport SendMessage
– define as WM_SETREDRAW = 0x000B;
– then in some derived control:
public void BeginUpdate()
{
SendMessage( this.Handle, WM_SETREDRAW, false, IntPtr.Zero );
}
public void EndUpdate()
{
SendMessage( this.Handle, WM_SETREDRAW, true, IntPtr.Zero );
Refresh();
}
cheers,
don
On the deleting 10000 Files in Explorer Issue:
I think there’s more to it than just the SHFileOperation cost. I think there (also) an complexity issue. If you select only the first say 5000, delete them, then select the rest and delete them, I’m quite sure it is quite a bit fastern then selecting and deleting the whole folder at once. It might be psychological but it certainly didn’t feel like that the last time I tried it. Perhaps I should stop watch it…
Just for the record: I timed it and believe it to be a memory/swapping problem. Perhaps making a list of the filenames or so. At least it becomes more apparent with long filenames.
eg deleting 10.000 files with the filname ca 150 characters long would take 95 seconds, 20.000 435 seconds.
Also I’ve noticed that there is often a very long time between the deletion has finished (ie the "delting…" popup closes itself) and explorer becoming responsive again:
20 seconds with 10.000 shortnamed files, 120 seconds with 20.000. I wasn’t patient enough to wait after trying it with 40.000 and killed explorer.exe after ca 480seconds. exlorer.exe is using all cpu it can get during this period.
I didn’t think it had to do with redrawing. I was being offtopic, I know. Sorry.
I just pointed out that (at least for a lot of files) it takes around 4 times as long deleting twice as many files when I would expect the deletion itself to be O(n). If it removes each file individually from it’s view in response to the event, that’s not too surprising really.
Thanks,
Ben
For those .Net folk that read the blog, the Control class offers the SuspendLayout() and ResumeLayout() functions that perfom this for you.
In .Net land, they’re useful for just about any control where you’re individually adding a large number of items – ListView s, DataGrid s and all…
http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemwindowsformscontrolclasssuspendlayouttopic.asp
SHFileOperation generates a SHCNE_DELETE as it deletes each file – this goes into the change notification system and eventually pops out the other side. The Explorer window that is viewing the folder receives the notification and then goes looking for the deleted item to remove it from the view. Repeat 10,000 times. This has nothing to do with redrawing. It’s all the bookkeeping that happens *BEFORE* the redraw.
Speaking of performance of listview, what about the performance of treeview and deletes. I have an application that has a HUGE number of elements in a treeview (40k+). When I need to repopulate the treeview for a different data set, it takes forever to clear the treeview. If I just delete the treeview, it takes forever. If I close the application, it is very fast.
To work around the problem I ended up having to hide that treeview, create a new one and then during idle processing delete batches of elements of the old treeview.
Is there a fast was to delete items from a treeview?
uintptr_t killTree(void *tree)
{
DestroyWindow((HWND)tree);
return 0;
}
_beginthread(killTree, 0, treeWin);
On a sidenote, it looks like whoever writes the winapi documentation on MSDN moved on to the C runtime’s because I don’t remember it being this crappy before.
asdf: That doesn’t help any. Remember: Windows have thread-affinity. Destroying 40,000 items entails 40,000 TVN_DELETEITEM notifications. Just by sheer numbers that’s going to take a while.
40,000 items in a treeview is unusably excessive.
Tim: You might want to check out the following CodeProject article:
"A faster tree control"
http://www.codeproject.com/treectrl/rgtree.asp
It is true that 40k items is pushing usability, but it isn’t like I can go to BioWare and say "Hey, can you ship a game with just 5k resources?".
From my research, it is the TVN_DELETEITEM notifications that is slowing it down. I would love the option of telling the treeview to not worry about sending those messages.
Thank god the program is using dynamic tree population. That help to minimize the problem.
See if this works Tim:
LONG_PTR style = GetWindowLong(treeWin, GWL_STYLE);
style &= ~(WS_CHILD|WS_VISIBLE);
style |= WS_POPUP;
SetWindowLong(treeWin, GWL_STYLE, style);
SetParent(treeWin, NULL);
Now delete it.
Unclear what you’re trying to accomplish there, asdf. (You really shouldn’t fiddle the WS_VISIBLE style directly; use ShowWindow instead.) That still doesn’t stop the 40,000 notifications. Unlike listview (LVN_DELETEALLITEMS), treeview doesn’t have a "mass delete" notification.
My idea was to kill the window in another thread but and since the tree control doesn’t have a parent, it doesn’t send a notification to the original thread. It can take as long as it wants to kill now, the main thread is now responsive and that’s all that matters. But now I realize what you meant by thread-affinity because it only matters which thread the window was originally created in.
As long as we’re here, what are some of the bad things that can happen if you toggle the visibility bit directly instead of using one of the many functions that take a SW_SHOW/SW_HIDE flag?
There’s a lot of stuff that happens when you hide and show a window – if you toggle the style directly then that stuff doesn’t happen, but you tricked the window manager into thinking that it did. This messes up the internal bookkeeping.
TristanK, in .NET, you use SuspendLayout/ResumeLayout if you are adding/removing controls from a form or container control.
For ListBoxes/ComboBoxes, you use BeginUpdate/EndUpdate.
However, there is a warning here. If your ListBox has the Sorted property set to True, and you use Begin/EndUpdate and only add a single item, no items will display.
I, too want multiple pointers. GetCursorPos() could just return values appropriate for the last pointer moved. Sounds reasonable-ish.
<<SHFileOperation generates a SHCNE_DELETE as it deletes each file – this goes into the change notification system and eventually pops out the other side. The Explorer window that is viewing the folder receives the notification and then goes looking for the deleted item to remove it from the view. Repeat 10,000 times. >>
Ugh, if Explorer uses a linear search that makes this an n^2 operation which would explain the observed issues. Pretty silly since Explorer damn well knows what file it was trying to delete, it ought to check if that’s the notification it just got.
Explorer knows which files it was planning on deleting, but it doesn’t know which ones actually succeeded in being deleted. And if you do a "select all+delete" then you’re back to where you started. "Okay, that file that got deleted is one of these O(n) files."