Is it possible to drag a TPanel to outside the TForm? - delphi

I'm wondering if it's possible to make something like this:
but without creating a second TForm.
I'm using Delphi 7, but if a newer version make it possible just tell me.

I've always thought the DockEx demo was over-complicated for learning the basics of docking.
The following is the simplest example I know of:
Add a TPanel to a blank form and set its DragKind property to dkDock, DragMode to dmAutomatic and its Align property to alTop.
Drop a TButton on the TPanel
Add the code below to the form:
Run the project and manually drag the panel off the form.
Click Button1.
The above shows how Delphi can undock a Panel (or TEdit, etc) without you
needing to create a second form to host it while undocked, like Remy said in a comment. The Button1 click-handler shows a way (admittedly imperfect) of re-docking the panel. Next:
Undock the panel again, but this time, click the Close button on its auto-created host.
Then, read the OLH and figure out a) how to get the now-hidden panel visible again and b) to re-position & re-align it on the form as it was prior to undocking,
type
TMyClass = TControl;
procedure TForm1.Button1Click(Sender: TObject);
begin
TMyClass(Panel1).ManualDock(Self, Nil, alNone);
end;

Related

How to let Parent component get click events of child components?

I have a FireMonkey application with multiple buttons on it (actually, rectangles). I want to have one procedure called on any click on the Form, besides the specific action of each button.
Since the HitTest of each child component is set to True, the parent's HitTest is automatically false.
So what is the right way to deal with this?
A silly workaround would be to assign this procedure to each button's OnClick event, but this will not make any sense when I have a Form with hundreds of buttons on it.
I found solution for vcl.
you can find component base of mouse position by this code:
var
ctrl : TWinControl;
begin
ctrl := FindVCLWindow(Mouse.CursorPos);
if ctrl.Name = '' then
ShowMessage(ctrl.Owner.Name);
For FireMonkey no result founded but You can get mouse position on form and analyze component base on result of that point and find name of it then proceed to your event base of that component.
A Simple Interceptor/Interposer Class of TRectangle did the job!
Thanks to everyone for their input.

Delphi : How can solve click issue for a Form which it's parent is a ScrollBox?

I Created a Project with 2 form. TForm1 (main form) and TForm2. on Form1 we have a ScrollBox. on Form2 we have 2 TEdit. and in OnCreate of Form1 we have the following code.
procedure TForm1.FormCreate(Sender: TObject);
var
frm:TForm2;
begin
frm:=TForm2.Create(ScrollBox1);
frm.Parent:=ScrollBox1;
frm.Show;
end;
when I run the program I see an instance of TForm2 in the Scrollbox1. everything looks good. when mouse hover close or minimize or maximize buttons or any other button on the form everything seems normal. but when click on one Edit or any other component that has typing ability on it the focus can not go to that TEdit or component. Only if I Right-Click on that component the focus goes to it.
Is it possible to fix this problem? and if yes how can I do it?
and MDIForm can not help me. because I need more than one form with ability of hosting some other forms in them. and delphi seems to not support mdiForm for non-mainForms. if this issue about click can be solved I think it is a good choice for me to manage some forms to host some other forms.

How to cope with the bug with style lookup in controls nested in FMX Frames

I have met a bug in applying of StyleLookup in TTabControl nested in FMX Frame. The appearance of the control does not accept the style if the frame with the control is nested in a HorzScrollBox and at form creation the position of the frame is outside the visible area of the form. In other words if the control is visible when the form is shown the control is painted with necessary style appearance otherwise - not.
To reproduce the bug:
Create a new grid metropolis application.
Create a Frame and put a TTabControl on it. Set Align of the TabControl to alClient. Add, say, 4 TTabItems.
Open detail form, and set its width to a value > your Screen.Width (for me 2500 was enough).
Copy-paste any of the Columns (Layouts), say, 2 times and use the most right layout to nest a frame in it. You will see normal presentation of tabitems at designtime. Set style settings. You will get the following picture:
Done. Run the project. You will get the bug.
At runtime the appearance of TabItem is like this:
If you set the StyleLookup manually at runtime the appearance might either not change either be set to every other item (for example 1st, 3rd and not set to 2nd and 4th though you assign the same StyleLookup to all TabItems). The other interesting finding. If you have other forms containing TabControls with TabItems (nested without frames and closer to left border of the form) for example Form2, you get the following. If you show the faulty Form1 at runtime first you will see the bug, but if you close this form and show Form2 you see proper TabControl. Closing Form2 and showing Form1 (faulty) afterwards will give you a proper appearance of TabControl in a faulty form.
It has something in common with the bug reported by me earlier: Incorrect selection of items in an FMX TListbox (Grid Metropolis UI). It is still not fully solved.
There was also a question yesterday but it is about an exact problem with VCL Frames and the solution is not suitable for FMX.
Appended.
The way is to partially set the style is to set OnPainting event handler for TTabControl in the parent form unit (it does not work being set in frame unit) and write something like this:
procedure TPatientsScrollF.HMDiagnosisFr1TabControl2Painting(Sender: TObject;
Canvas: TCanvas; const ARect: TRectF);
var
i: byte;
begin
(Sender as TTabControl).StyleLookup := 'tabcontrolstyle';
for i := 0 to (Sender as TTabControl).TabCount-1 do
(Sender as TTabControl).Tabs[i].StyleLookup := 'tabitemstyle';
end;
But than you still get a problem - the Tabs are not drawn properly - see the lower edge of inactive Tabs.
Or even like this:
Appended 2
I have just met the bug appearing even at design-time after placing a frame in a form and assigning style to TabItems. The TabControl looks like in fig. 3.

How to detach a panel and show it in a separate window?

Let's say I have form A that contains a panel (with many other controls in it) and a form B that it is empty.
Can I programmatically detach the panel from form A and move it in form B (and maybe back to form A)?
I know that I can change the Owner of the panel but does it work between different forms?
Update:
After some Googling I see that there is a ParentWindow property.
As noted by others, there are several problems with changing the parent window of a control without changing the ownership, and changing a controls owner can be difficult if it has multiple controls sitting on it...
One way around it is to use a frame instead. A frame owns all of it's sub-controls, so all you would need to do is change the owner and parent of the frame, and everything else will come along with it. This approach also allows you to keep all the event handlers and glue code in a single place as well.
N#
You have to take ownership into account, otherwise the destruction of form A would lead to the disappearance (i.e. destruction) of your panel on form B, or worse.
type
TForm2 = class(TForm)
public
InsertedPanel: TControl; // or TPanel
.
procedure RemoveComponents(AForm: TComponent; AControl: TWinControl);
var
I: Integer;
begin
for I := 0 to AControl.ControlCount - 1 do
begin
if AControl.Controls[I] is TWinControl then
RemoveComponents(AForm, TWinControl(AControl.Controls[I]));
if AControl.Controls[I].Owner = AForm then
AForm.RemoveComponent(AControl.Controls[I]);
end;
AForm.RemoveComponent(AControl);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
Form2.InsertedPanel := Panel1;
Panel1.Parent := nil;
RemoveComponents(Self, Panel1);
Form2.InsertComponent(Form2.InsertedPanel); // < this is not necessary
Form2.InsertedPanel.Parent := Form2; // as long as Parent is set
Panel1 := nil; // or if you free the panel
end; // manually
The extra reference may seem a bit silly: Form2.InsertedPanel and Panel1 point to the same object, but it's kind of semantically preferred. Maybe a central controlled variable is better.
Update:
I falsely assumed that RemoveComponent cascaded to the child controls on the panel. It doesn't, of course, so only removing the panel from form A would leave all the child controls of the panel still owned by form A. So I added the RemoveComponents routine to remove ownership from all the child controls of the panel.
Note that the child controls of the panel don't have an owner at this time. But since they are parented controls of the panel, destruction of the panel will free those controls. So be sure the panel has a parent, or free the panel explicitly.
All of the above only applies to a design time created panel, placed design time on the form, which was my assumption. Since this changing parents behaviour is apparently wanted or needed, you might want to consider to implement it completely at runtime. To keep the abbility to design the panel designtime, I suggest to create a Frame on which you can design that panel, and jump the Frame around your forms.
You can easily have something appear as if it was a panel, and also as a form, by really using a TForm for what you would have used the panel for. Then dock the form at runtime into the place where you have a blank panel left for that purpose, and undock it at runtime, by the same manner.
You can't undock a TPanel and have it appear as a top-level form window, but you can take a top level form window and dock it in code. To get the appearance and functionality you want you must use the correct tools (TForm, in this case).
Incidentally, component libraries like Toolbar 2000 do allow floating toolbar windows based on toolbar panels, so if you really insist on having all the designtim elements remain in one form, at desigtime, you should look into how it works in Toolbar 2000. It has a lot of code in there to render the toolbar in "undocked/floating" mode, and to handle the mouse-driven docking and undocking of toolbars into toolbar docks.
If the panel and child components are created at runtime, you can just set the Parent of the Panel to FormB:
Panel1.Parent := FormB;
Note that FormB has to have been created already before you can do this.
For more info, see the Delphi Wiki page here.

When I add a TPanel to a TToolBar, do I get a TPanel or a TToolButton?

When Delphi (2006) goes quantum: I've got "something" that appears to be both a TToolBar and a TPanel, depending on how you observe it. I'd like to understand what's going on.
Here is how to create it and what happens:
in the DFM
add a TToolBar named bar;
in that TToolBar, put a TPanel.
in the code and at runtime:
the panel appears in the list of buttons bar.Buttons[], let's say at index i
bar.Buttons[i], from the compiler point of view, is a TToolButton
bar.Buttons[i].ClassName = 'TPanel'
(bar.Buttons[i] is TToolButton) = true, but that's the compiler optimising the call to 'is' out;
indeed IsBarButton(bar.Buttons[i]) is false for function IsBarButton (defined below);
bar.Buttons[i].Name is the name I gave the TPanel in the DFM
inspecting the value bar.Buttons[i] in the debugging:
it has a property 'Caption' the real TToolButton's don't have
strangely, it has all properties TToolButton's have, like TToolButton.Indeterminate (=true).
IsToolButton:
function IsToolButton(X : TObject) : boolean;
begin
Result := X is TToolButton;
end;
So bar.Buttons[i] both is and is not a TToolButton... what's up ?
(Bottom story is I'd like to distinguish my TPanel from the genuine TToolButton's. This I can do in more or less hackish ways. My goal by asking this question here, is to get a fuller understanding of what's really happening here.)
Question: what is happening ?
Sub-question: is it legitimate to add a TPanel to a TToolBar ?
The only thing the OS allows to be added to a tool bar is a tool button. To add anything else, you technically need to create a button and then put your other things on top of it. The button that gets added is literally a placeholder. It's there to take up space so the next thing you add gets positioned properly.
You can see this sometimes if the non-tool-button control you add is transparent. Then you can see the tool bar's separator underneath, so it looks like there's a vertical line running through the middle of your control.
When you add a non-tool-button control to the tool bar, the Buttons property indeed lies about the type of the control. You'll notice throughout ComCtrls.pas that TToolBar itself always casts the buttons to TControl and then checks whether they really descend from TToolButton. It's completely legitimate to add non-buttons to a tool bar; that's why the Form Designer allows it in the first place.
I suggest you use the Form Designer to create your tool bar. That way, the IDE will maintain an identifier for you in your form, so you'll always have a direct reference to your panel. You won't have to go hunting for it in the tool bar. Even if you're creating the tool bar manually, it's a good idea to make an extra field to refer to the panel. Even if you move the panel around within the tool bar, it will still be the same object the whole time, so you needn't worry about dangling references.
When you put a couple of buttons and a panel on a toolbar, and a Memo somewhere, then run this code in the form's onCreate:
procedure TForm1.FormCreate(Sender: TObject);
function _IsToolButton(const aObject: TObject): Boolean;
begin
Result := aObject is TToolButton;
end;
function _IsPanel(const aObject: TObject): Boolean;
begin
Result := aObject is TPanel;
end;
var
i: Integer;
begin
for i := 0 to bar.ButtonCount - 1 do begin
Memo.Lines.Add(Format('bar.Buttons[%d].Name: %s', [i, bar.Buttons[i].Name]));
Memo.Lines.Add(Format('bar.Buttons[%d].ClassName: %s', [i, bar.Buttons[i].ClassName]));
Memo.Lines.Add(Format('bar.Buttons[%d] is TToolButton: %s', [i, BoolToStr(_IsToolButton(bar.Buttons[i]), True)]));
Memo.Lines.Add(Format('bar.Buttons[%d] is TPanel: %s', [i, BoolToStr(_IsPanel(bar.Buttons[i]), True)]));
// Memo.Lines.Add(Format('bar.Buttons[%d] has Caption property: %s', [i, 'dunno yet']));
Memo.Lines.Add('');
end;
end;
you'll see that the panel is not a TooButton and most definitely a TPanel.
The debugger showing properties of a ToolButton for the panel, is simply the debugger casting each and every bar.Buttons[i] to a TToolButton. When you right-click on the "Data" tab of the Debug inspector, you can Type Cast it to a TPanel and you will get the correct information.
'is it legitimate?' - well, you are definitely using the toolbar in a way that the creator of the toolbar did not ment it to be used. Will it blow up in your face? Who knows. I guess you could walk through the sourcecode for the toolbar and check if it is safe or not, but what about possible third party tools or components, expecting to find buttons in a toolbar?
I would see if I could find another way of solving my problem. Clever hacks have a tendency to turn out not so clever after all, and it will surely higten the wtf-rate of your code.
Do you have to use a toolbar? What about a flowpanel with buttons and panels instead? Or a panel with a toolbar and a panel?

Resources