Delphi How to force Main form scrollbars to be visible - delphi

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.

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.

Form positioning when using TStyleManager in Delphi XE7 application

I'm trying to make a Delphi application form remember it's position and recall it the next time the application is started:
procedure TForm1.LoadSettings;
var
xml : IXMLDocument;
node : IXMLNode;
begin
[ ... initializing XML ...]
// Loading theme
if node.HasAttribute('Theme') then TStyleManager.SetStyle(node.Attributes['Theme']);
// Loading position & size
if node.HasAttribute('PosTop') then self.Top := StrToInt(node.Attributes['PosTop']);
if node.HasAttribute('PosLeft') then self.Left := StrToInt(node.Attributes['PosLeft']);
if node.HasAttribute('PosWidth') then self.Width := StrToInt(node.Attributes['PosWidth']);
if node.HasAttribute('PosHeight') then self.Height := StrToInt(node.Attributes['PosHeight']);
[ ... some other stuff ... ]
end;
Everything works fine when the "Theme" is set to default "Windows" theme. With any theme, the form appears at it's default position set up by Windows, close to the upper left display corner, no matter where the form was located before.
Any ideas about what may be the cause and how to fix that?
Thanks in advance!
If you want to properly restore form's position you have to set it initial Position property to poDesigned in form designer.
Default Position property is poDefaultPosOnly. That means your form position will be determined automatically by Windows. Depending at which point you trigger your own form positioning during the form creating process and depending on other code you may either successfully change forms position or not.
However, if the Position property is poDesigned all positioning will be left to Delphi code itself and you will be able to properly apply your setting.
Why?
Because if you use any code that will trigger form's window recreation, that code will again try to set your form's position to Windows default one and can interfere with your positioning process.
Calling TStyleManager.SetStyle does exactly that.
Simple test case for above would be creating new VCL project, adding some style to it and put following code in FormCreate event
procedure TForm1.FormCreate(Sender: TObject);
var Styled: boolean;
begin
Styled := true;
if Styled then TStyleManager.SetStyle('Silver');
Top := 200;
Left := 300;
Width := 800;
Height := 600;
end;
If Styled is false setting position works, if it is true it doesn't.

What is the equivalent OnScroll Event to TScrollBar for FMX?

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;

How to work around AutoSize's being off while invisible?

Delphi controls have AutoSize property which is exposed e.g. in TPanel. It adjusts the width/height of the panel depending on the content.
Apparently it does nothing when the panel is invisible, and does no realigning later when it's set to visible. So if I put some controls into it and then make it visible, the size is not adjusted.
I can trigger adjusting size by setting height to any value in FormShow:
procedure TForm1.FormShow(Sender: TObject);
begin
Panel1.Height := Panel1.Height + 1; //triggers auto-resize
end;
But I have to do this manually for every control which has AutoSize on. I'm bound to forget something.
Are there better ways to fix this, preferably once and for all?
I don't think there's much that you can do. A better way to for the re-sizing is to add a call to the Realign method of the panel immediately after you make it visible.
You could hook into the CM_VISIBLECHANGED message and force the matter there, for auto-sized controls. For instance, using an interceptor:
type
TPanel = class(Vcl.ExtCtrls.TPanel)
protected
procedure CMVisibleChanged(var Message: TMessage); message CM_VISIBLECHANGED;
end;
procedure TPanel.CMVisibleChanged(var Message: TMessage);
begin
inherited;
if Visible and AutoSize then
Realign;
end;
Its been a while since i used delphi, but one thing i remember is that the controls did play a bit of mind games on me, most of the time it was because the rendering engine did not refresh the form and the controls.
If you have the control set to auto-resize i would suggest checking if form1.refresh or panel1.refresh since its been a few years since i played with it ( delphi 7 ) i might confuse refresh with repaint. which some controls had, which initiated the size calculation before repainting the control. since delphi controls are open source you can go in to the apropriate pas file to find the control and look at refresh/repaint to see if you can persist the auto-resizing.
hope that helped.

How do you make a TFrame's OnResize event occur when it first appears?

I have a frame that is being placed on a form. I expect to be placing a number of instances of this frame on the form.
It has a drawgrid with 2 columns and in the OnResize event I make the 2nd column extend to the end of the available space. That works when the form is manually resized with the frame Align set to alTop. But when the form first appears even though FrameResize gets called it has no effect. (Though it did have the desired effect when I put a break point on it).
So, what I am doing now is calling the FrameResize from the forms OnShow handler, but that is ugly. The frame should be able to appear correctly formed without help from the Form.
Any ideas? I did try overriding SetParent, but that didn't work. Using Xe2.
TIA
Mark
I have solved it with advice from Peter Below, Delphi Team B Delphi member.
I overrode the frame's set bounds. It was getting called even before the component variables were set, so it looks like this
procedure TfaDupDisplay.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); // Had to use SetBounds because OnRezise was not working
var grid: TDrawGrid;
begin
inherited;
if pnlWebData = nil then
exit;
pnlWebData.Width := Width div 2;
for grid in TArray<TDrawGrid>.Create(grdData, grdDup) do
grid.ColWidths[1] := grid.Width - grdData.ColWidths[0];
end{ SetBounds};

Resources