Yeah, performance does start to suffer with massive numbers of controls on screen. The situations I've used this technique in have generally been limited to a dozen or so rows in the common case, with some performance degradation being expected and acceptable if it grows beyond that. You may well be right, in the end, that a fully custom control will be the only performant solution, but I have a few thoughts—if nothing else, I'm curious about what's really going on here:
1. When profiling, make sure you're really profiling everything—some things (painting in particular, but that in turn may be what triggers layout) may only happen when the main event loop returns to processing messages. As a start, however it is you measured the 500ms you refer to, make sure your profiler sample set actually contains that many samples, actually covers the whole time period you're interested in. If it doesn't...I'm not familiar enough with the guts of the profiler and the event loop to just tell you what you need to know offhand, but have a look at InputState>>loopWhile:, and at the way the profiler makes a distinction between a sample set being "active" or somesuch and the profiler actually _running_. You may not be able to use the simple Profiler class>>profile: API, but it should be possible to manually create a sample set, start the sampling process before opening the list, and stop it after all processing is done—a #postToInputQueue block (*not* the newer #postToMessageQueue, you want to wait until *all* Win32 messages are processed) might be a good place, or...I think there's a method #onEnterIdle somewhere, do a selector search for *idle* maybe. Conceptually this is more-or-less a hook for WM_IDLE, though that may not be the implementation.
2. When you say it takes 500ms, is that for the first render of 50 messages, or is that every time you add/remove/update one? Make sure your "sync" method, that gets the right number of sub-presenters in place, isn't doing extra work—only add or remove if the *number* of items changes, and only as much as you need to. Mine looked something like (pseudocode):
self view noRedrawDo: [
[self model size > self subPresenters size] whileTrue: [SubPresenter createIn: self].
[self subPresenters size > self model size] whileTrue: [self remove: self subPresenters last].
self subPresenters with: self model do: [:presenter :item | presenter model: item]].
Where only one, and perhaps neither, of the while loops will run. If you know things about what items may have changed, you can optimize which presenters have their model replaced as well.
3. You can compute the height of a row without actually instantiating a view for it—DrawText[Ex] with DT_CALCRECT, you can work backwards from there to figure out what Dolphin exposes it as. So, now you know the height the whole list *would* take up, and you can optimize the initial creation of sub-presenters to be lazy—only create as many as are actually visible on-screen, but size the container within the ScrollingDecorator to the full height it will eventually need. Then intercept scrolling and add new rows as needed—though this will probably force you to choose between laggy scrolling, if you do it in a synchronous/blocking way, or a flash of empty space before the new items fill in. It'll be...*interesting*...to get it to work with click-and-drag-the-scrollbar scrolling, too, since that involves mouse capture, but...well, actually you could fill the extra space with an ImageView displaying a tiled mockup of a row, so the user knows there will be *something* there but it only populates when they release the mouse. Or, you could create the visible ones on initial load, then the rest one-at-a-time in...oh, any number of approaches—WM_IDLE handler, fast-firing WM_TIMER, forked Process that calls back in with #postToInputQueue, etc—so that most likely they would all be created by the time the user interacts with them, but the event loop is only ever blocked for as long as it takes to create a single row.
4. You might experiment with hard-coding the creation of the row views rather than instantiating them from a resource. You would do something like:
newSubPresenter := self add: ItemPresenter new.
newView := ContainerView new.
newView name: (newView addSubView: StaticText new) as: 'message'.
self view addSubView: newView.
newSubPresenter view: newView.
Not sure if this would be faster than the resource, but you said about switching from proxies to views being slow, so maybe? Also, I'm not sure whether creating the entire row view before adding it to the parent will work—some views have issues being re-parented, so if anything you do while configuring the children of the row causes it to be fully realized (i.e. #create'ed), it might not slot in to the outer container properly. If needed you can add it immediately when you create it, e.g. newView := self view addSubView: ContainerView new. Adding it later *might* have better performance, but it also might not, so check. In any case probably the most important thing is to wrap the whole thing in `self view noRedrawDo:`!
5. If you want to get *really* clever, you could do what the iOS and Android list components do, and recycle subviews when scrolling in order to *only* ever have as many as fit on screen at once. In your case you would reuse the Presenters as well, giving them a new model. Actually, now that I think about it, this might be quite doable—you would need to manually position the row views (still maintaining a container representing the overall size of the list, to get decent scrolling behavior out of the box), maintain a list of the order the item presenters are currently in and what index in the list is the first visible item, and cycle them around as you scroll, moving the first one to the end when scrolling down and vice-versa. You'd need to watch resize events in this case to add/remove sub-presenters.
Re: the owner-drawn ListBox—there are places elsewhere in Dolphin where a parent view automatically redirects such notifications to the appropriate child—I want to say all the NM_ messages work this way, or rather they are all *actually* a WM_NOTIFY to the parent but it re-dispatches to the appropriate child? You might be able to take a similar approach, and you might find you can get quite far with loose methods and additions to the message tables held in class variables, without needing to subclass ContainerView and without *technically* modifying core code at all.
For WM_MEASUREITEM specifically, I see something about "CtlID (Type: UINT): The identifier of the combo box or list box. This member is not used for a menu."—it's not a handle, but it might be enough information to identify the originating control in a generic way that you could be comfortable adding to ContainerView-in-general rather than a hack for your specific case. You'll have to search for what it actually means and how to use it to retrieve the actual control.
Okay, whew, wall of text. Hope that helps, let me know how it goes!