I have simple form TForm1 with 2 panels. First with Align := alLeft and second with Align := alClient and empty frame TFrame1. When i add following procedures to the form, everything works without problems.
procedure TForm1.FormCreate(Sender: TObject);
var
lFrame1, lFrame2 : TFrame1;
begin
lFrame1 := TFrame1.Create(nil);
lFrame1.Parent := pnl1;
lFrame1.Align := alClient;
lFrame2 := TFrame1.Create(nil);
lFrame2.Parent := pnl2;
lFrame2.Align := alClient;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
pnl1.Width := ClientWidth div 2;
end;
But when I set Constrains for TFrame1, for example TFrame1.Contraints.MinWidth := 100 and maximize and restore the form, then the form won't return to its previous state. Regardless of frame size, form size or constrains values, it always ends the same way. In my case default form has 300 width and after maximize and restore it ends with 1062. However without Constraints or FormResize it works. Can someone explain this strange behavior?
Related
How do you get the label height to automatically adjust when resizing the form? All of the properties are set. Align is top. Autosize is true. Word wrap is true.
When I change the form size the label adjust the caption fine. However, the actual label will not resize its height.
This leaves a gap when the form width is increasing or it leaves the bottom part of the caption unreadable. Makes it ugly when you have controls below the label that should move up or down depending on the label's height.
I would hate to do this using the form's resize event. Too bad there is no form "resize end" event.
Any help? Thanks.
If I recall correctly, with Autosize set to true, the height of the label is automatically set to the actual height of the text in Caption.
You might try setting Autosize to false and see how that works for you.
I've solved by inheriting from tlabel.
there is a bug with the autosize in this case (autosize, wordwrap and alTop)
to make it recalculate it size you need to:
AutoSize := false;
AutoSize := true;
so you can override the resize procedure like that:
procedure TResizableLabel.Resize;
begin
AutoSize := false;
AutoSize := true;
end;
however if you will do it on every resize it will shrink the width also, so you will lose the width of the parent from alTop, in case it is just aligned left it will probably be ok, but if you want center or right alignment you will need a better solution.
this is the full solution, it will call the autosize only when needed:
TResizableLaber = class(TLabel)
protected
FTextHeight, FTextWidth : integer;
function GetCaption : TCaption;
procedure SetCaption(ACaption : TCaption);
function GetFont : TFont;
procedure SetFont(AFont : TFont);
public
procedure Resize; override;
property Caption : TCaption read GetCaption write SetCaption;
property Font : TFont read GetFont write SetFont;
end;
implementation
procedure TResizableLaber.Resize;
var
num : double;
begin
inherited;
if AutoSize then
begin
if (FTextHeight = 0) or (FTextWidth = 0) then
begin
//lazy evaluation, we re evaluate every time the caption or font changes
FTextWidth := Utills.GetTextWidth(Caption, Font);
FTextHeight := Utills.GetTextHeight(Caption,Font);
end;
//TODO: there is still one bug here, set alCenter and make the last word long enough so it cant always wrapped to the line before, even though there is globally enough space
num := ( Height / FTextHeight) - (FTextWidth /Width );
//if num is greater then 1 it means we need an extra line, if it is lower then zero it means there is an extra line
if (num > 1) or (num < 0) then
begin
//just doing this all the time will cause it to really resize and will break alTop matching the whole space
AutoSize := false;
AutoSize := true;
end;
end;
end;
function TResizableLaber.GetCaption : TCaption;
begin
Result := inherited Caption;
end;
procedure TResizableLaber.SetCaption(ACaption : TCaption);
begin
FTextWidth := Utills.GetTextWidth(ACaption, Self.Font);
FTextHeight := Utills.GetTextHeight(ACaption,Self.Font);
inherited Caption := ACaption;
end;
function TResizableLaber.GetFont : TFont;
begin
Result := inherited Font;
end;
procedure TResizableLaber.SetFont(AFont : TFont);
begin
FTextWidth := Utills.GetTextWidth(Caption, AFont);
FTextHeight := Utills.GetTextHeight(Caption,AFont);
inherited Font := AFont;
end;
class function Utills.GetTextHeight(const Text:String; Font:TFont) : Integer;
var
bitmap: TBitmap;
begin
bitmap := TBitmap.Create;
try
bitmap.Canvas.Font := Font;
Result := bitmap.Canvas.TextHeight(Text);
finally
bitmap.Free;
end;
end;
class function Utills.GetTextWidth(const Text:String; Font:TFont) : Integer;
var
bitmap: TBitmap;
begin
bitmap := TBitmap.Create;
try
bitmap.Canvas.Font := Font;
Result := bitmap.Canvas.TextWidth(Text);
finally
bitmap.Free;
end;
end;
I've spent quite some time to get both the wordwrap and the height of a series of labels right. The previous answer (thanks ndori), using the pointless-looking solution of first making Autosize false, followed by setting it to true is the solution!
Below my code for publishing a (long) series of labels, where the caption text is generated somewhere else and can be as short as one character up to several lines of text. So, I need a fixed label width, active wordwrap and a constant white space between all different labels.
When resizing the form the label.width (arbitrary set to 560 below) may be adjusted to fit the new form when resizing. I think the real problem is getting the label heights correctly displayed.
{ AL[] = global variable: array of TLabel
{ AL[].caption (the text) is delivered elsewhere, and can be short or long (= multiline text)
{ N_ActiveLabels = global integer variable: # of active labels to publish }
procedure PublishListOfLabels;
var
i : integer;
begin
AL[0].Top := 15; // or whatever
AL[0].Visible := true;
AL[0].Width := 560; // (re)set this here as otherwise the wordwrap makes
// the label text a long narrow column!
AL[0].AutoSize := false; // THIS IS REQUIRED!
AL[0].AutoSize := true; // THIS IS REQUIRED!
if N_ActiveLabels > 1 then begin
for i := 1 to N_ActiveLabels -1 do begin
AL[i].Visible := true;
AL[i].Width := 560;
AL[i].AutoSize := false;
AL[i].AutoSize := true;
AL[i].Top := AL[i-1].Top + AL[i-1].Height + 18;
// 18 was chosen as vertical white space between any two labels
end;
end;
end;
I found repainting (or refreshing) of the labels not needed.
I also encountered solutions like:
H := AL[i].Canvas.TextHeight(AL[i].caption);
where H is supposed to contain the real height of AL[i] (after filling its caption with text and calling PublishListOfLabels. it is NOT working.
I mention this as this solution has been proposed at several other places dealing with the same issue (getting a correct TLabel height).
[I use Berlin 10.1 - perhaps later versions have solved the Autosize.false /.true aberation]
I already found much help for writing a components which allows Hiding of components here ( THIDEPANEL. Now I suffer a first issues:
During the OnCreate event of this class I take the Panel width and height, I want to restore to the original values while hidden / unhiding the panel. Actually the hide process always decrease the size of the panel
constructor THidePanel.Create(AOwner: TComponent);
begin
inherited;
// The inner panel
WorkingPanel := TPanel.Create(Self);
WorkingPanel.Caption := '***';
// The hide unhide
FActivateButton := TButton.Create(Self);
FActivateButton.Parent := self;
FActivateButton.Caption := '<';
FActivateButton.OnClick := H_ActivateButtonClick;
FActivateButton.Width := BoarderSize;
FActivateButton.Height := BoarderSize;
WorkingPanel.Caption := '';
// Grab the size of the hide panel, later restore to this values
FLargeWidth := Self.Width;
FLargeHeight := Self.Height;
SetButtonPosition(TopRight);
end;
It is because the FLargeWidth private field has an invalid value. You assign it with Self.Width during the constructor (and you presumably never update it). That is not the width you set at design time or at run time, but it is the hard coded width from TCustomPanel.Create, which is 185. Note that when a control's constructor is run, the control is not placed yet.
If you want to remember the set width, then you should "override TControl.SetWidth". But since that method is private (not virtual), you need to override either SetBounds or Resize in order to response to Width's change. I would choose the latter, probably with an additional condition:
procedure THidePanel.Resize;
begin
if not HasCustomWidth then //< Replace this with your own logic condition
FLargeWidth := Width;
inherited Resize;
end;
I have created one TScrollBox. I have added the Label and Edit Box on it dynamically on Button click. For setting the location of component i have used the height,width,left,top property of components.
But when Scroll Bar gets appeared on screen after 5 components added, the next components location gets disturbed. and the next component is not placed in synchronous manner on ScrollBox.
The Top coordinate for controls placed on a ScrollBox need to take into account the amount of "scroll" that already took place. If you add the controls all at once this is not a problem, because the ScrollBox doesn't get the chance to "scroll".
If you add controls to the ScrollBox after it got a chance to "scroll", you need to take into account the amount of vertical "scroll" that took place. Here's a sample piece of code that will add labels to ScrollBox1, taking vertical scroll into account so controls don't overlap each other. Here I'm using the form's "Tag" property to hold the Top for the next control added, and I'm also using Tag to generate unique names for the labels (so you can see they're going into the ScrollBox at the correct coordinates).
procedure TForm31.Button1Click(Sender: TObject);
var L: TLabel;
begin
L := TLabel.Create(Self);
L.Caption := 'Test: ' + IntToStr(Tag);
L.Parent := ScrollBox1;
L.Top := Tag + ScrollBox1.VertScrollBar.Size - ScrollBox1.VertScrollBar.Position;
Tag := Tag + L.Height;
end;
An other approach I sometimes used is to keep track of the last control added and base the coordinates for the new control on the coordinates of that last added control:
var LastControl: TControl;
procedure TForm31.Button1Click(Sender: TObject);
var L: TLabel;
begin
L := TLabel.Create(Self);
L.Caption := 'Test: ' + IntToStr(Tag);
L.Parent := ScrollBox1;
if Assigned(LastControl) then
L.Top := LastControl.Top + LastControl.Height
else
L.Top := 0;
Tag := Tag + L.Height;
LastControl := L;
end;
And yet an other approach would be to find the lowest control and add your control based on it's coordinates:
procedure TForm31.Button1Click(Sender: TObject);
var L: TLabel;
Bottom, TestBottom: Integer;
i: Integer;
begin
// Find "Bottom"
Bottom := 0;
for i:=0 to ScrollBox1.ControlCount-1 do
with ScrollBox1.Controls[i] do
begin
TestBottom := Top + Height;
if TestBottom > Bottom then
Bottom := TestBottom;
end;
L := TLabel.Create(Self);
L.Caption := 'Test: ' + IntToStr(Tag);
L.Parent := ScrollBox1;
L.Top := Bottom;
Tag := Tag + L.Height;
end;
I have a "wide" TPanel with several buttons on it (essentially a tool bar). All the buttons have Align=Left. I have created a function which will resize the buttons to the same size and calculate the width of them so they fill the entire TPanel. I call this function in the OnResize event handler of the TPanel.
procedure ScaleButtonsOnPanel;
var i: Integer;
begin
for i:=0 to mPanel.ControlCount-1 do begin
mPanel.Controls[i].Width := round(mPanel.width/mPanel.ControlCount-1)
end;
end;
The problem is if I minimize and then restore the form the layout of the buttons change from the design layout.
Can anyone offer a solution to having buttons on a panel which can be resized but maintain the design time order (in terms of left to right placement) ?
I do not really see your problem. But of course, you must set the position of the buttons, not only their size.
procedure TForm1.Panel1Resize(Sender: TObject);
var
i: Integer;
btnWidth: integer;
begin
btnWidth := Panel1.Width div Panel1.ControlCount;
for i := 0 to Panel1.ControlCount - 1 do
begin
Panel1.Controls[i].Left := i * btnWidth;
Panel1.Controls[i].Width := btnWidth;
end;
end;
This works very well.
See https://privat.rejbrand.se/panelresize.wmv.
OK, now I see. I think the alLeft is actually your problem. Controls with the same align tend to change their order. This is a well-known Delphi annoyance. Do it like I do above, instead. Just make sure that you go through the buttons in the right order. If you cannot rely on the ordering of Panel1.Controls, then you can do like this: Set the Tag property of each toolbar button to its position (0, 1, ...) in the toolbar then do
procedure TForm1.Panel1Resize(Sender: TObject);
var
i: Integer;
btnWidth: integer;
begin
btnWidth := Panel1.Width div Panel1.ControlCount;
for i := 0 to Panel1.ControlCount - 1 do
begin
Panel1.Controls[i].Left := Panel1.Controls[i].Tag * btnWidth;
Panel1.Controls[i].Width := btnWidth;
end;
end;
Have you tried to see if a TFlowPanel doesn't better suit your needs?
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.