Delphi: ButtonedEdit + Frame = Bug - delphi

Delphi XE.
There is a Buttoned Edit (with the left button), an image list with a picture for the button. All is on a frame (it is Ok if on a form).
There is no an indent of the button in a design time, but it is in a run time.
Is it a bug?
Thanks!

Yes it's a bug. For some reason the Ctl3D property of the TButtonEdit is not being streamed correctly from the .dfm file when the edit resides in a frame. The streaming is resulting in Ctl3D being False instead of True.
Then, in TEditButton.UpdateBounds the following code executes:
if (not FEditControl.Ctl3D) and (FEditControl.BorderStyle <> bsNone) then
begin
FGlyph.Top := 2;
Inc(NewLeft, 2);
end;
This is what is shifting the position of your button 2 pixels right and down.
You can work around the bug by manually setting Ctl3D in code and forcing UpdateBounds to be called again. I did this with an interposer:
type
TButtonedEdit = class(ExtCtrls.TButtonedEdit)
protected
procedure Loaded; override;
end;
procedure TButtonedEdit.Loaded;
begin
inherited;
Ctl3D := True;
LeftButton.Visible := not LeftButton.Visible;
LeftButton.Visible := not LeftButton.Visible;
RightButton.Visible := not RightButton.Visible;
RightButton.Visible := not RightButton.Visible;
end;
You can include this in your frame, but make sure that the declaration of the interposed TButtonedEdit is before your frame is declared. Or if the bug afflicts many frames, declare it in a common unit and use that unit in your frame after you use ExtCtrls.
Now, as for the obvious question as to why the streaming doesn't set Ctl3D correctly, I have no answer for that. Perhaps someone more knowledgeable than I am about form streaming could enlighten us!

Related

Issue with controls alignment in dynamically created Frame

My program uses dynamically created frames and sometimes I get an issue that their controls are improperly aligned.
I use my own container control inherited from TPanel, but the same problem can be found when using the GridPanel as well.
Here is the source of a test program that reproduces the problem (with compiled exe).
The key code snippets:
in the main form:
//creating Frame from the main form
procedure TForm1.FormCreate(Sender: TObject);
begin
f := TFrame2.Create(Self);
f.Parent := Self;
end;
in the frame:
//constructor of the Frame
constructor TFrame2.Create(AOwner: TComponent);
begin
inherited;
Edit1.Clear;// the problem appears
end;
The frame and all its controls are aligned and must have the width of the main form, but Edit1 and ComboBox1 are visually not aligned until you resize the form manually (Sending WM_SIZE has no effect).
But if you comment the Edit1.Clear line everything will work fine from the program start. This code is not specific for the error and you can enter here e.g. ComboBox1.Items.Add('') etc.
If the frame is created statically or the GridPanel is changed to Panel the problem disappears.
I have made a new test2 version thanks to #quasoft, it works better - now the controls are horizontally aligned proper but vertically combobox is not in the right place that can be seen by changing the form size.
Quick fix:
The quick solution to your problem is to use Text property of TEdit, instead of the Clear method - as already said, replacing Edit1.Clear with Edit1.Text := ''.
Understanding the problem
But you need to understand this problem better if you plan to use frames in Delphi in the long term, or they will haunt you while you sleep (joking).
The real problem is that you are modifying the state of the frame before a Parent has been assigned to it.
procedure TForm1.FormCreate(Sender: TObject);
begin
f := TFrame2.Create(Self); // <--- Your text edit is cleared inside
f.Parent := Self; // <--- Your frame is attached to the form here
end;
Doing so does not allow the TGridPanel component to take in account the width, height and position of parent when calculating the size of its columns and rows.
Using Text property works, because the property setter does not change the text of the control directly, but sends a message for the purpose to the message queue:
Except from Controls.pas:
...
procedure TControl.SetTextBuf(Buffer: PChar);
begin
Perform(WM_SETTEXT, 0, Longint(Buffer));
Perform(CM_TEXTCHANGED, 0, 0);
end;
procedure TControl.SetText(const Value: TCaption);
begin
if GetText <> Value then SetTextBuf(PChar(Value));
end;
...
Which in effect, causes the text to actually change after you have assigned the Parent of the frame - as the message queue will be processed a little bit after the form create method completes.
Clear method on the other hand directly changes the text:
Excerpt from StdCtrls.pas:
...
procedure TCustomEdit.Clear;
begin
SetWindowText(Handle, '');
end;
...
A better solution
As you already learned, the quick fix works only in the specific example you provided.
A better solution is to create an Init method in your frame and call this method from the main form after Parent has been assigned:
Your frame:
procedure TFrame2.Init;
begin
Edit1.Clear;
ComboBox1.Items.Add('Foo Bar');
end;
Your main form:
procedure TForm1.FormCreate(Sender: TObject);
begin
f := TFrame2.Create(Self);
f.Parent := Self; // <--- Your frame is attached to the form here
f.Init; // <--- Calls initialization code of frame
end;

Delphi XE5 Firemonkey TTabItem and TEdit repaint coordination

I created a TTabControl with two TTabItems. On each TTabItem there is one (or more) TImageViewers and several TEdits. When I click on the TImageViewer, a modal screen pops up, I set some values, and I want to report those values to the user through the TEdits. So on returning from the Modal screen,
I execute
editn.text := whateveritis;
I then say
editn.repaint;
Nothing happens. I say TTabItem.repaint. Nothing happens. I click the other TTabIem and then come back to the first TabItem and, voila, the Edit control contains the right information. So my editn.text := whateveritis must be working (that's the only write to the TEdit), but I can't get the blinkin' control to show the result without going off-tab. How do I get it to redisplay as soon as I change the content? Do I need to write an OnChange routine that is one line, self.repaint? Seems ugly, and I'd hope there's a more global approach. Suggestions?
In light of initial comments, let me give more details. Setup: In main screen, drop in tabcontrol, and in tabcontrol drop in 2 tabitems. In tabitem1, drop in a timageviewer and 4 tedits (plus other stuff, probably irrelevant). Image gets dropped into the imageviewer (and displays correctly). The onclick event activates the following (ellipsis cuts out irrelevant code):
procedure TSCKMain.ImageViewer1Click(Sender: TObject);
var
lochold, scrollhold : tpoint;
backfromwavform : tmodalresult;
begin
lochold.X := mouseloccallback.x;
lochold.Y := mouseloccallback.y;
scrollhold.X := round(imageviewer1.ViewportPosition.X);
scrollhold.Y := round(imageviewer1.ViewportPosition.Y);
…
repeat backfromwavform := Wavform.Showmodal until backfromwavform<>mrnone;
case backfromwavform of
mrOK : begin {blue end}
Specsingle.BlueEnd.X := lochold.X;
Specsingle.BlueEnd.y := lochold.y;
edit13.Text := inttostr(Specsingle.BlueEnd.X);
Edit14.Text := inttostr(Specsingle.BlueEnd.y);
PublicWindowFlag := 'RePlot';
end;
mrContinue : begin {red end}
Specsingle.RedEnd.X := lochold.X;
Specsingle.RedEnd.y := lochold.y;
edit15.Text := inttostr(Specsingle.RedEnd.X);
Edit16.Text := inttostr(Specsingle.RedEnd.y);
PublicWindowFlag := 'RePlot';
end;
…
end;
if PublicWindowFlag<>'Cancel' then
if PublicWindowFlag='RePlot' then
begin
specsingle.RegenImage;
end
else
showmessage('Single image semaphore error. Debug.');
Imageviewer1.scrollto(scrollhold.X-Imageviewer1.viewportposition.X, scrollhold.y-Imageviewer1.ViewportPosition.Y);
end;
The modal screen sends back either mrContinue or mrOK correctly, and the appropriate case executes. However, edit13, edit14, edit15, and edit16 do not change their content. However, if I click over to Tabitem2 and back to Tabitem1, they DO repaint and DO contain the correct characters, which they could only have gotten from the above code. Conclusion: somehow, the edits aren’t repainting independently, but it’s not clear why.
Got it. The canvas for the imageviewer, the canvas for the bitmap in the imageviewer, and the canvas for the parent form are all in play. One must be sure that the canvas is the right one. As soon as scenes got untangled between imageviewer.bitmap and everything else, the edits worked as one would expect.

Resize won't execute untill I manually call ClientHeigh or until I manually resize it

I want to create a custom control derived from TPanel that contains an image and a bunch of other controls on it.
After writing the code I have some weird behavior in my program. I realized that some vars that were supposed to be initialized in TDisplay.Resize (override) was never initialized because the Resize was never executed.
To 'solve it' I put a button on a form and called the LoadSample function which calls ClientHeight which calls Resize FOR THE FIRST TIME!
constructor TDisplay.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Ready := FALSE;
Parent := Owner as TWinControl;
Width := 200;
Height := 86;
Color := clSilver;
Caption := '';
DoubleBuffered:= TRUE;
InternalDisplay:= TImage32.Create(Self);
with Display DO
begin
Parent := Self;
Bitmap.Width := 1;
Bitmap.Height := 1;
RepaintMode := rmOptimizer;
Align := alClient;
SetupBitmap(TRUE, clBlack32);
Visible := TRUE;
OnMouseDown := DMouseDown;
end;
...
end;
Update:
Also the InternalDisplay won't be aligned to its parent size until I manually resize the form (the control) at runtime. Only then it will act as it was supposed to act (to stay aligned to alClient).
Update 2:
Resize is declared like that: procedure Resize; override;
Update 3:
I removed the ClientHeight line from my construnctor and move it here:
procedure TDisplay.LoadSample(VAR Obj: TMySample; CONST bReleaseOnExit: boolean)
begin
ClientHeight; <--------- this will call Resize for the first time and my code will be finally initialized. But until this point my display will look odd because the code was never initialized. So the user will see weird stuff until it pushes the 'LoadSample' button.
more code here....
end;
Update 4:
I used HandleNeeded as David suggested and it solved the initialization problem. However, the Image still won't align to the entire client area unless I manually resize the form/control.
Update 5
Continued here, as David suggested: TImage won't align to parent
Your control is derived from TWinControl, and TWinControl calls Resize in response to the WM_SIZE message. So, Resize will not be called unless the control's window handle has been created.
The Resize method is not called when you assign Height, or indeed Width, because the window handle has not yet been allocated.
When you evaluate the ClientHeight property, that results in the window handle being created, and then Resize is called. That's because GetClientHeight calls GetClientRect which looks like this:
function TWinControl.GetClientRect: TRect;
begin
Winapi.Windows.GetClientRect(Handle, Result);
end;
And it's the evalutation of the Handle property that forces the window handle into existence.
Your form isn't showing yet, so it isn't yet able to receive Windows messages (such as the resize message that triggers the OnResize event).

Dynamically resize a form to fit the size of a Frame in Delphi

I have an application that has 5 different sized frames. I'd like to dynamically re-size the main form to fit the frame when I move from one frame to another.
I can use the MinHeight/MinWidth properties of the frame to force the main form to fit the frame, but then when moving to a smaller frame, the main form does not adjust it's size.
Any ideas?
--Edit
...
TFormMain = Class(TForm)
...
public
FrameImportPackage: TFrameImportPackage;
...
procedure TFormMain.MenuPackagesImportClick(Sender: TObject);
begin
if not (Assigned(FrameImportPackage)) then
begin
FrameImportPackage := TFrameImportPackage.Create(Self);
FrameImportPackage.LabelFrameCaption.Caption := 'Import or Edit a Package';
end
else
begin
FrameImportPackage.BringToFront;
end;
FrameImportPackage.Parent := Self;
end;
--Edit
Regards, Pieter
If I understand your question correctly, you've got frames that don't change size, you want the form to update size to fit your frames. Let Delphi handle that for you, using the AutoSize property.
Set AutoSize = True for your form.
I've tested AutoSize with the following code, using Delphi 2010:
Create a new VCL application. On the blank form drop a single Panel, let it keep it's name (Panel1). Make sure the panel is not too small, because we'll write code to decrease it's size at runtime.
Set the form's AutoSize property to True.
Drop two buttons on the panel, Button1 and Button2.
Double click the buttons, and copy-paste the following event handlers:
Code:
procedure TForm31.Button1Click(Sender: TObject);
var NewR: TRect;
begin
NewR := Panel1.BoundsRect;
Dec(NewR.Right, 32);
Dec(NewR.Bottom, 32);
Button1.Parent := Self;
Button2.Parent := Self;
Panel1.Free;
Panel1 := TPanel.Create(Self);
Panel1.BoundsRect := NewR;
Panel1.Parent := Self;
Button1.Parent := Panel1;
Button2.Parent := Panel1;
end;
procedure TForm31.Button2Click(Sender: TObject);
begin
Panel1.Height := Panel1.Height - 32;
Panel1.Width := Panel1.Width - 32;
end;
This essentially gives you two ways of decreasing the size of the panel, to handle two possible scenarios: Button1 frees the old panel and creates a new, smaller panel. Button2 directly resize the existing panel. Both work as expected!
At least on Delphi 2006 there is a really anonying BUG with Form AutoSize.
You put a TStringGrid onto the form (Left and Top equal zero, align equal None, Top, CLient does not matter), when you change its ClientWidth and ClientHeightt the form not allways adjusts its size to the control.
Normally when it fails is when control size is reduced, form size does not get reduced.
There is no good fix, the only way to do it is manually set clientwidth and clientheight of the form when the object is resized.
It is said: Form AutoSize does not allways work well! It is a BUG on the VCL.

Right click(popup menu) does not work when change diretion of treeview with SetWindowLong Command

When I use SetWindowLong command to change direction of treeview, popupmenu on its node dose not show. Full Code is here :
Procedure SetWinControlBiDi(Control: TTreeView);
var
ExStyle: Longint;
begin
ExStyle := GetWindowLong(Control.Handle, GWL_EXSTYLE);
SetWindowLong(Control.Handle, GWL_EXSTYLE, ExStyle or WS_EX_RTLREADING or WS_EX_RIGHT or WS_EX_LAYOUTRTL or WS_EX_NOINHERITLAYOUT );
end;
procedure TMainForm.FormShow(Sender: TObject);
begin
SetWinControlBiDi(TreeView1);
end;
The standard way to do this is to use the Delphi BiDiMode property. It's best to do it this way so that the VCL is aware that you want right-to-left. You need to change the BiDiMode property on the popup menu too.
Now, the correct way to do this is not to change the properties on the individual components. Doing it that way is laborious and very error prone. Set Application.BiDiMode somewhere in your application's initialization and the change will propagate through to all your components.
For example you can make the change in your application's .dpr file:
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.BiDiMode := bdRightToLeft;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
You need to make sure that you have not modified any component's BiDiMode or ParentBiDiMode in any .dfm file. If you have simply remove those lines from your .dfm file and that will allow the single application wide Application.BiDiMode setting to control everything.
Your approach of setting GWL_EXSTYLE is problematic. The VCL is in control of that setting and if you do need to change it, doing so in TForm.OnShow will lead to strange bugs. Sometimes windows need to be re-created and when this happens your code to set GWL_EXSTYLE will not run and your tree view will revert to left-to-right. If you do need to modify the window styles then you need to override TWinControl.CreateParams for the component. However, in this case the VCL has direct support for BiDi and that is the best solution.
This is an alternative solution to show TPopupMenu In this case
1- Use OnMouseDown Event
2- Write this code to show a TPopupMenu when you click the right mouse button
var
pt : TPoint;
begin
pt := Mouse.CursorPos;
if Button = mbRight then
APopupMenu.Popup(pt.X, pt.Y);
Good luck !

Resources