today I've a question about streaming a part of a form to a file.
In this example i use a Tmemo instead of file in order to see the stream.
here is my form:
The panel on the right top of the form has some controls, like label, edit and so on.
with the "Save panel" butto I save the panel on a TStream:
Here the code:
procedure TfrmMain.btnSaveClick(Sender: TObject);
var
idx: Integer;
MemStr: TStream;
begin
MemStr := TMemoryStream.Create;
PanelStr := TMemoryStream.Create;
try
for idx := 0 to pnlSource.ControlCount - 1 do begin
MemStr.Position := 0;
MemStr.WriteComponent(pnlSource.Controls[idx]);
StreamConvert(MemStr);
end;
PanelStr.Position := 0;
mmoStream.Lines.LoadFromStream(PanelStr);
finally
MemStr.Free;
end;
end;
and here the StreamConvert:
{ Conversione stream in formato testo }
procedure TfrmMain.StreamConvert(aStream: TStream);
var
ConvStream: TStream;
begin
aStream.Position := 0;
ConvStream := TMemoryStream.Create;
try
ObjectBinaryToText(aStream, ConvStream);
ConvStream.Position := 0;
PanelStr.CopyFrom(ConvStream, ConvStream.Size);
lblStreamSize.Caption := IntToStr(ConvStream.Size);
finally
ConvStream.Free;
end;
end;
PanelStr is a TStream object declared in private section of the form and create during form create.
This part works good and, as you can see in right part of the image the elements present on the form are register correctly.
Now my problem is to restore this element into the panel on the left bottom of the form.
I've tryed this routine:
{ Carica i controlli presenti nel pannello pnlSource in uno stream }
procedure TfrmMain.btnLoadClick(Sender: TObject);
var
idx: Integer;
MemStr: TStream;
begin
pnlSource.Free;
MemStr := TMemoryStream.Create;
try
PanelStr.Position := 0;
ObjectTextToBinary(PanelStr, MemStr);
MemStr.Position := 0;
MemStr.ReadComponent(pnlTarget);
finally
MemStr.Free;
end;
end;
but it doesn't work and in the following picture you can see the result:
What is wrong in my routine, and How can I read all the element present in the stream and not only the first?
Can someone help me in this headache?
The code you are currently running effectively transforms the source panel to a label. That's because the first object streamed is a label and the code is reading only one component. IOW, when the reader reaches the first end, reading is complete since there are no sub controls in the stream.
So, first of all, you have to write the panel - and only the panel. The panel is the one that is supposed to stream it's children. To have it to do so, it must own it's controls.
var
idx: Integer;
MemStr: TStream;
begin
MemStr := TMemoryStream.Create;
PanelStr := TMemoryStream.Create;
try
// transfer ownership of controls to the panel
for idx := 0 to pnlSource.ControlCount - 1 do
pnlSource.InsertComponent(pnlSource.Controls[idx]);
// write the panel
MemStr.WriteComponent(pnlSource);
StreamConvert(MemStr);
PanelStr.Position := 0;
mmoStream.Lines.LoadFromStream(PanelStr);
finally
MemStr.Free;
end;
This produces an output to the memo like this:
object pnlSource: TPanel
Left = 8
Top = 8
Width = 201
Height = 265
Caption = 'pnlSource'
TabOrder = 0
object Label1: TLabel
Left = 48
Top = 208
Width = 31
Height = 13
Caption = 'Label1'
end
object Label2: TLabel
...
Note the indentation of the label definition and the missing 'end' of the owning panel (it's at the end).
You will need to register classes for the streamer to be able to find them when loading:
var
idx: Integer;
MemStr: TStream;
begin
pnlSource.Free;
RegisterClasses([TLabel, TEdit, TCheckBox, TRadioButton]);
MemStr := TMemoryStream.Create;
try
PanelStr.Position := 0;
ObjectTextToBinary(PanelStr, MemStr);
MemStr.Position := 0;
MemStr.ReadComponent(pnlTarget);
finally
MemStr.Free;
end;
Registration can be of course moved to elsewhere, like form creation or unit initialization.
You can also transfer ownership of the controls back to the form if it's required, like in the saving code.
As I put in my comments, you need to surround your data with Panel2 information. You also need to register each control type you are saving and restoring.
This means that only the load procedure needs to change - like this:
procedure TfrmMain.btnLoadClick(Sender: TObject);
var
iTemp, iTemp2 : TStringList;
MemStr: TStream;
i: Integer;
begin
// first read the destination panel an put it into a string list
pnlSource.Free;
iTemp := TStringList.Create;
iTemp2 := TStringList.Create;
iTemp.Duplicates := TDuplicates.dupAccept;
iTemp2.Duplicates := TDuplicates.dupAccept;
MemStr := TMemoryStream.Create;
try
PanelStr.Position := 0;
iTemp2.LoadFromStream( PanelStr ); // our original source
PanelStr.Size := 0;
MemStr.Position := 0;
MemStr.WriteComponent(pnlTarget);
StreamConvert(MemStr);
// PanelStr now has our destination poanel.
PanelStr.Position := 0;
iTemp.LoadFromStream( PanelStr );
for i := 0 to iTemp2.Count - 1 do
begin
iTemp.Insert( ITemp.Count - 1, iTemp2[ i ]);
end;
PanelStr.Size := 0;
iTemp.SaveToStream( PanelStr );
PanelStr.Position := 0;
mmoStream.Lines.LoadFromStream(PanelStr);
MemStr.Size := 0;
PanelStr.Position := 0;
ObjectTextToBinary( PanelStr, MemStr);
MemStr.Position := 0;
RegisterClass( TLabel );
RegisterClass( TPanel );
RegisterClass( TEdit );
RegisterClass( TCheckBox );
RegisterClass( TRadioButton );
MemStr.ReadComponent( pnlTarget );
finally
iTemp.Free;
iTemp2.Free;
MemStr.Free;
end;
end;
As commented in the previous answer, registration can be put somewhere else.
Unlike the previous answer, you do not need to change the ownership of the controls first. (That is just a comment - not a criticism). This is just an implementation of my comment.
My naming conventions are different to yours. I have tried to use the same names, but forgive me if I have missed any.
Related
I need to get the number of displayed lines in TMemo (this include the lines that was wrapped because WordWrap is set to true). I need this to auto adjust the height of the Tmemo to it's content.
lines.count of course don't care about wrapped lines so i can't use it. strangely TextPosToPos also don't care about wrapped lines so i can't use it too ...
I m under firemonkey and delphi Berlin
Edit after I learned about ContentsBounds. My original (and obsolete) answer is still visible in the revisions.
Why do you need the number of displayed lines to adjust the height of the TMemo? This resizes the TMemo to its content:
Memo1.Height := Memo1.ContentBounds.Height + 5; // Small const to allow for rendering margins
It also takes word-wrap into consideration.
I don't know why using ContentBounds is "not really ideal". Here's how I do it:
uses
FMX.TextLayout, FMX.Graphics;
function MeasureTextHeight(const AFont: TFont; const AText: string): Single;
var
LLayout: TTextLayout;
begin
LLayout := TTextLayoutManager.DefaultTextLayout.Create;
try
LLayout.BeginUpdate;
try
LLayout.WordWrap := False;
LLayout.Font.Assign(AFont);
LLayout.Text := AText;
finally
LLayout.EndUpdate;
end;
Result := LLayout.TextHeight;
finally
LLayout.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
LTextHeight: Single;
LLines: Integer;
begin
LTextHeight := MeasureTextHeight(Memo1.TextSettings.Font, Memo1.Text);
LLines := Round(Memo1.ContentBounds.Height / LTextHeight);
end;
This is the rough way i use to calculate the number of lines an TMemo (styled) under firemonkey :
type
_TStyledMemoProtectedAccess = class(TStyledMemo);
procedure TALStyledMemo.OnApplyStyleLookupImpl(sender: Tobject);
Var I, j, k, l: integer;
begin
// TALStyledMemo
// TStyledMemo
// TLayout
// TActiveStyleObject
// TLayout
// TScrollBar
// TScrollBar
// TLayout
// TSmallScrollBar
// TSmallScrollBar
// TScrollContent
for I := 0 to controls.Count - 1 do begin
if (controls[i] is TStyledMemo) then begin // << TStyledMemo
fStyledMemo := TStyledMemo(controls[i]);
end;
end;
end;
function TALStyledMemo.getLineHeight: Single;
begin
if fStyledMemo <> nil then result := _TStyledMemoProtectedAccess(fStyledMemo).GetLineHeight
else result := 0;
end;
function TALStyledMemo.getLineCount: integer;
var aLineHeight: Single;
begin
aLineHeight := getLineHeight;
if compareValue(aLineHeight, 0, Tepsilon.Position) > 0 then result := round(ContentBounds.Height / aLineHeight)
else result := 0;
end;
procedure TForm1.controlClick(Sender: TObject);
var
i: Integer;
begin
for i := 2 to Dest.Count-1 do
begin
img[i-2].Create(Form1);
with img[i-2] do begin
Parent:= Panel1;
Width:= 100;
Height:= 150;
Top:= 10;
Left:= (i-2)*100;
end;
end;
end;
img type is array of TImage, control is a tab. I want to timages to show like an android gallery. But this gives me an error Access Violation.
This looks like the classic error in creating an object. Instead of
obj.Create;
you must write:
obj := TSomeClass.Create;
In your case you need to first of all allocate the array:
SetLength(img, Dest.Count-2);
And then in the loop you write:
img[i-2] := TImage.Create(Form1);
to instantiate the images.
My code is the below, it's working correctly but, but after compiling program i see all the fullname and country listed vertically something like :
_________________________________
Fullname1
Country1
Fullname2
Country2
Fullname3
Country3
etc...
SQLQuery1.SQL.Text := 'SELECT * FROM users where user_age="'+age+'"';
SQLQuery1.Open;
rec := SQLQuery1.RecordCount;
SQLQuery1.First; // move to the first record
ListView1.Visible := false;
if rec>0 then
begin
while(not SQLQuery1.EOF)do begin
ListView1.Visible := true;
// do something with the current item
ListView1.AddItem('Full name: '+SQLQuery1['fullname'], Self);
ListView1.AddItem('Country: '+SQLQuery1['cntry'], Self);
// move to the next record
SQLQuery1.Next;
end;
But i want something Like :
First: add the column headers:
var
Col: TListColumn;
begin
Col := ListView1.Columns.Add;
Col.Caption := 'Name';
Col.Alignment := taLeftJustify;
Col.Width := 140;
Col := ListView1.Columns.Add;
Col.Caption := 'Country';
Col.Alignment := taLeftJustify;
Col.Width := 140;
end;
then add the records as follows:
var
Itm: TListItem;
begin
// start of your query loop
Itm := ListView1.Items.Add;
Itm.Caption := SQLQuery1['fullname'];
Itm.SubItems.Add(SQLQuery1['cntry']);
// end of your query loop
end;
Update:
Of course, in order to get the list as in your screenshot, you need to set the ListView's ViewStyle property to vsReport
Your code should look like that:
var
ListItem: TListItem;
...
ListView.Items.BeginUpdate;
try
while(not SQLQuery1.EOF)do begin
ListItem:= ListView.Items.Add;
ListItem.Caption:= 'Full name: '+SQLQuery1['fullname'];
with ListItem.SubItems do begin
Add('Country: '+SQLQuery1['cntry']);
// if you need more columns, add here
end;
SQLQuery1.Next;
end;
finally
ListView.Items.EndUpdate;
end;
You should also set ListView.Style to vsReport to show listview as grid.
I'm not sure how to get the listview to multiline, but I do know you're not using the Query correctly.
As it stands your code has an SQL-injection hole and the implicit reference to 'fieldbyname' inside the loop makes it slow.
var
FullName: TField;
Country: TField;
ListItem: TListItem;
begin
//Use Params or suffer SQL-injections
SQLQuery1.SQL.Text := 'SELECT * FROM users where user_age= :age';
SQLQuery1.ParamByName('age').AsInteger:= age;
SQLQuery1.Open;
if SQLQuery1.RecordCount = 0 then Exit;
//Never use `FieldByName` inside a loop, it's slow.
FullName:= SQLQuery1.FieldByName('fullname');
Country:= SQLQuery1.FieldByName('cntry');
ListView1.Style:= vsReport;
SQLQuery1.First; // move to the first record
SQLQuery1.DisableControls; //Disable UI updating until where done.
try
ListView1.Items.BeginUpdate;
//ListView1.Visible := false;
while (not SQLQuery1.EOF) do begin
//Code borrowed from #Serg
ListItem:= ListView.Items.Add;
ListItem.Caption:= 'Full name: '+Fullname.AsString;
ListItem.SubItems.Add('Country: '+Country.AsString);
SQLQuery1.Next;
end; {while}
finally
SQLQuery1.EnableControls;
ListView1.Items.EndUpdate;
end;
end;
The Delphi documentation contains this example that does exactly what you want.
procedure TForm1.FormCreate(Sender: TObject);
const
Names: array[0..5, 0..1] of string = (
('Rubble', 'Barney'),
('Michael', 'Johnson'),
('Bunny', 'Bugs'),
('Silver', 'HiHo'),
('Simpson', 'Bart'),
('Squirrel', 'Rocky')
);
var
I: Integer;
NewColumn: TListColumn;
ListItem: TListItem;
ListView: TListView;
begin
ListView := TListView.Create(Self);
with ListView do
begin
Parent := Self;
Align := alClient;
ViewStyle := vsReport;
NewColumn := Columns.Add;
NewColumn.Caption := 'Last';
NewColumn := Columns.Add;
NewColumn.Caption := 'First';
for I := Low(Names) to High(Names) do
begin
ListItem := Items.Add;
ListItem.Caption := Names[I][0];
ListItem.SubItems.Add(Names[I][2]);
end;
end;
end;
For all that the Delphi documentation is much maligned, it often has very useful examples like this. The gateway page to the examples is here and the examples are even available on sourceforge so you can check them out using your favourite svn client.
Procedure TForm1.GetUsers;
var
ListItem: TListItem;
begin
try
ListView1.Items.BeginUpdate;
try
ListView1.Clear;
MySQLQuery.SQL.Clear;
MySQLQuery.SQL.Add('select * from users;');
MySQLQuery.Open;
while (not MySQLQuery.EOF) do
begin
ListItem := ListView1.Items.Add;
ListItem.Caption:= VarToSTr(MySQLQuery['username']);
with ListItem.SubItems do
begin
Add(VarToSTr(MySQLQuery['password']));
Add(VarToSTr(MySQLQuery['maxscore']));
end;
MySQLQuery.Next;
end;
MySQLQuery.Close;
finally
ListView1.Items.EndUpdate;
end;
except
on E: Exception do
MessageDlg(PWideChar(E.Message), TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0);
end;
end;
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.
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.