Scaling a TFrame - c++builder

What is the correct procedure for Scaling a TFrame in C++Builder?
I am developing in PixelsPerInch=120 (aka. Windows font size 125%), but want my forms to work in PixelsPerInch=96 also (aka. default).
My main form loads fine: the VCL code to create a TForm includes a check of whether the design PPI differs from runtime PPI and it scales everything correctly at that point.
However when loading a TFrame, no automatic scaling occurs. So far as I have been able to discover, I have to manually call Frame1->ScaleBy(M, D);.
This mostly works however it has a bug (which I will describe below) so the result is a frame that is not scaled correctly.
What is the proper way to create a Frame and have it scale on load, like a Form does?
I am currently using the following:
// class member
TFrame *f;
// called just before Application->Run()
f = new TFrame(fMain);
int M = fMain->PixelsPerInch;
int D = 120; // Matt's development
if ( M != D )
{
f->ScaleBy(M, D);
}
and then when I want to activate the frame, `f->Parent = fMain->Panel1;
(*f)->ScaleBy(M, D);
I have also tried writing f->Parent = fMain->Panel1; before calling ScaleBy, based on the theory that the Frame has controls using ParentFont and so on, so it might be better if this info is available during scaling; however this introduces other bugs.
The bug is that when calling ScaleBy on a Frame or a Form which has a TLabel which has AutoSize=true and ParentFont=false and Anchors including akBottom, then the Label is displayed much higher up on the window than it should be (maybe even off the top of the window).
I have tracked down the problem: the TControl.ChangeScale function includes a call to ScaleMargins, and the Margin property has an on-set trigger that ends up calling AlignControls for the parent form, which will move any controls with AutoSize=true.
By stepping through Vcl.Controls.pas, I see that my Label scales correctly at first , however when the next control (a radio group, it happens to be) is processed, the AlignControls is triggered, which changes my Label's Top.
The AlignControls function apparently can't handle the TLabel with the parameters I just described when called part-way through a rescaling operation.
I haven't firmly nailed down why, but I think it is because the parent Form or Frame has not yet been scaled (the ScaleControls function scales all the children before scaling the self), so it uses the form's old Height to calculate the actual Top that results from doing alignment to the bottom.
This effect is not triggered when loading the Form the first time, because the scaling code checks for csLoading component state and disables a whole lot of reactionary effects, including the call to AlignControls.
I haven't figured out why the bug is only triggered when ParentFont=false.
My current workaround is to include a line in the FormResize handler that manually realigns the Labels in question with another label that has ParentFont=true (and thus doesn't suffer from the bug).

Related

How do you add an item to a TStackPanel at runtime

I have a TStackPanel which I want to add a number of frames into at runtime. The number may vary each time the form is opened.
There seems to be limited information about TStackPanel around, and the examples I can find are in languages other than Delphi.
I have a loop that creates the frame, gives it a unique name and then adds it to the TStackPanel:
for i := 0 to 10 do
begin
mfSubFrame[i] := TMyFrame.Create(Application);
mfSubFrame.Name := name_array[i];
StackPanel1.InsertComponent(mfSubFrame[i]);
end;
This does not put anything in the stack panel. If I change the SP line to:
StackPanel1.InsertControl(mfSubFrame[i]);
then I do get a frame in the SP. It is the last one of the loop as I can tell by the name, the others may be hidden behind it but I can't tell. They are certainly not stacked horizontally like they should.
I have tried various other things like setting the parent of the frames to be the SP, and had a look at things like:
StackPanel1.Components.InsertComponent(mfSubFrame[i]);
and other sub-methods, but had no luck so far.
I suspect it may require a combination of statements, like add a control item as well as the actual component, but as I am working on the basis of trial and error it could be a long time before I stumble on the right combination.
I have never used the TStackPanel before, but it seems like you can add controls to it exactly the same way you add controls to any other windowed control: just create the control and assign its Parent.
For example,
for var i := 1 to 10 do
begin
var Memo := TMemo.Create(Self);
Memo.Parent := StackPanel1;
end;
will add ten memo controls (all owned by Self) to StackPanel1. There is no need to name the controls; referring to components by string name at runtime is an antipattern. (So is using FindComponent.)
InsertComponent() changes a component's Owner, which has no effect on visual display. You are creating each frame with Application as its Owner, and then changing its Owner to StackPanel1. You should assign the desired Owner when calling the component's constructor.
InsertControl() changes a control's Parent, which does affect visual display. You are creating each frame without a Parent, and then changing its Parent to be StackPanel1. You should be using the actual Parent property, not calling InsertControl() directly.
That being said, TStackPanel has a ControlCollection property that you are not doing anything with. That collection manages the actual stacking.
If needed 1, for each frame, try calling StackPanel1.ControlCollection.Add(), and then assigning the frame to the TStackPanelCollectionItem.Control property.
1: I don't have the source code for TStackPanel to look at, but I suspect TStackPanel probably handles this automatically for UI controls dropped onto it at design-time, but you might need to perform it manually for controls that you create dynamically at runtime. I'm not sure.

Why does TForm.SetBounds only work correctly when TForm.Position is set to poDefault at design time

I have noticed something very strange. I am persisting the top, left, width, and height properties of a form when it is closing, and using this information to restore the form's last position when it is once again opened by calling SetBounds using the previously stored information. This works well, but only if the form's Position property is set to poDefault at design time. If set to something else, such as poDesigned, poScreenCenter, or poMainFormCenter, SetBounds does not restore the form's previous position and size.
Here's the strange part. What appears to matter is what the Position property is set to at design time. I can change the value of this property at runtime to poDefault and the call to SetBounds still does not work correctly. I have tried something like the following
if Self.Position <> poDefault then
Self.Position := poDefault;
in both the form's OnCreate event handler, as well as from an overridden constructor (and have set Position to poDefault in the constructor, and called SetBounds in the OnCreate event handler). In all cases, changing the form's Position property to poDefault at runtime does not fix the problem that I've observed with SetBounds. The only consistent pattern that I have found is that SetBounds works as it should only if the form's Position property was poDefault at design time.
There are other things that I've noticed with respect to how SetBounds works when a form's Position property is not set to poDefault at design time. For example, a form whose Position property is set to poScreenCenter at design time will not necessarily appear centered on the screen if you call SetBounds. However, it does not appear in the top-left location defined by SetBounds, nor does it respect the width and height specified in the call to SetBounds. Let me repeat, however, that I am setting the Position property of the form to poDefault before calling SetBounds. I've even stuck a call to Application.ProcessMessages between the two operations, but that doesn't fix the problem.
I have tested this extensively with Delphi 10.1 Berlin running on Windows 10. I have also tested it using Delphi XE6 on Windows 7. Same results.
If you have doubts, create a VCL application with four forms. On the first form place three buttons, and add something like the following OnClick to each button:
with TForm2.Create(nil) do
try
ShowModal;
finally
Release;
end;
where the constructor creates TForm2, then TForm3 and TForm4.
On the OnCreate of forms 2 through 4, add the following code:
if Self.Position <> poDefault then
Self.Position := poDefault;
Self.SetBounds(500,500,500,500);
On form2, set Position to poDefault, on form3 set Position to poScreenCenter, and on form4 leave Position set to the default, poDefaultPosOnly. Only form2 will appear at 500, 500, with a width of 500 and a height of 500.
Does anyone have a logical explanation for this result?
poDefault and friends mean "let Microsoft Windows position this form's window when the form would create and show it".
You just created Delphi object - but I wonder if it also has created/shown Windows object (HWND handle and all corresponding Windows internal structures). Especially with themed applications, not ones using standard pre-XP look and feel - they tend to ReCreateHWND when showing, because pre-loading those fancy Windows Themes is relatively expensive operation and only should be done when needed.
I think your default bounds (every property value set in the constructor might be considered a default non-tuned value, to be tuned later after object being constructed) are correctly ignored when you (or TApplication - that makes little difference for the topic) finally do FormXXX.Show.
It is during "make me a window and display it" sequence when your form looks at its properties and tells to MS Windows something like "now I want to create your internal HWND-object and position it at default coordinates/size at your discretion".
And that is absolutely correct behaviour - otherwise WHEN and HOW could TForm apply the Position property??? It just makes no sense to ask Windows for coordinates of a window that does not exists on the screen yet and maybe never would. Windows offers default coords/sizes for the this very second it being asked, looking how many other windows are there and where they are positioned ( and AMD/NVidia video drivers might also apply their correction to it).
It would make little sense, to acquire defaults now, and apply them two hours later when everything would probably be different - different amount of other windows and different positions of those, different set of monitors attached and with different resolutions, etc.
Just consider a "desktop replacement" type of notebook. It was set upon the table connected to large stationary external monitor. Then - let's imagine it - I run your application and it created the tform Delphi object and in the constructor it asked MS Windows for position - and Windows rightfully offered the position at that very secondary large monitor. But then an hour later I unplugged the notebook and walked away with it. Now an hour later I tell your application to show the form - and it will do what? display it with coordinates belonging to that now-detached external display? Outside of the viewport of the notebook's internal display that I only have at the moment? Should this form be displayed in the now "invisible" position just because when I started the application back then that spot was still visible there yet??? Way to confuse users for no gain, I think.
So the only correct behaviour would be to ask Windows for default coords this very second WHEN the form is going from hidden to visible and not a second earlier.
And that means that if you want to move your form - you should do it after it was being show. Place your Self.SetBounds(500,500,500,500); into OnShow event handler. So let the MS Windows materialize your form into default position like required by poDefault in Position property - and move your Window after that. Attempts to move the window that does not exist yet look correctly futile to me.
Either PRESET your form ( in constructing sequence) to explicitly ignore MS Windows defaults and use pre-set cords (via poDesigned value), or let the form ask Windows coordinates, but MOVE it with SetBounds after it got visible via OnShow handler.

Scroll bar in LibreOffice dialog

I am trying to make an image picker component in LibreOffice.
I have a dialog that is dynamically filled with images. When the user clicks on one images, it should be selected and the dialog should be closed.
The problem is that the number of images is variable. So I need to enable scrolling in the dialog (so that the user can navigate through all images).
There seems to be some properties on the dialog object (Scrollbars, Scroll width, Scroll height, etc)
However, I cannot find a way to use them anywhere.
Any ideas?
The scrollbar is one of the Controls available through the dialog box editor. That is the easier way to put a ScrollBar on a dialog box. Just insert it like any other control. There is a harder way via DialogModel.addControl but that seems non-essential to answering this question.
If you add a scrollbar to the dialog box and run the dialog box, you will find it does nothing by default. The functionality (apparently) must be written into a macro. The appropriate triggering event is the While Adjusting event on the ScrollBar object, although it does not trigger the macro simply with the "Test Mode" function in the dialog editor. Running the dialog box through a macro triggers the While Adjusting event when the scroll arrows are triggered, when the slider area is clicked to move the slider, and when the slider itself is dragged. The Object variable returned by the scrollbar event contains a property .Value which is an absolute value between 0 and the EventObject.Model.ScrollValueMax, which allows you to manipulate the other objects on the page manually based on the position of the slider.
Yes, that's right, manipulate objects manually. The sole example I found, from the LibreOffice 4.5 SDK, does precisely this. Of course, it is not as bad as it sounds, because one can iterate through all of the objects on the page by reading the array Dialog.getControls(). In any event, the secret sauce of the example provided in the SDK is to define Static variables to save the initial positions of all of the objects you manipulate with the scrollbar and then simply index those initial positions based on a ratio derived from the scrollbar Value divided by the ScrollValueMax.
Here is a very simple working example of how to scroll. This requires a saved Dialog1 in the Standard library of your document, which contains an object ScrollBar1 (a vertical scrollbar) and Label1 anywhere in the dialog. The ScrollBar1 must be configured to execute the macro ScrBar subroutine (below) on the While Adjusting event. Open the dialog by executing the OpenDialog macro and the scrollbar will move the Label1 control up and down in proportion to the page.
Sub OpenDialog
DialogLibraries.LoadLibrary("Standard")
oVariable = DialogLibraries.Standard.Dialog1
oDialog1 = CreateUnoDialog( oVariable )
oDialog1.Execute()
End Sub
Sub ScrBar (oEventObj As Object)
Static bInit As Boolean
Static PositionLbl1Y0 As Long
oSrc = oEventObj.Source
oSrcModel = oSrc.Model
scrollRatio = oEventObj.Value / oSrcModel.ScrollValueMax
oContx = oSrc.Context
oContxModl = oContx.Model
oLbl1 = oContx.getControl("Label1")
oLbl1Model = oLbl1.Model
REM on initialization remember the position of the label
If bInit = False Then
bInit = True
PositionLbl1Y0 = oLbl1Model.PositionY
End If
oLbl1Model.PositionY = PositionLbl1Y0 - (scrollRatio * oContx.Size.Height)
End Sub
The example provided by the SDK does not run on my setup, but the principles are sound.
There appears to be a second improvised method closer to the functionality one might expect. This method uses the DialogModel.scrollTop property. The property appears to iterate the entire box up or down as a scroll based on the user input. There are two problems using this methodology, however. First, unless you put the scrollbar somewhere else, the scroll bar will scroll away along with the rest of the page. You will need to adjust the location of the scrollbar precisely to compensate for/negate the scrolling of the entire page. In the example below I tried but did not perfect this. Second, the property seems to miss inputs with frequency and easily goes out of alignment/ enters a maladjusted state. Perhaps you can overcome these limitations. Here is the example, relying on the same setup described above.
Sub ScrBar (oEventObj As Object)
Static scrollPos
oSrc = oEventObj.Source
oSrcModel = oSrc.Model
scrollRatio = oEventObj.Value / oSrcModel.ScrollValueMax
If IsEmpty(scrollPos) = False Then
scrollDiff = oEventObj.Value - scrollPos
Else
scrollDiff = oEventObj.Value
End If
scrollPos = oEventObj.Value
oContx = oSrc.Context
oContxModl = oContx.Model
oContxModl.scrollTop = scrollDiff * -1
oSrcModel.PositionY=(scrollRatio * oContx.Size.Height/5) * -1
End Sub
This (sort of) will scroll the contents of the entire dialog box, within limits and with the caveats noted above.

Why scrolling a TListView calls for an entire form repaint - Delphi Firemonkey

Starting with a blank mobile app, I added a TlistView, a TCircle, and a TMemo. None of the controls had any alignment other than the default and they are all direct children of the main form.
In the OnPaint event of the ListView, I put this:
Memo1.Lines.Add('ListView paint'),
For the circle OnPaint:
Memo1.Lines.Add('Circle paint');
When I ran the app the results were that scrolling the listview resulted in lots of "Listview paint" and "Circle paint" getting added to the memo, both items always getting added at the same time. Why does scrolling a listview (or scrolling the memo) call an entire form repaint?
There's a line in the call stack that makes me think the entire form is getting repainted:
Fmx.Platform.Ios.TFMXView3D.drawRect(0x14659ec0,{origin = {x = 0, y = 0}, size = {width = 768, height = 1024}})
I'm want to know if this is supposed to be happening or not. This is causing poor performance in a listivew I have because a chart is constantly getting repainted.
Judging by the comments this appears to be a issue with Firemonkey, however it has been specified as by design. From the QC issue
GPU Canvas in FireMonkey that is used on mobile platforms always repaints the entire form, which is by design.
As a workaround, there is TRasterEffect, which can be dropped on the form and parented to one of the controls. If done so, the control will be rendered to internal image first and then such image will be drawn, alleviating the problem.

How to solve non-alignment of Delphi child form within parent form in XP

I have a number of forms that 'host' other forms using ChildForm.Parent := HostForm and ChildForm.Align = alClient etc. Some child forms are host themselves for another child form. Under Windows 7, the alignment is fine but Windows XP sometimes leaves the child form visible but not client aligned. Touch the outer window frame to resize it by one pixel and it all springs aligned correctly. Is there a command I can send to the host form to ensure that the alignment has taken place properly?
You might want to take look at Realign method. Look also at AlignControls and ArrangeControl to manually align the child control. Consider also using Frames instead of Forms if it is possible - they are designed for the job you described (but on the other hand have a number of inconveniences - for example lack of OnCreate event).
Best regards -- Spook.
I also had this problem a few times and it seems it is often caused by faulty controls on the child form.
How to avoid issues when embedding a TForm in another TForm?

Resources