I think we have to face it: More and more systems are equipped with monitors capable of displaying in higher density and people expect that our apps support these higher resolutions. While not that often, but still significant, we can see multiple monitor systems with mixed density getting their share.
With the release of Delphi 11.2 I thought that it might be time to bite the bullet and empower one of my bigger apps for High DPI support. Some edge cases with not commonly used components and other unavoidable quirks were expected. Luckily I was able to fix all of them or at least found acceptable workarounds.
The big task I have already been aware of for quite some time were the image lists. The application uses a couple of PngImageLists with 24×24 images. Most of them reside on a single TDataModule just for that purpose. Most of the controls on the forms and frames link to this image lists, which makes changing or extending them pretty easy. Actions can use a dedicated image index that works independently of the controls linking to them and where they are located. Local image lists inside a form or frame are rare and when, they contain only 3 to 5 images with a very special meaning. It was already planned to eliminate these and integrate them into the central data module.
On High DPI this approach can no longer be used. Delphi 10.3 Rio introduced TVirtualImageList in combination with TImageCollection, which allows proper scaled images for arbitrary sizes. It even automatically adapts to the current form scaling induced by the monitor the form is placed. Alas, it has to be owned by the form or a frame on that form.
Technically that is just logical: The image list has to provide images in a specific size and that can be different when the forms reside on displays with different scaling. One image list can only have one size at any time, so it cannot be used for two forms that require distinct sizes.
So I thought through the process to transform my current one-datamodule-with-image-lists approach into something with virtual image lists on each form.
Having one data module with one or more image collections was a no brainer. That would just work similar to the current approach. The data module was instantiated once and all virtual image lists link to their image collection on that data module. Check!
Getting the virtual image lists to the different forms seemed reasonable, but would end in having several copies of the image lists. As that is the basic necessity as explained above, there is not much I could do against it. So, check!
The app makes use of frames a lot. Deeply nested frames. And visual frame inheritance. More than 300 frames and about 100 forms.
While virtual image lists do work when placed on a frame, it just didn’t look right to have an image list with 350 items on a small frame with just one or two images. So I thought of putting only those images into the list needed in that specific context. Alas, that broke the image index values stored in the actions the controls link to.
Well, I could switch to the ImageName feature, also introduced in Delphi 10.3, which allows to identify images by their name instead of their index in the list. Unfortunately a couple of 3rd party controls still don’t support image names, yet. Also there are cases where the image index is calculated with a fixed offset. Not really the road I was comfortable to go.
Each frame sits on some form at the end. I could use the image list of the form. Actually it is the form that implies the requirement to have a separate image list, not the frame. Unfortunately that would give some nasty cyclic dependencies, when the controls of the frame need to know about the forms image list during design time. I could als not rule out that one frame class was later used on different forms. Also not a viable solution.
All approaches shown above have the common drawback that maintaining all these image lists and keep them in synch will become a real nightmare. Yes, there is AutoFill, but that implies that all distinct image lists need their own image collection with its images following the required order. Doable at least.
My wish was to have the same simple experience at design time to link controls to a central image list on a data module, while later at runtime some magic happens and the images are available in the appropriate sizes on each form and frame. At best the changes to the current code should be near to none – at least neglectable. The less changes I have to make to the code the more confidence I can have that I don’t break anything.
Well, finally I found a way to achieve this. I added a data module with some image collections and changed the existing image lists to TVirtualImageList, strictly keeping the order of the images as before. I can continue to link to these image lists from any control on any form and frame during design time, while no image list on any form or frame is needed. At run time the images are shown in the required sizes – even simultaneously on different forms. The only changes I have to do is to add a unit to the uses clause of every direct descendant of TForm and TFrame and add a RegisterClass/UnRegisterClass call inside the initialization/finalization section of the data module unit containing the image lists.
It’s late now and I need some sleep. The solution will be presented in a follow-up article soon together with an explanation of the magic happening behind the scenes.