FireMonkey FlowLayout isn't refreshing items based on visible property - delphi

How are you? Hope you doing fine.
My question: I have a FlowLayout with a lot of layouts inside it, and I need to hide a few based on a radio button selection, in design time it works fine I set the visible property to false and all the other layouts realign the right way, but when I do this at runtime it doesn't work, it keeps a white gap between the layout that has been hidden and the next one. When I do a resize manually (go to the form border and drag a little) it realigns and gets right, but if I select another radio the layout gets back and it override another layout so I need to resize manually again to realign. I tried to look at the source code of Resize but I got nothing relevant. What I tried: Repaint, Realign, InvalidateRect, RecalcAbsolute. Is there any way that I have to force the refresh of components?
procedure
TFrmApontamentoProducaoOrdemProducao.rbOrdensProducaoQuantidadeParcialClick(
Sender: TObject);
begin
if not lytQuantidadeParcial.Visible then
lytQuantidadeParcial.Visible := True;
// Tried to realign here
end;
procedure
TFrmApontamentoProducaoOrdemProducao.rbOrdensProducaoQuantidadeTotalClick(
Sender: TObject);
begin
if lytQuantidadeParcial.Visible then
lytQuantidadeParcial.Visible := False;
// Tried to realign here
end;
It's a simple code, but it's giving me a little problem. Thanks for the help, if you need more code or more details just let me know.

You must surround your code that makes changes to the layout of the TFlowLayout with a pair of FlowLayout1.BeginUpdate; and FlowLayout1.EndUpdate; To assure that the update counter stays in sync, you should also use a try..finally..end block.
For example
procedure TForm21.Button6Click(Sender: TObject);
begin
FlowLayout1.BeginUpdate;
try
Layout3.Visible := not Layout3.Visible;
finally
FlowLayout1.EndUpdate;
end;
end;

Using BeginUpdate cause access violation on a project when insert chars from tcp-ip comunication in real time on FlowLayout, the solution:
var
PodeAtualizarGrade:TCriticalSection;
// ...
FormCreate
PodeAtualizarGrade:=TCriticalSection.Create;
// ...
PodeAtualizarGrade.Enter;
(FlowLayout.AddObjects)
PodeAtualizarGrade.Leave;
Works fine on my project.

Related

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};

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.

How to make a Drawn Line on a Form Invisible in Delphi 7

I am trying to make an analog clock, wherein I would like to make my 'seconds' Line Invisible when the Seconds Changes. I have tried to set the Pen mode to pmNotCopy but it only gives Inverse of Pen Color. What Property must be set in this Form1.Canvas.Pen.Mode:=<Blank> so that My Line Disappears?
Any other Ideas are also appreciated.
Thanks
Modern computers are very fast. If I were you, I'd definitely draw the entire clock from scratch every second. Problem solved. In addition, if you need anti-aliasing, then a simple pen mode trick will never work.
(If you are not using a remote desktop, you might want to employ double-buffering.)
I don't know anything about delphi but just some out if the box thinking:
couldn't you change the color of your line to the background color, making it 'invisible'
You were close. You need to use pmXOR.
Try this:
Create a new Delphi VCL Forms application. Drop a TButton on the bottom of the form (Button1).
Add the code below to the Button1Click event.
Run the application. Click the button, and three parallel lines will be drawn across the top. Click the button again, and the three lines will disappear.
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
Canvas.Pen.Mode := pmXor;
Canvas.Pen.Color := clGreen;
for i := 1 to 3 do
begin
Canvas.MoveTo(50, i * 20);
Canvas.LineTo(Width - 50, i * 20);
end;
end;
All drawing should normally be done in the OnPaint event; I'm intentionally drawing in the Button1Click event for demonstration reasons only.
Just as a note: You should never use Form1 inside an event handler for that form. Referencing the Form1 variable prevents you from creating more than one instance of the form. Use Self instead, and that will automatically refer to the instance the code is running in.

Making a TPageControl flat in Delphi 7

I don't know whether this question can be answered here, but I hope it will.
I wrote a simple text editor in Delphi 7 that serves as my primary IDE for writing C code under Windows. I run Windows in a VM and I needed something light.
In any case, it uses a TpageControl that gets a new tab whenever you open or create a new file. Pretty standard.
Now, the TPageControl under Delphi has no flat property.
NO I don't mean setting the tab style to tsButtons or tsFlatButtons
the borders cannot be set to "none" and it looks pretty bad when you add a text editor into the tab control.
Is there any way to make a TpageControl flat?
EDIT:
On an open source PageControl that supports flat here's what I found:
procedure TCustomTabExtControl.WndProc(var Message: TMessage);
begin
if(Message.Msg=TCM_ADJUSTRECT) and (FFlat) then
begin
Inherited WndProc(Message);
Case TAbPosition of
tpTop : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Top:=PRect(Message.LParam)^.Top-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpLeft : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Left:=PRect(Message.LParam)^.Left-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
tpBottom : begin
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=ClientWidth;
PRect(Message.LParam)^.Bottom:=PRect(Message.LParam)^.Bottom-4;
PRect(Message.LParam)^.Top:=0;
end;
tpRight : begin
PRect(Message.LParam)^.Top:=0;
PRect(Message.LParam)^.Left:=0;
PRect(Message.LParam)^.Right:=PRect(Message.LParam)^.Right-4;
PRect(Message.LParam)^.Bottom:=ClientHeight;
end;
end;
end else Inherited WndProc(Message);
end;
The thing is when I tried something similar on the main application it won't work. It won't even compile.
When the tabs are drawn as buttons, no border is drawn around the display area, so set the Style property to tsButtons or tsFlatButtons. (For non-VCL programmers, this is equivalent to including the tcs_Buttons window style on the tab control.)
An alternative is to use a TNotebook. It holds pages, but it doesn't do any painting at all. You'd have to provide the tabs yourself, such as by setting the tab control's height equal to the height of the tabs, or by using a TTabSet. (TTabSet is available in Delphi 2005; I'm not sure about Delphi 7.)
Regarding the code you found, it would be helpful if you indicated why it doesn't compile, or if you gave a link to where you found it, since I suppose the compilation error was because it refers to fields or properties of the custom class rather than the stock one. Here's what you can try to put it in your own code, without having to write a custom control.
Make two new declarations in your form like this:
FOldTabProc: TWndMethod;
procedure TabWndProc(var Msg: TMessage);
In the form's OnCreate event handler, assign that method to the page control's WindowProc property:
FOldTabProc := PageControl1.WindowProc;
PageControl1.WindowProc := TabWndProc;
Now implement that method and handle the tcm_AdjustRect messsage:
procedure TForm1.TabWndProc(var Msg: TMessage);
begin
FOldTabProc(Msg);
if Msg.Msg = tcm_AdjustRect then begin
case PageControl1.TabPosition of
tpTop: begin
PRect(Msg.LParam)^.Left := 0;
PRect(Msg.LParam)^.Right := PageControl1.ClientWidth;
Dec(PRect(Msg.LParam)^.Top, 4);
PRect(Msg.LParam)^.Bottom := PageControl1.ClientHeight;
end;
end;
end;
end;
You can fill in the other three cases if you need them. Tcm_AdjustRect is a message identifier declared in the CommCtrl unit. If you don't have that message in that unit, declare it yourself; its value is 4904.
I suspect this doesn't stop the control from drawing its borders. Rather, it causes the contained TTabSheet to grow a little bigger and cover up the borders.
I'm using Delphi XE8 and the following seems to do the trick:
ATabControl.Tabs.Clear;
ATabControl.Style := TTabStyle.tsFlatButtons;
ATabControl.Brush.Color := clWhite;
You could always use a commercial solution. I would strongly recommend Raize components, which support flat TPageControls with tabs. The component set is very easy to work with, and supports numerous visual enhancements which in my opinion give a better feel to any application.
(source: raize.com)
Drop two TPageControls, one with tabs as Tabs, with a global height equal to the tabs, and one with flatbuttons and Tabvisible properties set to false, which would be aligned under the first one. Then make sure the tab change on the first TPagecontrol makes the tabs also change in the second one.

Resources