It is easy to detect whether the vertical scrollbar of a TScrollBox is at the very top or not:
IsScrollBarAtTop := ScrollBox1.VertScrollBar.Position = 0;
But how can I detect whether the vertical scrollbar of a TScrollBox is at the very BOTTOM or not?
You can retrieve scroll bar information through the API and determine if its at the bottom.
function IsScrollBarAtBottom(Box: TScrollBox): Boolean;
var
Info: TScrollInfo;
begin
Info.cbSize := SizeOf(Info);
Info.fMask := SIF_POS or SIF_RANGE or SIF_PAGE;
Win32Check(GetScrollInfo(Box.Handle, SB_VERT, Info));
Result := Info.nPos >= Info.nMax - Info.nMin - Info.nPage;
end;
From Vcl.Forms.TControlScrollBar.Range:
Range represents the virtual size (in pixels) of the associated control's client area. For example, if the Range of a form's horizontal scroll bar is set to 500, and the width of the form is 200, the scroll bar's Position can vary from 0 to 300.
IsScrollBarAtBottom := ScrollBox1.VertScrollBar.Position =
(ScrollBox1.VertScrollBar.Range - ScrollBox1.ClientHeight);
If the range is less than the height of the scrollbox, the scrollbar is not visible.
Related
I have an RTF document with defined page settings:
(...}\paperw16840\paperh11907\margl794\margt709\margr794\margb983\viewkind4\\uc1\trowd\....)
In my app I use a TRichEdit to show the document.
The TRichEdit has a TPanel as its Parent, and is using Align=alClient and AlignWithMargins=True.
I set the Panel's Width to 16840 * PixelsPerInch/1440 (1123 pixels) and I see that is equal to the page's width, as shown in MSWord (scale=100%).
Setting the RichEdit's Margins to 794 * PixelsPerInch/1440 (53 pixels), the Width of the RichEdit is smaller than it must be, or the margins are bigger than they must be (compared with MSWord).
No borders, no other margins, except what I set in code:
function pixelsOf(prop : string) : integer;
var
i,j,l : integer;
begin
result := -1;
l := length(prop);
i := pos(prop,s);
if i > 0 then begin
j := i+l;
while s[j] in ['0'..'9'] do inc(j);
result := round(strToIntDef(copy(s,i+l,j-i-l),-1)*PixelsPerInch/1440);
end;
end;
paperW := pixelsOf('\paperw'); // pixelsOf() calcs twips*pixelsPerInch/1440
PanelPreview.Width := paperW;
Lm := pixelsOf('\margl');
RichEdit1.Margins.Left := Lm;
Rm := pixelsOf('\margr');
RichEdit1.Margins.Right := Rm;
Tm := pixelsOf('\margt');
RichEdit1.Margins.Top := Tm;
The value of paperW gives the correct Panel width (compared with MSWord), but the values of Lm and Rm give bigger margins, so the RichEdit becomes narrower.
How can I calculate the correct margins so the RichEdit has the same layout as MSWord?
This maybe helps. I noticed that :
TRichedit leaves a space about 10 pixels at the left side (the rendering is starting after this space). Is there a parameter that can be fix this other than margins.left ?
TRicheditdoesn't render any table wider than its width (MSword do
this adjusting the margins). So the TRichedit trancates everything
outside its margins.
The result of the above is that the left margin seems wider than must be and truncates the right side of the table if it is winder.
see the image
I have a grid that can be resized. And i'm now stuggeling with filling the blank space around columns in the grid. I'm trying to achieve this on FormResize.
First i calculate what is the total of columns width and then i'm comparing it to the string grid width. if the stringgrid widths is bigger then i add to each columns width equal portions of the blank space left. This is how it looks in formResize Procedure:
procedure TBDDTool.FormResize(Sender: TObject);
var
totColWidth,i : integer;
begin
totColWidth := 0;
for i := 0 to sgFilePreview.ColCount - 1 do
totColWidth := totColWidth + sgFilePreview.ColWidths[i];
if sgFilePreview.Width > TotColWidth then
begin
for i := 0 to sgFilePreview.ColCount - 1 do
begin
sgFilePreview.ColWidths[i] := round(sgFilePreview.ColWidths[i] +
((sgFilePreview.Width - totColWidth)/(sgFilePreview.colCount)));
end;
end;
end;
This actualy doesn't work cause sgFilePReview.Width is the width of my grid. And i don't know how to get the width of the whole space inside the grid, like every columns + blank space left. How can i get the real width of the grid? Cause sgFilePreview.Width return the width of the grid but as seen from outside the grid.
Thank you!
EDIT
Addine new columns
for val in sLineSplitted do
begin
if Pos('#',val) <> 0 then propVal := copy(val,0,pos('#',val)-1)
else propVal := val;
col := col +1;
if (row = 1) then
begin
if (col >1) then
//Add column
sgFilePreview.ColCount := col;
sgFilePreview.Cols[col-1].Text := propVal;
SetLength(aSourceData[row-1],col);
aSourceData[row-1,col-1] := val;
end
else
begin
sgFilePreview.RowCount := row;
SetLength(aSourceData[row-1],col);
aSourceData[row-1, col-1] := val;
sgFilePreview.Cells[col-1, row-1] := propVal;
pnlFileManager.Visible := true;
end;
end;
Auto size columns to fit word if the world is bigger than the cell's width
procedure TBDDTool.AutoSizeGrid(Grid: TStringGrid);
const
ColWidthMin = 10;
var
C,R,W, ColWidthMax: integer;
begin
for c := 0 to Grid.ColCount - 1 do
begin
ColWidthMax := ColWidthMin;
for R := 0 to Grid.RowCount - 1 do
begin
W := Grid.Canvas.TextWidth(Grid.Cells[C,R]);
if W > ColWidthMax then
ColWidthMax :=W;
end;
Grid.ColWidths[C] := ColWidthMax +5;
end;
end;
The main problem why these empty spaces are occurring to you even when you have too many columns so that all of them can be seen at the same time is the fact that in StringGrid scrolling works a bit different than you are used to in other controls.
When you scroll around in StringGrid the scrolling position is always aligned to the position of TopLeft visible cell. So if the combined width of visible cols isn't the same as ClientWidth this means that you will either have partially visible col at the right side or and empty space when you have scrolled all the way to the right.
Now one possible way to avoid this is to resize the columns so that they always fit into the client width (no partially visible columns). But the problem is that this becomes practically impossible if you have different widths for each column.
In case if you can live with the fact that all columns will have same width you can use the code below which works in most cases. It isn't perfect because you can only set column width to integer values where sometimes you would need larger precision.
procedure TForm1.FormResize(Sender: TObject);
var cwDefaultWidth: Integer;
VisibleCols: Integer;
ColWidth: Integer;
begin
cwDefaultWidth := 64;
VisibleCols := StringGrid1.ClientWidth div cwDefaultWidth;
if VisibleCols >= StringGrid1.ColCount then
begin
ColWidth := Round(StringGrid1.ClientWidth / StringGrid1.ColCount-1);
end
else
begin
ColWidth := Round(StringGrid1.ClientWidth / VisibleCols-1);
end;
StringGrid1.DefaultColWidth := ColWidth;
end;
But if you are using variable column widths then the only thing that you could do is adjust the size of the last column so that it's width fills the empty space that would otherwise appear.
In order to do that you would first have to check to see if you are scrolled fully to the right. Then you would have to sum up the width of currently seen columns. You could do this by using:
for I := StringGrid1.LeftCol to StringGrid1.RowCount-1 do
begin
VisibleColsWidth := VisibleColsWidth + StringGrid1.ColWidths[I];
end;
Then you subtract this width from StringGrid1.ClientWidth and you get the width of empty space. So finally you increase the size of last column for the empty space width.
I really hope that even if my answer doesn't provide you with an actual solution it would at least guide you towards finding the right solution.
There seems to be align property that works really well, but is is possible to align element so all elements on panel would be aligned to center all on bottom of each other if they all have less than size of container? Something like top-center-center.
Something like this:
Or at least horizontally, and vertically they can have 100%.
Put the elements into their own container, such as a TPanel or TFrame, that is a child of your main container. Set the child container's Align property to alCustom and use the parent container's OnAlignPosition event to keep the child container centered to itself:
// Panel1 is the Parent container for the child panel...
procedure TMyForm.Panel1AlignPosition(Sender: TWinControl; Control: TControl;
var NewLeft, NewTop, NewWidth, NewHeight: Integer; var AlignRect: TRect;
AlignInfo: TAlignInfo);
begin
if Control = ChildPanel then
begin
NewLeft := AlignRect.Left + ((AlignRect.Width - Control.Width) div 2);
NewTop := AlignRect.Top + ((AlignRect.Height - Control.Height) div 2);
end;
end;
There is no need for coding anything. Just place panels and other visual objects in the right way an set the properties of the visual objects as shown here:
Align: alNone or alCustom
and
Anchors: none (akLeft=False, akTop=False, akRight=False, akBottom=False)
Than and an object will stay at its relative horizontal and vertical position. If you place it in the middle in a container it will stay centered.
To center it only vertical set
Align: alNone or alCustom
and
Anchors: akTop=True OR akBottom=True
To center it only horizontal set
Align: alNone or alCustom
and
Anchors: akLeft=True OR akRight=True
You can center the control with this little procedure
procedure CenterControl( AControl : TControl );
begin
if Assigned( AControl.Parent )
then
begin
// remove alignment
AControl.Align := alNone;
// remove the anchors
AControl.Anchors := [];
// center on parent
AControl.Left := ( AControl.Parent.ClientWidth - AControl.Width ) div 2;
AControl.Top := ( AControl.Parent.ClientHeight - AControl.Height ) div 2;
end
else
raise Exception.Create( 'Control needs a Parent!' );
end;
If the parent gets resized the control will always be centered, as long as you did not change its size.
In RAD 10+ there is control TRelativePanel which has AlignVerticalCenterWithPanel and AlignHorisontalCenterWithPanel life-save options (and other useful capabilities).
You can also position invisilble line or dot in the center and build other controls around it with TRelativePanel provided properties Above/Below/etc.
Worth to mention, control is made on top level Embarcadero quality standards (crashes only in design mode).
Ok, here's the problem. I have a label component in a panel. The label is aligned as alClient and has wordwrap enabled. The text can vary from one line to several lines. I would like to re-size the height of the the panel (and the label) to fit all the text.
How do I get the necessary height of a label when I know the text and the width of the panel?
You can use the TCanvas.TextRect method, along with the tfCalcRect and tfWordBreak flags :
var
lRect : TRect;
lText : string;
begin
lRect.Left := 0;
lRect.Right := myWidth;
lRect.Top := 0;
lRect.Bottom := 0;
lText := myLabel.Caption;
myLabel.Canvas.Font := myLabel.Font;
myLabel.Canvas.TextRect(
{var} lRect, //will be modified to fit the text dimensions
{var} lText, //not modified, unless you use the "tfModifyingString" flag
[tfCalcRect, tfWordBreak] //flags to say "compute text dimensions with line breaks"
);
ASSERT( lRect.Top = 0 ); //this shouldn't have moved
myLabel.Height := lRect.Bottom;
end;
TCanvas.TextRect wraps a call to the DrawTextEx function from the Windows API.
The tfCalcRect and tfWordBreak flags are delphi wrappers for the values DT_CALCRECT and DT_WORDBREAK of the windows API. You can find detailed information about their effects in the DrawTextEx documentation on msdn
Use TextWidth and TextHeight.
See an example here:
http://www.greatis.com/delphicb/tips/lib/fonts-widthheight.html
TextWidth will tell you how wide the text would be, and then you can divide that by the control width to see how many rows you need. The remainder of the division should be an additional row.
You can use one line of code for this:
label.width := label.canvas.textwidth(label.caption);
or you can set the label's autosize property to true in the object inspector.
If you can align it alTop and keep AutoSize on then TLabel will auto adjust the height after settign the caption.
in FMX there is a trick to do that simply :
when creating a Label set Autosize := true and use the OnResize Event to update the size of the parent...
Rectangle1 := TRectangle.create(Form1);
Rectangle1.parent := Form1;
Label1 := TLabel.create(Rectangle1);
Label1.parent := Rectangle1;
Label1.Align := TAlignLayout.Top; // keep the same width and auto size parent height
Label1.OnResize := DoReSize;
Label1.WordWrap := true;
Lable1.Autosize := true;
The parent size will be updated here (assuming that the Sender object is the most bottom control in the parent, if not you need to arrange this function to summarize all the components size and verticaly)
procedure DoParentResize(Sender : TObject);
begin
TControl(TControl(Sender).parent).Height := TControl(Sender).Height + 4;
end;
if we use Label1.Align := TALignLayout.None;
then we should add the position inside the parent :
procedure DoParentResize(Sender : TObject);
begin
TControl(TControl(Sender).parent).Height := TControl(Sender).Position.Y + TControl(Sender).Height + 4;
end;
Wich result in a single function for (almost) all cases :
procedure TForm1.DoParentResize(Sender : TObject);
begin
if TControl(Sender).Align in [TAlignLayout.None, TAlignLayout.Client, TAlignLayout.Center, TAlignLayout.VertCenter ] then
begin
TControl(TControl(Sender).parent).Height := TControl(Sender).Position.Y + TControl(Sender).Height + 4;
end
else
begin
TControl(TControl(Sender).parent).Height := TControl(Sender).Height + 4;
end;
end;
You need to reduce the LRect.right by the label left and right margins, and then add the label top and bottom margins to the label height at the end or the text might not fit the label.
procedure TFrm.PatternEditTyping(Sender: TObject);
begin
(Sender as Tedit).Canvas.Font.Size := (Sender as Tedit).Font.Size;
(Sender as Tedit).Width := (Sender as Tedit).Canvas.TextWidth((Sender as Tedit).Text);
end;
This code adjusts Tedit.Width while you type inside it. Just keep the font family in Canvas and in Tedit the same.
What is your preferred way of keeping controls centered on its parent when the parent change width or height?
If by 'centered' you mean "it was already in the middle and you want to keep it there without resizing it", then remove all anchors. If it should be resized, gabr's solution is the one to with :)
Set control's Anchors property to [akLeft, akTop, akRight, akBottom].
If you mean a sort of "updating, please wait..." type thing, I manually move it in the Form's OnResize event. This allows me to keep a panel out of the way during design, and hidden normally, but I can make it visible when needed.
procedure TMyForm.FormResize(Sender: TObject);
var
nNewTop : Integer;
begin
inherited;
pnlRegenerating.Left := (ClientWidth - pnlRegenerating.Width) div 2;
nNewTop := (ClientHeight div 5) {* 4};
if (nNewTop + pnlRegenerating.Height) > ClientHeight then
nNewTop := ClientHeight - pnlRegenerating.Height - 4;
pnlRegenerating.Top := nNewTop;
end;