This blog post is about two discoveries I have made in Firemonkey, and the way to circumvent them.
The TExpander bug
I have noticed occasionally that I can close a TExpander, but not reopen it again. Since I did not earlier have the time nor energy to investigate what was the reason, I just completely avoided using TExpander in Firemonkey applications.
However it is a useful control, and now I’m in a situation where I do need such functionality to minimise use of screen-estate, and thus had to take a close look at what happens.
First I will show you the problem.
I have a fairly complex, dynamically built list of controls, contained in a fixed size (its size is calculated at a point, but for the sake of the discussion, it has a fixed height) vertical layout, which is placed in a vertical scroll box. It looks like this in Firemonkey on Windows:
In this case I can click the expander (Rest vare) or (Fraktion 1) and it closes nicely.
And I can click on it again to expand it again without problem.
However if I scroll it off view while it is closed, and then scroll it back in view, this happens when attempting to expanding it:
Notice that the symbol indicates its expanded, but the control is still looking non expanded.
Debugging it a bit, I discovered that the TExpander’s ApplyStyle is called the moment the expander is scrolled out of view. In the ApplyStyle method, UpdateControlSize(False); is called.
UpdateControlSize updates the TExpanders size, which results in the DoSetSize method being called.
DoSetSize unfortunately updates the FContentHeight to 0, after which the TExpander do not display correctly when scrolled back in view and being expanded again.
This is obviously a bug in Firemonkey. I’m not completely sure how to make a correct fix for it, and I honestly do not have the time to do so, but I do have a workaround for it.
At time of creation of the TExpander, I can set the TagFloat property to expander.Height.
Then add an event handler for the OnExpandedChanging event, which purpose is to reestablish the expanders height. It works fine regardless if the expander is in closed or expanded state:
procedure TMyStuff.DoOnExpandedChanging(Sender: TObject); var exp:TExpander; begin exp:=TExpander(Sender); exp.Height:=exp.TagFloat; end;
When making multiple changes to sizes and placement of controls in a Firemonkey application, it is a good idea to start with BeginUpdate of the container layout, and end with EndUpdate.
It makes the changes look atomic, saves CPU cycles and saves visual noise while the changes are going on.
BeginUpdate and EndUpdate are also supposed to be able to handle the situation that child components are added or removed in any child control or children of it.
It worked reasonably well in Delphi versions until 10.3 Rio, but from that version, I started to get argument out of range errors in EndUpdate:
Interestingly FControls.Count in my case have a value of 1, but accessing FControls throws the exception.
The structure of the code I use is (in pseudo code):
BeginUpdate; try RemoveSubControls; CreateNewSubControls; finally EndUpdate; end;
Unfortunately that causes chaos with something internally in Firemonkey, which results in EndUpdate failing in some occasions, but not all.
The workaround solution is:
BeginUpdate; try RemoveSubControls; finally EndUpdate; end; BeginUpdate; try CreateNewSubControls; finally EndUpdate; end;
It will still work reasonably well visually and performance wise… and it does not throw an exception.
19,299 total views, 1 views today