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.
Related
I have not come across a solution for an Event (OnScroll) handler for a TScrollBar under Delphi 10 Seattle. I chose it because it is not a TScrollBox for which I will put any content into (like a Panel or DBGrid). It is merely going to detect the mouse action (None of the MouseUp/Down/Side-to-Side have worked either) and it is going to be used as file seek tool for which it will force a bunch of TeeCharts to update, based on the direction of the scroll.
So far I have only come across incomprehensible examples consisting of WindowsMessages (WM_HScroll) and VLC.StdCtrls.TScrollBar.OnScroll
What is the efficient way to detect the ScrollBar moving besides OnChange?
It would help to be able to manipulate the ScrollBar and have it reset itself in the middle. Much like it would if I set the value much like if I set the Min = -50 and Max = 50 (0)
To overcome the limitations of the FMX TScrollBar, you could use the following as a basis for further enhancement (partly inspired by the link you provided).
If the control(s) you will scroll with the scrollbar has (or can be equipped with) a property to store current scroll position you may use/define such, of type single. For testing I just defined it as a private property of the form, and called it sbIncremental.
I set the properties of the TScrollBar as Orientation = Vertical, SmallChange = 10, Max = 100 and Min = -100
Then, the OnChange event looks like this in my test:
procedure TForm6.ScrollBarChange(Sender: TObject);
var
sb: TScrollBar;
sbOnChange: TNotifyEvent;
begin
sb := (Sender as TScrollBar);
sbIncremental := sbIncremental + sb.Value;
Label1.Text := FloatToStr(sbIncremental); // Use the new value
sbOnChange := sb.OnChange; // Disable OnChange event
sb.OnChange := nil; // -"-
sb.Value := (sb.Min + sb.Max) / 2; // Reset position
sb.OnChange := sbOnChange; // Re-enable OnChange event
end;
To reset the ScrollBar position to the center, we need to disable the OnChange event temporarily.
Update after comments.
As I now understand, your issue is that the visual appearance of the scrollbar doesn't return to the zero position even if it's value is changed (within OnChange) to zero (with min=-100 and max=100 the zero position is in the middle). It does when clicking on the arrow buttons, but it does not if dragging the thumbgrip or clicking on either side of the thumbgrip. Visual update seems to be prevented in these two cases within the OnChange event. Also, calling ScrollBar.Repaint does not update the visual appearance. I found no way to use the OnMouseDown/OnMouseUp events. They don't appear to be linked internally?
This leaves us with following (hacky) solution: Fire a timer with a small delay, say 300 ms. When timeout occurs, the scrollbar is ready to accept new value changes and update visually. The timer also has the positive effect, that the thumbgrip moves as you click, and then moves back. Without any time between there would be no visual indication that something happens.
The OnChange event handler when using a timer
procedure TForm6.ScrollBarChange(Sender: TObject);
begin
// Use the scrollbar value
Label1.Text := FloatToStr((Sender as TScrollBar).Value);
// Enable reset timer
ScrollBarTimer.Enabled := False;
ScrollBarTimer.Enabled := True;
end;
And the OnTimer event
procedure TForm6.ScrollBarTimerTimer(Sender: TObject);
var
sbOnChange: TNotifyEvent;
begin
ScrollBarTimer.Enabled := False;
sbOnChange := ScrollBar.OnChange; // store OnChange event
ScrollBar.OnChange := nil; // disable OnChange event
ScrollBar.Value := (ScrollBar.Min + ScrollBar.Max) / 2; // reset position
ScrollBar.OnChange := sbOnChange; // re-enable OnChange event
end;
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.
I have a tab-oriented Firemonkey multi-platform application. I have some tabs which might have individual notifications. I'm aware of the Notification Center for mobile apps, but internally within my own app, I'm not sure how to do a similar indicator on tab items.
For example, I have a TTabControl with 4 tabs. Each tab has a custom icon. On each tab next to the icon, I would like to show a notification indicator, similar to that shown on the application icon when using Notifications.
Something like this:
How can I show such an indicator on tabs for iOS and Android?
I've tried putting a label on it, but there's no way of knowing how to programatically position that label when the form is resized / rescaled / rearranged for different platforms. If the TTabItem had a Position and Size property, together I could position such a label. But without it, how can I position anything relevant to this tab?
PS - Please pardon my horrific graphics :-)
I figured out how to position a label over a tab item. It's a bit sloppy, but it works. First of all, the TTabControl.TabPosition must NOT be PlatformDefault. It must be either Top or Bottom. Secondly, the label must reside in the same parent control as the TTabControl. Then, implement a function like so:
procedure TfrmMain.PositionNotifyLabel(ALabel: TLabel; ATabItem: TTabItem);
var
TabLeft, TabTop: Single;
X: Integer;
T: TTabItem;
TabControl: TTabControl;
begin
TabControl:= ATabItem.TabControl;
//Calculate left position of tab
TabLeft:= 0;
for X := 0 to TabControl.TabCount-1 do begin
T:= TabControl.Tabs[X];
if T = ATabItem then Break;
TabLeft:= TabLeft + T.Width;
end;
case TabControl.TabPosition of
TTabPosition.Top: begin
TabTop:= 0;
end;
TTabPosition.Bottom: begin
TabTop:= TabControl.Height - TabControl.TabHeight;
end;
else begin
//Not Supported
TabTop:= 0;
end;
end;
//Calculate label width based on tab width
ALabel.Width:= ATabItem.Width / 3;
//Position label to calculated Left/Top positions
ALabel.Position.X:= TabLeft + ATabItem.Width - ALabel.Width;
ALabel.Position.Y:= TabTop;
ALabel.BringToFront;
end;
Call this function on form show and on form resize like so:
PositionNotifyLabel(lblCartNotify, tabCart);
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);
What options/properties should use to show a main from scrolbars when I want to?
or always visible in Delphi 2010
The help is as too often useless
thanks
Pw
#Philippe, you can use the ShowScrollBar function and the HorzScrollBar, VertScrollBar propeties to do this.
check this code
procedure TForm1.FormCreate(Sender: TObject);
begin
HorzScrollBar.Range := 10000; // set the range to an higher number
VertScrollBar.Range := 10000; // set the range to an higher number
ShowScrollBar(Handle, SB_BOTH, True);
end;
If you set AutoScroll = true, they should show up if needed. That is, if any visual component is placed outside of the visible client area.
If you do not have any components 'off-screen', why would you need the scrollbar showing?
Anyway, you can set Horz-/VertScrollBar.Range to anything larger than the clientheight/width, and they will show up.
If you need the scrollbar for something else, you can always drop a TScrollBar component on the form.