Display second form as full screen with slide animation - delphi

I have a simple application that contain 2 forms, both forms has these properties:
BorderStyle := bsnone;
WindowState := wsMaximized;
I set these properties to make both of them full screen. In the main form there is a button and when I click I want to show the second form as full screen with slide animation so I used this code:
AnimateWindow(form2.Handle, 500, AW_ACTIVATE OR AW_SLIDE OR AW_HOR_NEGATIVE);
Form2 is in auto create and visible property is set to false.
The problem when I tried this I saw odd results, the animation play but the form2 appear without any controls and not covering the full screen.
How to fix that so I can display form2 as full screen with slide animation ?
I am using XE5

Based on MSN
https://msdn.microsoft.com/en-us/library/windows/desktop/ms632669(v=vs.85).aspx
there are lots of problems reported of using this function. So I recomend you go and implement the animation yourself.
Since you are interested only in sliding information do next:
Fist change your form size to fit into the monitor size.
MyForm.Width := Screen.Width;
MyForm.Height := Screen.Height;
Then move your form to the edge of the screen you want the animation to start from. When doing so keep atleast one pixel of the form inside the visible area of the monitor.
//Strating animation from left border
MyForm.Left := 1 - MyForm.Width;
MyForm.Top := 0;
//Starting from right border
MyForm.Left := MyForm.Width - 1;
MyForm.Top := 0;
//Starting from top border
MyForm.Left := 0;
MyForm.Top := 1 - MyForm.Height;
//Starting from bottom border
MyForm.Left := 0;
MyForm.Top := MyForm.Height - 1;
Once your have positioned your form in starting possition make it visible and enable timer that will beused to update forms position multiple times (animate) until it gets into desired position
MyForm.Show;
AniTimer.Enabled;
And start animation which is basically just updating your form position by using a simple timer
//Left to right animation
procedure MyForm.AniTimerOnTimer(Sender: TObject);
//Constant used to define by how many pixels will the form be moved
//on each timer interval
const MoveStep: Integer = 5;
begin
if MyForm.Left < MoveStep then
begin
MyForm.Left := MyForm.Left + MoveStep;
end;
else
begin
MyForm.Left := 0;
AniTimer.Enabled := False;
end;
end;
Use similar approach for other directions if needed.
Instead of defining MoveStep as constant you can make it as a variable and then dynamically calculate its value so that animation is finished in N steps.
MoveStep := Screen.Width div N;
If you would like to have diagonal animation you would need two MoveStep variables. One for horizontal and one for vertical axis. And you need to make sure that both are being calculated in order to finish animation in specific number of steps
MoveStepX := Screen.Width div N;
MoveStepY := Screen.Height div N;
So now you can controll your animation speed by changing MoveStep and timer interval.
Note I don't recomend setting timer interval to small. Why?
As ypu probably know TTimer component isn't known for its acruacy so it could lead to noticable speed variation of of your animation.
Also changing form position multiple times woulrd require part of it being rerendered hwen it comes into visual rage so it could generate significant load to the CPU.
Moving fomr a few less times and with larger increments could greatly reduce the CPU load whle still performing adequate aniamtion smothness.
So do some testing to find the best combination of timer interval and move step.

Your problem is, before Form2 is first shown, the VCL does not create API windows of the windowed controls. Because it doesn't need to. Remember 'visible' is still set to false when you call AnimateWindow.
Below is a not very elegant workaround which sets 'visible' while the form has 0 width and height. It also addresses an additional problem which I don't know why you are not having. It is that I cannot animate a maximized window at all, which seems logical to me - a maximized window does not move. Anyway, to test it I suggest setting 'wsNormal' as WindowState at design time.
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if Form2.Visible then begin
Form2.WindowState := wsNormal;
AnimateWindow(Form2.Handle, 500, AW_HIDE OR AW_SLIDE OR AW_HOR_NEGATIVE);
Form2.Close;
end else begin
Form2.Width := 0;
Form2.Height := 0;
Form2.Visible := True;
ShowWindow(Form2.Handle, SW_HIDE);
Form2.WindowState := wsNormal;
Form2.Width := Form2.Monitor.Width;
Form2.Height := Form2.Monitor.Height;
AnimateWindow(form2.Handle, 500, AW_ACTIVATE or AW_SLIDE OR AW_HOR_NEGATIVE);
Form2.WindowState := wsMaximized;
end;
end;

AnimateWindow is a bit of a loner. It doesn't play nicely with the rest of the team in Delphi. Even though the MSDN doc for it says that it shows the form it actually doesn't do it properly. It only does the animation. I suppose it would do it nicely if you went all out Windows API and knew all that is required.
A few things to keep in mind:
I suppose you noticed that the slide effect doesn't show properly
with the borders enabled.
It doesn't know about the WindowState property of the form so it will not Maximize the form if you wanted it to.
It doesn't show controls after the call, only graphic controls
It knows nothing of Delphi and how Delphi handles the showing and
hiding of its forms
So the trick is:
Take away borders like you have done.
Before showing the form for the first time you must specify its size.
Seeing that you want it maximized, just set it to the screen size
where it will display and set its position to the four corners of
that screen. This can be done on Form2's OnCreate.
So upon clicking the button you first call the AnimateWindow then call normal Form2.Show
There might be other fixes but this is the one I know of.

Related

Make footer bar disappear on scrolling in a Delphi / Firemonkey app

I have a simple Delphi / Firemonkey app that has a lot of data within a TListView. To maximize the available space on screen, I want to make the footer bar disappear with animation if the user scrolls down. Just the same way the LinkedIn app does (at least on Android) for example. Is there an easy way to get there? Some mechanism that is integrated in then Firemonkey framework (like TMultiView)?
I'm using Delphi 11.2.
Thank you!
In FireMonkey you can achieve this quite easily by using Animations like TFloatAnimation which you can attach to any visual FMX component.
For instance if your footer bar is a StatusBar this is how you will done it:
In Form design select your status bar component
Find TFloatAnimation component in your component palette and double click on it. This will add Float Animation to the status bar. If done correctly Float Animation will be shown as child component of your status bar.
Change the PropertyName property of the added Float Animation to point to StatusBar.Size.Height property. This tells the Float Animation that it will be updating Status bars height property.
Now write two new procedures which you can then call to fire the prepared animation when needed.
The procedures would look something like this:
procedure TForm2.HideStatusBar;
begin
//Store initial height of the status bar as start value so we can use it later
//for restoring status bar to initial size
FloatAnimation2.StartValue := StatusBar1.Height;
FloatAnimation2.StopValue := 0;
//Set inverste property to false.
FloatAnimation2.Inverse := False;
//Start the animation
FloatAnimation2.Start;
end;
procedure TForm2.ShowStatusBar;
begin
//In order to reverse animation we need to set Inverse property to True
//We don't store any StartValue or StopValue since we have done that already
//when we decided to hide the status bar
FloatAnimation2.Inverse := True;
//Start the reverse animation
FloatAnimation2.Start;
end;
The reason why I store initial height of the status bar within HideStatsBar procedure instead of setting it at design time is to allow scenarios where you may allow your users to change the size of the said status bar.
NOTE: If your footer bar is a panel with Bottom alignment be aware that animating panel breaks the alignment and other components don't update their size properly as they should. I haven't figured out why this is happening and how to fix it.
Thanks to the help of SilverWarior, I got it finally working perfect just for my needs. To apply to Tom Brunberg's questions I will provide a full solution here including SilverWarior's slightly modified code of course.
I'm using the "HeaderFooter Template" of Delphi 11.2 but had to change the footer TToolbar to a bottom-aligned TPanel. It did not work with a TToolbar on Android. I placed the TListview in the middle of the form.
procedure THeaderFooterForm.FormShow(Sender: TObject);
begin
// just add some testing data
ListView1.BeginUpdate;
for var iCnt := 0 to 4711 do
ListView1.Items.Add.Text:='ListView Item ' + iCnt.ToString;
ListView1.EndUpdate;
FScrollPos:=0;
end;
procedure THeaderFooterForm.ListView1ScrollViewChange(Sender: TObject);
begin
var iNewPos:=ListView1.ScrollViewPos;
if FScrollPos > iNewPos then
begin
HeaderLabel.Text:='OnScroll Up';
Self.ShowStatusBar;
end
else
begin
HeaderLabel.Text:='OnScroll Down';
Self.HideStatusBar;
end;
FScrollPos:=iNewPos
end;
procedure THeaderFooterForm.HideStatusBar;
begin
// hiding the footer if height is already zero makes the footer simply do
// nothing on Android (but works in Win64)
if Footer.Height < 2 then
Exit;
// store initial height of the status bar as start value so we can use it later
// for restoring status bar to initial size
FloatAnimation1.StartValue := Footer.Height;
FloatAnimation1.StopValue := 0;
// start the animation
FloatAnimation1.Start;
// just for debugging
HeaderLabel.Text:=HeaderLabel.Text + ' AnimHideStart';
end;
procedure THeaderFooterForm.ShowStatusBar;
begin
// showing the footer if already visible makes it simply do nothing (but works in Win64)
if Footer.Height > 40 then
Exit;
// using the (far more elegant) .Invers machanism did not work
FloatAnimation1.StartValue := 0;
FloatAnimation1.StopValue := 48;
// start the animation
FloatAnimation1.Start;
// just for debugging
HeaderLabel.Text:=HeaderLabel.Text + ' AnimShowStart';
end;
This minimal example code placed within the form works on Windows 11 x64 and x86 and on my Android 13 device. Would be interesting on an iOS device of course.

Delphi XE5 Firemonkey TTabItem and TEdit repaint coordination

I created a TTabControl with two TTabItems. On each TTabItem there is one (or more) TImageViewers and several TEdits. When I click on the TImageViewer, a modal screen pops up, I set some values, and I want to report those values to the user through the TEdits. So on returning from the Modal screen,
I execute
editn.text := whateveritis;
I then say
editn.repaint;
Nothing happens. I say TTabItem.repaint. Nothing happens. I click the other TTabIem and then come back to the first TabItem and, voila, the Edit control contains the right information. So my editn.text := whateveritis must be working (that's the only write to the TEdit), but I can't get the blinkin' control to show the result without going off-tab. How do I get it to redisplay as soon as I change the content? Do I need to write an OnChange routine that is one line, self.repaint? Seems ugly, and I'd hope there's a more global approach. Suggestions?
In light of initial comments, let me give more details. Setup: In main screen, drop in tabcontrol, and in tabcontrol drop in 2 tabitems. In tabitem1, drop in a timageviewer and 4 tedits (plus other stuff, probably irrelevant). Image gets dropped into the imageviewer (and displays correctly). The onclick event activates the following (ellipsis cuts out irrelevant code):
procedure TSCKMain.ImageViewer1Click(Sender: TObject);
var
lochold, scrollhold : tpoint;
backfromwavform : tmodalresult;
begin
lochold.X := mouseloccallback.x;
lochold.Y := mouseloccallback.y;
scrollhold.X := round(imageviewer1.ViewportPosition.X);
scrollhold.Y := round(imageviewer1.ViewportPosition.Y);
…
repeat backfromwavform := Wavform.Showmodal until backfromwavform<>mrnone;
case backfromwavform of
mrOK : begin {blue end}
Specsingle.BlueEnd.X := lochold.X;
Specsingle.BlueEnd.y := lochold.y;
edit13.Text := inttostr(Specsingle.BlueEnd.X);
Edit14.Text := inttostr(Specsingle.BlueEnd.y);
PublicWindowFlag := 'RePlot';
end;
mrContinue : begin {red end}
Specsingle.RedEnd.X := lochold.X;
Specsingle.RedEnd.y := lochold.y;
edit15.Text := inttostr(Specsingle.RedEnd.X);
Edit16.Text := inttostr(Specsingle.RedEnd.y);
PublicWindowFlag := 'RePlot';
end;
…
end;
if PublicWindowFlag<>'Cancel' then
if PublicWindowFlag='RePlot' then
begin
specsingle.RegenImage;
end
else
showmessage('Single image semaphore error. Debug.');
Imageviewer1.scrollto(scrollhold.X-Imageviewer1.viewportposition.X, scrollhold.y-Imageviewer1.ViewportPosition.Y);
end;
The modal screen sends back either mrContinue or mrOK correctly, and the appropriate case executes. However, edit13, edit14, edit15, and edit16 do not change their content. However, if I click over to Tabitem2 and back to Tabitem1, they DO repaint and DO contain the correct characters, which they could only have gotten from the above code. Conclusion: somehow, the edits aren’t repainting independently, but it’s not clear why.
Got it. The canvas for the imageviewer, the canvas for the bitmap in the imageviewer, and the canvas for the parent form are all in play. One must be sure that the canvas is the right one. As soon as scenes got untangled between imageviewer.bitmap and everything else, the edits worked as one would expect.

How do I make two controls each occupy half their parent's area?

I have an application that has a sidebar attached to it (TPanel --> alRight), that uses a CategoryPanel (alClient) inside of it. This CategoryPanel has exactly 2 Groups which are non aligned. I would like to share the boundary of these 2 groups so it would fill up the whole panel space in a 50/50 ratio. Unfortunately The CategoryGroups do not support alignment in designtime, which forces me to run my application every time I want to test it. I tried to set each CategoryGroup to the height to half of the panel, but it displays scrollbars. (See Picture 2)
How can I align/share the boundary in a ratio of 50/50 properly?
According to your comments, you want to run this code:
procedure TForm1.UpdateGroupHeights;
begin
if not CategoryPanel1.Collapsed then
CategoryPanel1.Height := CategoryPanelGroup1.ClientHeight div 2;
if not CategoryPanel2.Collapsed then
CategoryPanel2.Height := CategoryPanelGroup1.ClientHeight -
CategoryPanelGroup1.ClientHeight div 2;
end;
whenever anything changes that you wish to affect the layout of your groups. So I think you need to call this function from the following events:
The OnCreate event of the form.
The OnResize event of the TCategoryPanelGroup.
The OnCollapse and OnExpand events of the two category panels.
That looks a bit weird though when one panel is collapsed, and the other is expanded. Personally I'd rejig the code to fill all available space.
if not CategoryPanel1.Collapsed then
;//nothing to do
if CategoryPanel1.Collapsed and not CategoryPanel2.Collapsed then
CategoryPanel2.Height := CategoryPanelGroup1.ClientHeight-CategoryPanel1.Height;
if not CategoryPanel1.Collapsed and CategoryPanel2.Collapsed then
CategoryPanel1.Height := CategoryPanelGroup1.ClientHeight-CategoryPanel2.Height;
if not CategoryPanel1.Collapsed and not CategoryPanel2.Collapsed then
begin
CategoryPanel1.Height := CategoryPanelGroup1.ClientHeight div 2;
CategoryPanel2.Height := CategoryPanelGroup1.ClientHeight-CategoryPanel1.Height;
end;

Workaround for anchors being broken when recreating a window?

This happens in all Delphi up to XE3:
Create a form and put a panel on it. Anchor the panel to [akLeft, akTop, akRight, akBottom], but leave space between it and the borders.
Add a button which calls RecreateWnd()
Run the app. Resize the form so that the panel is hidden because it's less than 0 pixels in size due to anchoring. Press the RecreateWnd button.
Resize the form back and note that the panel's anchoring is broken.
As long as I can remember myself using Delphi, anchors were always impossible to use because of this. Resize the form, then dock it: the window is recreated, your layout is broken.
I wonder if there's some sort of workaround?
Update
Two workarounds are available in the comments, one proven and stable but with form blinking, another one experimental but potentially more thorough and clean.
I'm not going to vote for either one for a while, as one of those is mine and I'm not even sure it is stable. Instead, I'll wait for some public input.
The two options I have used of which neither is really ideal for problems with the bottom and right anchors are:
Make the window big again before calling or causing to be called RecreateWnd();, then make it small again. Has to be visible before you make it small again however.
Set the Form's constraints so it can't be re-sized so small that stuff ends up hidden.
An example that flashes the larger form, use height and width large values enough so the panel is not hidden:
procedure TForm1.Button1Click(Sender: TObject);
Var
OldWidth, OldHeight : integer;
begin
OldWidth := Form1.Width;
OldHeight := Form1.Height;
Form1.Visible := false;
Form1.Width := 1000;
Form1.Height := 800;
RecreateWnd();
Form1.Visible := true;
Form1.Width := OldWidth;
Form1.Height := OldHeight;
end;
Turns out that the function which breaks everything is UpdateAnchorRules. TControl stores FOriginalParentSize and it's own original size in FAnchorRules, and uses that to auto-resize as parent resizes. UpdateAnchorRules() takes current parent size and current control Width and Height and saves those into FOriginalParentSize and FAnchorRules.
If everything worked properly that would have no effect during normal resizes, as the control and it's parent change size in accord.
But when the control Width is less than zero due to anchoring, Windows, and consequently Delphi still considers it 0. If UpdateAnchorRules is called at that point, it saves wrong, out-of-accord 0 value for original width. After this the layout is beyond repair.
(If it's not called, Width continues to be updated in proper relation to parent Width due to original sizes preserved)
Turns out anything which involves creating a window handle calls UpdateAnchorRules twice: first in WinAPI CreateWindow as it dispatches WM_SIZE before returning (and WM_SIZE handler calls UpdateAnchorRules), and second, explicitly in CreateHandle after handle creation.
It would seem that as long as we can disable UpdateAnchorRules for the duration of CreateHandle, we will succeed. But there are explicit calls to UpdateAnchorRules in CreateHandle, which means someone thought there needs to be an adjustment of Anchor rules after handle creation.
So perhaps I'm missing something, and by disabling it something will break?
Anyway, there are two ready ways to disable UpdateAnchorRules: to set FAnchorMove or to set csLoading. First one is no good as there's code which clears it midway through RecreateWnd and then calls UpdateAnchorRules again.
Second one works and here's a solution:
type
TComponentHack = class helper for TComponent
public
procedure SetCsLoading(Value: boolean);
end;
procedure TComponentHack.SetCsLoading(Value: boolean);
var i: integer;
begin
if Value then
Self.FComponentState := Self.FComponentState + [csLoading]
else
Self.FComponentState := Self.FComponentState - [csLoading];
for i := 0 to Self.ComponentCount-1 do
if Self.Components[i] is TControl then
TControl(Self.Components[i]).SetCsLoading(Value);
end;
procedure SafeRecreateWnd();
begin
MyControl.SetCsLoading(true);
try
MyControl.RecreateWnd(); //or any operation which triggers it -- such as docking or making the window visible first time after RecreateWnd()
finally
MyControl.SetCsLoading(false);
end;
end;
Disclaimer:
I have no idea what else will be broken by running TControl operations with csLoading set.
Better alternative would be to hook UpdateAnchorRules procedure and add another flag check specifically for this purpose, but that'd require either reimplementing UpdateAnchorRules completely (prone to breaking on different versions of Delphi with different original UpdateAnchorRules) or inventing some way to call original UpdateAnchorRules which is usually destroyed by rewriting it with a hook.

String Grid using Styles in Delphi XE2 - Scroll bar doesn't update

I'm making a simple application in Delphi XE2 which uses specifically the "Carbon" style. There's a large String Grid which has thousands of rows. I have a process which loops through the records of this grid, does some work, and makes some changes in the grid. As the process loops, the row which is being currently processed gets highlighted (by setting TStringGrid.Row).
The problem is that when I apply the style to this grid, the scroll bar doesn't change position as the row is changed. The loop does properly highlight each row as it's being processed, but by the time it gets towards the end of the list, the scroll bar on the right is still all the way at the top.
How do I make the grid's scroll bar move along with it?
Here's a sample of how I'm looping:
procedure TForm1.Button1Click(Sender: TObject);
var
X: Integer;
begin
FStop:= False;
for X:= 1 to Grid.RowCount - 1 do begin
if FStop then Break; //Ability to stop loop
Grid.Row:= X; //Highlight current row
DoSomeLenghyWork;
ChangeSomethingOnGrid;
Application.ProcessMessages; //Keep program responding
end;
end;
Everything works just fine when I'm not using any styles.
If invalidate and repaint don't do anything for you, try resizing the string grid:
Grid.Width := Grid.Width - 1;
Grid.Width := Grid.Width + 1;
Try playing with the string grid options that hide and show the scrollbars. Hide them before you update and show them after. Perhaps that will force them to repaint.
Try moving the scroll position and moving it back to the original position.
This worked for me - it forces windows to repaint the border area of the StringGrid:
SetWindowPos(Grid.Handle, 0, 0, 0, Grid.Width, Grid.Height, SWP_DRAWFRAME);

Resources