Using Dolphin's UI framework, what is the correct way to generate a list of items of variable height with a vertical layout?
For example, a list of messages in a chat room. I tried ListView but that didn't work since all rows have to have the same height and will need to do a lot more customization besides. Also tried drawing each message on a canvas but that was too painful.
Is there an abstraction between these two that could solve the problem?
- Joe
Then a Presenter for the list that can use `<ItemPresenter> createIn: self` to add rows, `#model:` to update them, and `#remove:` to remove them.
Naively, I put it all in `model:`, but that broke when it was initialized with a nonempty list, presumably because the view for the container presenter is still a DeafObject at that point.
What I did is have a method (e.g. `#updateListPresenters`) that brings the model and sub-presenters/views in sync, but bails if either the model or the view is missing (nil/deaf). Then I called it from both `#onViewAvailable` and `#model:`. That way itdoesn't matter what order things happen in, we'll get everything in sync as soon as we have everything we need.
2) Use a ContainerView with a FlowLayout layout manager and add each item as a subview. Example (modified from FramingLayout class>>example1):ou'd lose the selection behavior compared to the ListBox approach but you could create a custom view for each item which provides any required functionality (e.g. reply to message).
1) Use a ListBox created with the LBS_OWNERDRAWVARIABLE style and override wmMeasureItem:wParam:lParam in ListBox to set the size of each item. You'll also need to draw the items yourself. See https://docs.microsoft.com/en-us/windows/win32/controls/wm-measureitem
Okay, at this point I've tried both the ContainerView with FlowLayout and ListBox with custom draw method and neither worked out. In short, the former ended up being too slow and the former just ends up being all sorts of wrong on an implementationlevel.
message was added, but even after removing the layout it was still orders of magnitude too slow. The only thing that made any impact on the draw time was removing as many MVP triads as possible and render the entire message with a single TextPresenter.2) Use a ContainerView with a FlowLayout layout manager and add each item as a subview. Example (modified from FramingLayout class>>example1):ou'd lose the selection behavior compared to the ListBox approach but you could create a custom view for each item which provides any required functionality (e.g. reply to message).
The problem here was the messages took way too long to draw. For 50 messages it took 500ms rather than 0 or 1ms when I just shoved them into a ListView. My intuition was that it was an issue with the FlowLayout having to recompute each time a new
Running the profiler on the loop that calls `MessagePresenter createIn: self` for each message was interesting but not very enlightening. If interpreted it correctly, most of the time seemed to be spent in InputState, probably waiting for responsesfrom Windows APIs. Another hotspot seemed to be code for replacing view proxies with actual views.
In the end, my conclusion is that there is significant performance overhead in using the MVP triad and that it doesn't scale well for non-standard controls.wm-measureitem
1) Use a ListBox created with the LBS_OWNERDRAWVARIABLE style and override wmMeasureItem:wParam:lParam in ListBox to set the size of each item. You'll also need to draw the items yourself. See https://docs.microsoft.com/en-us/windows/win32/controls/
It turns out that the draw and measure events don't get sent to the ListBox but rather their "owner", which ends up being whatever the parent view of ListBox is. E.g., if it's a ContainerView, then it needs to be subclassed and the wmDrawItem: andwmMeasureItem: methods need to be overriden in order to receive draw and measure events for the ListBox. Really annoying.
This is feasible for wmDrawItem: because the DRAWEVENTSTRUCT contains a reference to the ListBox view which you can then delegate the message to. It doesn't work so well with wmMeasureItem: since it contains no such reference, meaning that theContainerView needed to hold either a reference to the ListBox or its model in order to even know what its measuring.
All in all it sortof works but seems to break the standard way of organizing views and would require a significant refactor to work in a sane way.
3) Create a complete custom view to display the entire list and manage the required behavior. Probably the most work but also the most flexible. See the Dolphin MoenTree View package for an example of a custom view of a selectable collection of objects.
So yeah, I'm left with this, which I strongly suspected was going to be the case from the beginning. The fact that the class hierarchy tree seems to be able to draw hundreds of objects nearly instantly is promising and what I'm aiming for.
Okay, whew, wall of text. Hope that helps, let me know how it goes!
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 youmeasured 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
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?Just on the first render. It did well enough for add/remove/update events, aside from a flicker which I assume could have been fixed with a double buffered view.
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.
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?
At this point I've divorced from MVP and standard Windows controls completely and am now drawing everything using GDI+.
I think that's called virtual scrolling?
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 ina 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.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 293 |
Nodes: | 16 (2 / 14) |
Uptime: | 229:04:46 |
Calls: | 6,624 |
Calls today: | 6 |
Files: | 12,171 |
Messages: | 5,318,998 |