Remove controls from a scrollbox - delphi

I am dynamicaly (at run time) adding controls into a TScrollBox using myScrollBox.AddObject
Now I need to remove all the controls I added to put new ones.
I tryed myScrollBox.Controls.Clear but after I call that function, any control I add are not showing up.
(Warning: I'm new to delphi and Firemonkey)
Update 1
Here is how I add my objects (this is just a test function)
procedure TMainForm.TaskDetailsAdd;
var
btn1 : TButton;
intI : Integer;
count: Integer;
begin
scbTaskVariables.BeginUpdate;
count := 0;
for intI := 0 to 100 do
begin
btn1 := TButton.Create(self);
btn1.Text := 'Salut ' + IntToStr(intI);
btn1.Parent := scbTaskVariables;
btn1.OnClick := Button1Click;
btn1.Tag := intI * 10;
btn1.Position.Y := intI * 50;
btn1.Position.X := intI * 15;
scbTaskVariables.AddObject(btn1);
count := scbTaskVariables.ControlsCount;
end;
scbTaskVariables.EndUpdate;
end;
The funny thing is that if I place a break point on count := scbTaskVariables.ControlsCount
I can see that ControlsCount goes from 0 to 1 for the first control and then it stays to 1 for the others.
Update 2
I submitted QC#125440.

The inverse of AddObject is RemoveObject. Call ScrollBox.RemoveObject(aChildObject) for each child object that you wish to remove.
The alternative is to set the Parent property of the child object. Set it to ScrollBox to add it. Set it to nil to remove it. This is interchangeable with AddObject and RemoveObject. You can do it either way.
However, when you attempt to do this, just as your said, attempts to add new controls fail if you have removed controls earlier. This would appear to be a bug. Please submit a QC report.
I tested on XE6.

Try with:
myScrollBox.Content.DeleteChildren;

I have added this as an Answer but as there are bugs in FMX it should be considered as a workaround at this stage.
I spent some time on your problem about deleting your buttons, but also to tried to find out more about the bug. David was very quick to spot this and shows his experience.
Two of my findings were that (1) the AddObect() does not appear to work with the buttons, for some reason, they are not being seen as "Objects" but as "Components". (2) Also I found that creating btn1 with the "scrollBox" as its owner helped to achieve an adequate result.
I used 1 x TScrollbox, 2 x TButton and 4 x TLabel. The buttons left with their default name and the TScrollBox with Your default name. So you can just copy and paste. btn1 is made a private variable along with it's procedures.
procedure TMainForm.TaskDetailsAdd;
var
intI : Integer;
begin
label1.Text := IntToStr(scbTaskVariables.ComponentCount);
// Initial count = 1, Probably the scroll box.
if scbTaskVariables.ComponentCount >1 then
TaskDetailsDel; // Don't create Buttons with same Name if already exists.
scbTaskVariables.BeginUpdate;
for intI := 0 to 99 do
begin
Sleep(20); //Keeps the "Pressed Button" active to prove it is working
btn1 := TButton.Create(scbTaskVariables);
btn1.Parent := scbTaskVariables;
btn1.Position.Y := intI * 50;
btn1.Position.X := intI * 15;
btn1.Tag := intI * 10;
btn1.TabOrder := 10 + intI;
btn1.Name := 'MyBtn' + IntToStr(intI);
btn1.Text := 'Salut ' + IntToStr(intI);
btn1.OnClick := Button1Click;
if btn1.IsChild(scbTaskVariables) = true then
Label2.Text := 'True'
else // All this, proves buttons not seen as children.
Label2.Text := 'False';
scbTaskVariables.AddObject(btn1);
// AddObject() taken out as button is not seen as "FmxObject"
end;
scbTaskVariables.EndUpdate;
Label3.Text := IntToStr(scbTaskVariables.ComponentCount);
// Count now all created (includes ScrollBox).
Label4.Text := IntToStr(scbTaskVariables.ControlsCount);
end;
The "TaskDetailsDel" procedure was was quite easy once I had determined that I was really dealing with "Components"
procedure TMainForm.TaskDetailsDel;
var
intI : Integer;
count: Integer;
begin
label1.Text := '';
label2.Text := '';
label3.Text := '';
label4.Text := '';
for intI := 0 to 99 do
begin
Sleep(20); //Keeps the "Pressed Button" active to prove it is working
btn1 := TButton(scbTaskVariables.FindComponent('MyBtn' + IntToStr(intI)));
btn1.Parent := Nil;
FreeAndNil(btn1);
end;
Count := scbTaskVariables.ComponentCount;
Label1.Text := IntToStr(Count);
end;
Using the FindComponent line did the trick.
Press F1 and type the links into the URL Box; I found these interesting, especially seeing how TButton is derived in the VCL and FMX.
ms-help://embarcadero.rs_xe3/libraries/Vcl.StdCtrls.TButton.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Controls.TButton.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Types.TStyledControl.html
ms-help://embarcadero.rs_xe3/rad/Objects,_Components,_and_Controls.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Types.TFmxObject.AddObject.html

The TScrollBox has by default 1 component that is of type TScrollContent that is responsible of the display of the other components. So if we delete him then nothing will be shown ever.
I created this little function to RemoveAllComponents inside the TScrollBox (Expect the TScrollContent):
procedure RemoveAllComponentsScrollBox(ScrollBox : TScrollBox);
var i : integer; Obj : TFmxObject;
begin
for I := ScrollBox.ComponentCount-1 downto 0 do
begin
if ((ScrollBox.Components[i] is TFmxObject) and not (ScrollBox.Components[i] is TScrollContent)) then
begin
Obj:=TFmxObject(ScrollBox.Components[i]);
Obj.Parent:=nil;
FreeAndNil(Obj);
end;
end;
end;
This method can be improved by recursivity

Related

Trouble synchronizing generic TList and TListBox

I have trouble keeping a TListbox in sync with a TList. Each time an item is added to a generic TList, OnNotify is called and the callback calls just one procedure: create_gradients. Its code is below:
procedure TColor_Dialog.create_gradients;
var Editor: TGradient_Editor;
eGradient: Int32;
y: single;
s: string;
begin
List_Names.Clear;
List_Gradients.Clear;
for eGradient := 0 to FColor_Editor.nGradients - 1 do
begin
List_Names.Items.Add (FColor_Editor [eGradient].Check_Rainbow.Text);
end; // for
List_Gradients.BeginUpdate;
try
for eGradient := 0 to FColor_Editor.nGradients - 1 do
begin
Editor := FColor_Editor [eGradient];
y := (eGradient + 1) * Editor.Height;
Editor.Position.Y := y;
s := Editor.Check_Rainbow.Text;
List_Gradients.AddObject (Editor);
end; // for
finally
List_Gradients.EndUpdate;
end; // try..finally
end; // create_gradients //
As you see it simply enumerates all items in the list. Each item in the list is a TGradient_Editor which in turn has TFrame as a parent. On the parent are some FMX controls as combolorboxes, an image and a checkbox (Check_Rainbow). Check_Rainbow.Text is used for identification purposes. When the gradient editor is created, it creates a unique name from frame_%s where %s is a sequence number that is incremented each time a gradient editor is created. Owner and Parent are both List_Gradients.
From the image above you can see what happens. the listbox on the right is added for checking and just shows the text's, which is the correct sequence by the way. When I use the debugger to follow the addition of the gradient editors to List_Gradient they are processed in the same order. But the order of the gradient editors is wrong. I have to mention that the aligment of the gradient editors is alTop. I added even some code to ensure that the editor is Positioned at the very bottom of the List_Gradients.
I appear not to understand something. I cannot imagine that sequential adding to a TListBox cannot result in the correct order. What am I doing wrong?
Try this instead:
procedure TColor_Dialog.create_gradients;
var
Editor: TGradient_Editor;
eGradient: Int32;
y: single;
begin
List_Names.Clear;
List_Gradients.Clear;
for eGradient := 0 to FColor_Editor.nGradients - 1 do
begin
List_Names.Items.Add (FColor_Editor[eGradient].Check_Rainbow.Text);
end;
List_Gradients.BeginUpdate;
try
y := 0.0; // or whatever value you want to start at...
for eGradient := 0 to FColor_Editor.nGradients - 1 do
begin
Editor := FColor_Editor[eGradient];
Editor.Position.Y := y;
List_Gradients.AddObject(Editor);
y := y + Editor.Height;
end;
finally
List_Gradients.EndUpdate;
end;
end;
As requested I moved the answer to this section. The correct code is:
procedure TColor_Dialog.create_gradients;
var Editor: TGradient_Editor;
eGradient: Int32;
y: single;
begin
List_Gradients.BeginUpdate;
try
List_Gradients.Clear;
y := 0;
for eGradient := 0 to FColor_Editor.nGradients - 1 do
begin
Editor := FColor_Editor [eGradient];
Editor.Position.X := 0;
Editor.Position.Y := y;
Editor.Width := List_Gradients.Width;
List_Gradients.AddObject (Editor);
y := y + Editor.Height;
end; // for
finally
List_Gradients.EndUpdate;
end; // try..finally
end; // create_gradients //
and not using any alignment anymore. Adding Objects to a TListBox is a real nice feature of FMX. However, be prepared that things sometimes work differently than you expect. For one thing: objects are not positioned in the same way as strings.

How can I refer to a control whose name is determined at runtime?

As a kind of self-study exercise, I've made a form which contains six panels in a 2x3 rectangle and I want them to switch between visible and invisible one after another. I'm trying to do so by using a for loop of some kind. I could of course write something like:
Panel1.Visible := true;
Panel1.Visible := false;
Panel2.Visible := true;
Panel2.Visible := false;
Panel3.Visible := true;
etc. etc.
But this takes quite a lot of typing and is pretty inefficient when I decide I want it to wait for 100ms between each step. For example, I'd then have to edit all the six steps to wait. This is doable for six steps, but maybe another time I want to do it a hundred times! So I'm thinking there must also be a way to use a for loop for this, where a variable varies from 1 to 6 and is used in the object identifier. So it would something like this:
for variable := 1 to 6 do begin
Panel + variable.Visible := true;
Panel + variable.Visible := false;
end;
Now, this obviously doesn't work, but I hope somebody here can tell me if this is in fact possible and if yes, how. Maybe I can use a string as the identifier? My explanation is probably pretty bad because I don't know all the technical terms but I hope the code explains something.
You can loop through the panel's Owner's Components array.
var
i: Integer;
TmpPanel: TPanel;
begin
{ This example loops through all of the components on the form, and toggles the
Visible property of each panel to the value that is opposite of what it has (IOW,
if it's True it's switched to False, if it's False it's switched to True). }
for i := 0 to ComponentCount - 1 do
if Components[i] is TPanel then
begin
TmpPanel := TPanel(Components[i]);
TmpPanel.Visible := not TmpPanel.Visible; // Toggles between true and false
end;
end;
You can also use the FindComponent method, if you want a very specific type of component by name. For instance, if you have the 6 panels, and their names are Panel1, Panel2, and so forth:
var
i: Integer;
TmpPanel: TPanel;
begin
for i := 1 to 6 do
begin
TmpPanel := FindComponent('Panel' + IntToStr(i)) as TPanel;
if TmpPanel <> nil then // We found it
TmpPanel.Visible := not TmpPanel.Visible;
end;
end;
This is a situation where you want to create the controls dynamically at runtime rather than at designtime. Trying to grapple with 6 different variables is just going to be a world of pain. And when you need the grid to be 3x4 rather than 2x3, you'll regret that decision even more.
So, start with a completely blank form. And add, in the code, a two dimensional array of panels:
private
FPanels: array of array of TPanel;
Then, in the form's constructor, or an OnCreate event handler, you can initialise the array by calling a function like this:
procedure TMyForm.InitialisePanels(RowCount, ColCount: Integer);
var
Row, Col: Integer;
aLeft, aTop, aWidth, aHeight: Integer;
Panel: TPanel;
begin
SetLength(FPanels, RowCount, ColCount);
aTop := 0;
for Row := 0 to RowCount-1 do begin
aLeft := 0;
aHeight := (ClientHeight-aTop) div (RowCount-Row);
for Col := 0 to ColCount-1 do begin
Panel := TPanel.Create(Self);
FPanels[Row, Col] := Panel;
Panel.Parent := Self;
aWidth := (ClientWidth-aLeft) div (ColCount-Col);
Panel.SetBounds(aLeft, aTop, aWidth, aHeight);
inc(aLeft, aWidth);
end;
inc(aTop, aHeight);
end;
end;
And now you can refer to your panels using cartesian coordinates rather than a flat one dimensional array. Of course, you can easily enough declare a flat one dimensional array as well if you want.
The key idea is that when you are creating large numbers of control in a structured layout, you are best abandoning the designer and using code (loops and arrays).
Use FindComponent method of TComponent:
for variable := 1 to 6 do begin
pnl := FindComponent('Panel' + IntToStr(variable));
if pnl is TPanel then
begin
TPanel(pnl).Visible := true;
TPanel(pnl).Visible := false;
end;
end;
As others have answered, FindComponent is the way to go.
But if you just want to modify generic properties for the component, such as visible, position etc, it's not necessary to compare to the type.
This will work just as fine:
for i := 1 to 16 do
begin
(FindComponent( 'P' + inttostr(i) ) as TControl).Visible := false;
end;
(NOTE: this is for Delphi 6/ 7, modern versions probably do this in other ways)
Actually my answer
If you use a name convention to name your component like
"Mycomponent" + inttostr(global_int)
you can use it to find it very easily :
function getMyComponent(id:integer) : TComponent;
begin
result := {Owner.}FindConponent('MyComponent'+inttostr(id));
end;
You also can make your generated components to interact each other by using (sender as TComponent).name to know which other component are related to him.
Exemple
Following is an example of what you can do with this :
Imagine a pagecontrol where tabs are an interface you want to have multiple time
(for ex, to describe columns in a file with 1 tab = 1 col, and you want to dynamically add tabs).
For our example, we are naming button and edit this way :
Button : "C_(column_number)_btn"
Edit : "C_(column_number)_edi"
You can actually refer directly to the edit with a buttonclick, linked at runtime by calling findcomponent :
procedure TForm1.ColBtnClick(Sender:TObject);
var nr : string; Edit : TEdit;
begin
// Name of the TButton. C(col)_btn
nr := (Sender as TButton).Name;
// Name of the TEdit C_(column)_edi
nr := copy(nr,1,length(nr)-3)+'edi';
// Get the edit component.
edit := (Form1.Findcomponent(nr) as TEdit);
//play with it
Edit.Enabled := Not Edit.Enabled ;
showmessage(Edit.Text);
Edit.hint := 'this hint have been set by clicking on the button';
//...
end;
Of course, you link this procedure to every generated buttons.
If anyone wants to practice with it, you may want to know how to generate the tabsheet and components, here you go :
procedure Form1.addCol(idcol:integer, owner : TComponent); // Form1 is a great owner imo
var
pan : TPanel; // Will be align client with the new tabsheet
c: TComponent; //used to create components on the pannel
tab : TTabSheet;
begin
try
pan := TPanel.create(owner);
pan.name := format('Panel_%d',[idcol]);
pan.caption := '';
// dynamically create that button
c := TButton.create(Owner);
with c as TButton do
begin
Name := format('C%d_btn',[idcol]);
Parent := pan;
//Top := foo;
//Left := bar;
caption := 'press me';
OnClick := Form1.ColBtnClick; // <<<<<<< link procedure to event
end;
//create a Tedit the same way
c := TEdit.create(Owner);
with c as TEdit do
Name := format('C%d_edi',[idcol]);
Parent := pan;
// other properties
// create the tabsheet and put the panel in
finally
tab := TTabSheet.Create(Parent);
tab.caption := 'Column %d';
tab.PageControl := Pagecontrol1;
pan.Parent := tab;
pan.Align := alClient;
end;
end;
Generating names to get the component is actually a very good way to have a clean code.
Scrolling through parent - child components in order to find the one you want is actually inefficient and becomes hell if there is many component (in my example, if there is 3, 10 or unknown number of TEdit looping child (brother) components will be ugly.
Maybe this example is useless but It may helps someone, someday.

TListView: VCL loses the order of columns if you add a column

I'm trying to add a column between existing columns in a TListView. Therefor I add the new column at the end and move it by setting it`s index to the designated value. This works, until adding another new column.
What I did:
Add the column at last position (Columns.Add) and add the subitem at the last position (Subitems.Add) too. Afterwards I move the column by setting it's index to the correct position.
This works fine as long as it's just one column that gets added. When adding a second new column, the subitems get screwed up. The new subitem of the first column is moved to the last position, e.g. like this:
0 | 1 | new A | new B | 3
Caption | old sub 1 | old sub 3 | new Sub B | new sub A
I would be very happy if someone could help!
For example, is there maybe a command or message I can send to the ListView so it refreshes or saves it's Column --> Subitem mapping that I could use after adding the first new column and it's subitems so I can handle the second new column the same way as the first.
Or is this just a bug of TListViews column-->subitem handling or TListColumns...?
example code for a vcl forms application (assign the Form1.OnCreate event):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
listview: TListView;
initButton: TButton;
addColumn: TButton;
editColumn: TEdit;
subItemCount: Integer;
procedure OnInitClick(Sender: TObject);
procedure OnAddClick(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
listview := TListView.Create(self);
with listview do
begin
Left := 8;
Top := 8;
Width := self.Width - 30;
Height := self.Height - 100;
Anchors := [akLeft, akTop, akRight, akBottom];
TabOrder := 0;
ViewStyle := vsReport;
Parent := self;
end;
initButton := TButton.Create(self);
with initButton do
begin
left := 8;
top := listview.Top + listview.Height + 20;
Width := 75;
Height := 25;
TabOrder := 1;
Caption := 'init';
OnClick := OnInitClick;
Parent := self;
end;
editColumn := TEdit.Create(self);
with editColumn do
begin
left := initButton.Left + initButton.Width + 30;
top := listview.Top + listview.Height + 20;
Width := 120;
Height := 25;
TabOrder := 2;
Parent := self;
Caption := '';
end;
addColumn := TButton.Create(self);
with addColumn do
begin
left := editColumn.Left + editColumn.Width + 10;
top := listview.Top + listview.Height + 20;
Width := 75;
Height := 25;
TabOrder := 1;
Enabled := true;
Caption := 'add';
OnClick := OnAddClick;
Parent := self;
end;
end;
procedure TForm1.OnInitClick(Sender: TObject);
var col: TListColumn;
i, j: integer;
item: TListItem;
begin
listview.Items.Clear;
listview.Columns.Clear;
// add items
for I := 0 to 2 do
begin
col := ListView.Columns.Add;
col.Caption := 'column ' + IntToStr(i);
col.Width := 80;
end;
// add columns
for I := 0 to 3 do
begin
item := ListView.Items.Add;
item.Caption := 'ItemCaption';
// add subitems for each column
for j := 0 to 1 do
begin
item.SubItems.Add('subitem ' + IntToStr(j+1));
end;
end;
subItemCount := 5;
end;
procedure TForm1.OnAddClick(Sender: TObject);
var number: integer;
col: TListColumn;
i: Integer;
ascii: char;
begin
listview.Columns.BeginUpdate;
number := StrToInt(editColumn.Text);
ascii := Chr(65 + number);
// create the new column
col := TListColumn(ListView.Columns.add());
col.Width := 80;
col.Caption := ascii;
// add the new subitems
for I := 0 to ListView.Items.Count-1 do
begin
ListView.Items[i].SubItems.Add('subitem ' + ascii);
end;
// move it to the designated position
col.Index := number;
listview.Columns.EndUpdate;
Inc(subItemCount);
end;
end.
Thank you!
Edit: The suggested fix from Sertac Akyuz works fine, though I can't use it because changing the Delphi sourcecode is no solution for my project. Bug is reported.
Edit: Removed the second question that was unintended included in the first post and opened new question (See linked question and Question-revision).
Update: The reported bug is now closed as fixed as of Delphi XE2 Update 4.
Call the UpdateItems method after you've arranged the columns. E.g.:
..
col.Index := number;
listview.UpdateItems(0, MAXINT);
..
Update:
In my tests, I still seem to need the above call in some occasion. But the real problem is that "there is a bug in the Delphi list view control".
Duplicating the problem with a simple project:
Place a TListView control on a VCL form, set its ViewStyle to 'vsReport' and set FullDrag to 'true'.
Put the below code to the OnCreate handler of the form:
ListView1.Columns.Add.Caption := 'col 1';
ListView1.Columns.Add.Caption := 'col 2';
ListView1.Columns.Add.Caption := 'col 3';
ListView1.AddItem('cell 1', nil);
ListView1.Items[0].SubItems.Add('cell 2');
ListView1.Items[0].SubItems.Add('cell 3');
Place a TButton on the form, and put the below code to its OnClick handler:
ListView1.Columns.Add.Caption := 'col 4';
Run the project and drag the column header of 'col 3' to in-between 'col 1' and 'col 2'. The below picture is what you'll see at this moment (everything is fine):
Click the button to add a new column, now the list view becomes:
Notice that 'cell 2' has reclaimed its original position.
Bug:
The columns of a TListView (TListColumn) holds its ordering information in its FOrderTag field. Whenever you change the order of a column (either by setting the Index property or by dragging the header), this FOrderTag gets updated accordingly.
Now, when you add a column to the TListColumns collection, the collection first adds the new TListColumn and then calls the UpdateCols method. The below is the code of the UpdateCols method of TListColumns in D2007 VCL:
procedure TListColumns.UpdateCols;
var
I: Integer;
LVColumn: TLVColumn;
begin
if not Owner.HandleAllocated then Exit;
BeginUpdate;
try
for I := Count - 1 downto 0 do
ListView_DeleteColumn(Owner.Handle, I);
for I := 0 to Count - 1 do
begin
with LVColumn do
begin
mask := LVCF_FMT or LVCF_WIDTH;
fmt := LVCFMT_LEFT;
cx := Items[I].FWidth;
end;
ListView_InsertColumn(Owner.Handle, I, LVColumn);
Items[I].FOrderTag := I;
end;
Owner.UpdateColumns;
finally
EndUpdate;
end;
end;
The above code removes all columns from the underlying API list-view control and then inserts them anew. Notice how the code assigns each inserted column's FOrderTag the index counter:
Items[I].FOrderTag := I;
This is the order of the columns from left to right at that point in time. If the method is called whenever the columns are ordered any different than at creation time, then that ordering is lost. And since items do not change their positions accordingly, it all gets mixed up.
Fix:
The below modification on the method seemed to work for as little as I tested, you need to carry out more tests (evidently this fix does not cover all possible cases, see 'torno's comments below for details):
procedure TListColumns.UpdateCols;
var
I: Integer;
LVColumn: TLVColumn;
ColumnOrder: array of Integer;
begin
if not Owner.HandleAllocated then Exit;
BeginUpdate;
try
SetLength(ColumnOrder, Count);
for I := Count - 1 downto 0 do begin
ColumnOrder[I] := Items[I].FOrderTag;
ListView_DeleteColumn(Owner.Handle, I);
end;
for I := 0 to Count - 1 do
begin
with LVColumn do
begin
mask := LVCF_FMT or LVCF_WIDTH;
fmt := LVCFMT_LEFT;
cx := Items[I].FWidth;
end;
ListView_InsertColumn(Owner.Handle, I, LVColumn);
end;
ListView_SetColumnOrderArray(Owner.Handle, Count, PInteger(ColumnOrder));
Owner.UpdateColumns;
finally
EndUpdate;
end;
end;
If you are not using packages you can put a modified copy of 'comctrls.pas' to your project folder. Otherwise you might pursue run-time code patching, or file a bug report and wait for a fix.

Forms creation and destroying in OnMouseEnter ; OnMouseLeave events in Delphi

Sorry if there is already made such question earlier, but I have no time at the moment to dig in stackoverflow db ...
So, I have this code:
procedure TForm1.GraphPrevBtnMouseEnter(Sender: TObject);
var frm_PrevBtn : TForm;
begin
GraphPrevBtn.Width := 75;
if z = 0 then begin
frm_PrevBtn := TForm.Create(nil);
with frm_PrevBtn do begin
Name := 'frm_PrevBtn';
BorderStyle := bsNone;
Position := poDesigned;
Top := Form1.Top + GraphprevBtn.Top + (form1.Height - Form1.ClientHeight) - 3;
Left := Form1.Left + GraphprevBtn.Left + 3;
Width := GraphprevBtn.Width; Height := GraphprevBtn.Height; transparentColor := True; TransparentColorValue := clbtnFace;
Show;
end;
GraphPrevBtn.Parent := frm_PrevBtn;
if GetLastError = 0 then z := frm_prevBtn.GetHashCode;
end;
end;
procedure TForm1.GraphPrevBtnMouseLeave(Sender: TObject);
var frm_PrevBtn_H : THandle;
begin
// if form is created then- if mouse is under button then- if z = formshashcode ( form is on creatin stage )
if not (FindVCLWindow(Mouse.CursorPos) = GraphPrevBtn) and ((FindControl(FindWindow('TForm','frm_PrevBtn')) as TForm).Visible = True) and (GraphPrevBtn.Parent = FindControl(FindWindow('TForm','frm_PrevBtn')) as TForm) then begin // if mouse is not under graphprevBtn
ShowMessage(FindVCLWindow(Mouse.CursorPos).Name); //
if z = 112 // then if form is created
then begin
GraphPrevBtn.Parent := Form1;
GraphPrevBtn.bringtoFront;
GraphPrevBtn.Top := 29; GraphPrevBtn.Left := 226;
(FindControl(FindWindow('TForm','frm_PrevBtn')) as TForm).Free;
if GetLastError = 0 then z := 0;
end;
end;
end;
So, my wish is the following:
When I enter this GraphPrevBtn with mouse, form is created. As for is created, the focus goes from Control to new form. As focus is to new form, the OnMouseLeave event is fired. As event is fired, it should destroy the form, BUT ONLY IF user ( NOT active control / focus ) actually leaves control by mouse.
What happens now is that either new forms is not destroyed at all or both events goes infinite loop ( *frm_PrevBtn* is created and destroyed again and again and again...).
What would be best solution?
My idea is to get new forms rect and check whenever mouse is inside this rect. If it is, then perform allow OnMouseLeave event, otherwise deattach it ... would it work?
As much I tried with these samples:
http://delphi.about.com/od/windowsshellapi/a/get-active-ctrl.htm
http://delphi.about.com/od/delphitips2010/qt/is-some-delphi-tcontrol-under-the-mouse.htm
No luck. Where is the problem ... ?
Remarks: global var z : byte;
P.S. Thanks for negative votes ... great motivation to use this site in future ...
Mouse enters on 'GraphPrevBtn', you create a form over the button. As soon as this form becomes visible, since mouse is not anymore over 'GraphPrevBtn', 'OnMouseLeave' is fired. You destroy the new form and now mouse is again on the button so 'OnMouseEnter' is fired, hence the infinite loop.
As a solution, you can move the form disposing code to 'OnMouseEnter' of Form1:
procedure TForm1.FormMouseEnter(Sender: TObject);
begin
if z = 112
then begin
GraphPrevBtn.Parent := Form1;
[...]
.. and what's with the 'GetLastError', it seems fully irrelevant. If you're going to use it, at least set last error to '0' by calling GetLastError or SetLastErrorbefore beginning your operation.
Maybe something more like this will help you:
var
frm_PrevBtn : TForm = nil;
procedure TForm1.GraphPrevBtnMouseEnter(Sender: TObject);
var
P: TPoint;
begin
GraphPrevBtn.Width := 75;
if frm_PrevBtn = nil then begin
P := GraphPrevBtn.ClientOrigin;
frm_PrevBtn := TForm.Create(nil);
with frm_PrevBtn do begin
BorderStyle := bsNone;
Position := poDesigned;
SetBounds(P.X, P.Y, GraphPrevBtn.Width, GraphPrevBtn.Height);
TransparentColor := True;
TransparentColorValue := clBtnFace;
GraphPrevBtn.Parent := frm_PrevBtn;
GraphPrevBtn.Top := 0;
GraphPrevBtn.Left := 0;
Show;
end;
end;
end;
procedure TForm1.GraphPrevBtnMouseLeave(Sender: TObject);
begin
if (FindVCLWindow(Mouse.CursorPos) <> GraphPrevBtn) and (frm_PrevBtn <> nil) then begin
GraphPrevBtn.Parent := Self;
GraphPrevBtn.BringToFront;
GraphPrevBtn.Top := 29;
GraphPrevBtn.Left := 226;
FreeAndNil(frm_PrevBtn);
end;
end;
Why don't you do it like this:
MainForm.OnMouseOver: Create a secondary form.
SecondaryForm.OnMouseOver: Set FLAG_ON_SECONDARY.
SecondaryForm.OnMouseLeave: Clear FLAG_ON_SECONDARY.
MainForm.OnMouseLeave: if not FLAG_ON_SECONDARY then destroy the secondary form.
This might not work in case SecondaryForm.OnMouseOver fires after MainForm.OnMouseLeave. Well, think of something similar. Another solution is to start a timer which destroys SecondaryForm and disables itself if mouse is neither on Main nor on SecondaryForm.

how to read registry key for certain condition only?

i have create 5 dynamic buttons in 1 row and store the button info (caption, isActive, etc) in registry. It shows the button in form when the IsActive = TRUE from registry. The result is as below (each button indicate by a * symbol):
*****
When i set the IsActive = FALSE for button2 and button4, the button being remove and left the space as below:
* ** *
Any idea to solve this problem? The source code is as below:
procedure TfrmPOS.CreateDynamicBtn;
var
Reg : TRegIniFile;
lstKey : TStringList;
sKeyName : String;
bActive : boolean;
btn1 : TBitBtn;
i, k, iIcon : integer;
begin
lstKey := TStringList.Create;
Reg := TRegIniFile.Create;
try
//clear bitbtn
if ScrollBox2.ControlCount > 0 then begin
for k := ScrollBox2.ControlCount - 1 downto 0 do begin
with ScrollBox2.Controls[k] AS TBitBtn do begin
Free;
end;
end;
end;
sKeyName := Sysmem.RegKeyRoot+'\POSConfig\ItemSetting\';
Reg := TRegIniFile.Create(sKeyName);
Reg.GetKeyNames(lstKey); //button1, button2,...
for i := 0 to lstKey.Count - 1 do begin
Reg.OpenKeyReadOnly(sKeyName);
bActive := Reg.ReadBool(lstKey.Strings[i], 'IsActive', TRUE);
if bActive = TRUE then begin
//create dynamic bitbtn
btn1 := TBitBtn.Create(self);
btn1.Parent := ScrollBox2;
btn1.Height := 82;
btn1.Width := 82;
btn1.Left := ((i mod 5) * btn1.Width);
btn1.Top := (Trunc((i div 5)) * btn1.Height);
btn1.Caption := Reg.ReadString(lstKey.Strings[i], 'Caption', '');
iIcon := Reg.ReadInteger(lstKey.Strings[i], 'IconImage', 0);
imglstIcon.GetBitmap(iIcon, btn1.Glyph);
btn1.Layout := blGlyphTop;
btn1.Name := lstKey.Strings[i];
btn1.OnClick := OnButtonClick;
end;
Reg.CloseKey;
end;
finally
lstKey.Free;
Reg.Free;
end;
end;
I suspect you wonder why the space for the second button is still there, instead of the third button filling that area.
It's because you're setting the Left properties for the buttons as though all the buttons were there:
btn1.Left := ((i mod 5) * btn1.Width);
When i = 1, you skip over it because that button is invisible. But when i = 3, you calculate its position the same as you would have if button 2 had been visible. Keep a visible-button counter separate from your list iterator, and use it to position your buttons:
BtnIndex := 0;
Reg.OpenKeyReadOnly(sKeyName);
for i := 0 to lstKey.Count - 1 do begin
bActive := Reg.ReadBool(lstKey.Strings[i], 'IsActive', TRUE);
if bActive then begin
//create dynamic bitbtn
btn1 := TBitBtn.Create(self);
btn1.Parent := ScrollBox2;
btn1.SetBounds(BtnIndex mod 5 * 82, BtnIndex div 5 * 82, 82, 82);
Inc(BtnIndex);
There are better ways to do what you want. If you have a sufficiently recent version of Delphi, use a TFlowPanel or TGridPanel. They will arrange your buttons next to each other for you automatically. If your Delphi version didn't come with that control, then try a TToolBar instead and fill it with TToolButton controls.
Your question actually had nothing to do with the registry, but you can make better use of the registry anyway. There's no need to keep re-opening the same key every time. The value of sKeyName doesn't change inside the loop, so open the key once before you enter the loop (as shown above) and then just leave it open for as long as you need it.

Resources