Right-click doesn't update ItemIndex in TControlList - delphi

I've added a TPopupMenu to a TControlList, on right-click the ItemIndex isn't updated to reflect the item clicked on. Is there a way to make a right-click respond in a similar way to a left-click?
This would be nice so that the user right-clicks on a particular item, the PopupMenu is associated with that item, not the currently focused item.

You can use HotItemIndex property from OnPopupMenu event and save it to a variable. Then for the popup menu items events you can use it.
Example code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.ControlList, Vcl.StdCtrls;
type
TForm1 = class(TForm)
ControlList1: TControlList;
PopupMenu1: TPopupMenu;
TestPopupMnu: TMenuItem;
Label1: TLabel;
Memo1: TMemo;
procedure ControlList1BeforeDrawItem(AIndex: Integer; ACanvas: TCanvas; ARect:
TRect; AState: TOwnerDrawState);
procedure ControlList1Click(Sender: TObject);
procedure TestPopupMnuClick(Sender: TObject);
procedure PopupMenu1Popup(Sender: TObject);
private
FPopupItemIndex : Integer;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ControlList1BeforeDrawItem(AIndex: Integer; ACanvas: TCanvas;
ARect: TRect; AState: TOwnerDrawState);
begin
Label1.Caption := AIndex.ToString;
end;
procedure TForm1.ControlList1Click(Sender: TObject);
begin
Memo1.Lines.Add('Clicked item ' + ControlList1.ItemIndex.ToString);
end;
procedure TForm1.TestPopupMnuClick(Sender: TObject);
begin
Memo1.Lines.Add('Test PopupMenu item ' + FPopupItemIndex.ToString);
end;
procedure TForm1.PopupMenu1Popup(Sender: TObject);
begin
FPopupItemIndex := ControlList1.HotItemIndex;
end;
end.
The form itself:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 299
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object ControlList1: TControlList
Left = 24
Top = 20
Width = 317
Height = 200
ItemCount = 5
ItemMargins.Left = 0
ItemMargins.Top = 0
ItemMargins.Right = 0
ItemMargins.Bottom = 0
ParentColor = False
PopupMenu = PopupMenu1
TabOrder = 0
OnBeforeDrawItem = ControlList1BeforeDrawItem
OnClick = ControlList1Click
object Label1: TLabel
Left = 92
Top = 20
Width = 31
Height = 13
Caption = 'Label1'
end
end
object Memo1: TMemo
Left = 368
Top = 24
Width = 241
Height = 201
Lines.Strings = (
'Memo1')
TabOrder = 1
end
object PopupMenu1: TPopupMenu
OnPopup = PopupMenu1Popup
Left = 436
Top = 128
object TestPopupMnu: TMenuItem
Caption = 'Test'
OnClick = TestPopupMnuClick
end
end
end

In order to handle Right Clicks you will have to make use of OnMouseDown or OnMouseUp events instead of OnClick event.
Unlike OnClick event that only detects left clicks OnMouseDown and OnMouseUp events are able to detect all mouse clicks (left, right, middle).
procedure TForm1.ControlList1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button = mbRight then
begin
//Do what needs to be done on right click
end
else if Button = mbMiddle then
begin
//Do what needs to be done on middle click
end
end;

The HotItemIndex property can be used to detect which item has been right-clicked by using the following
procedure TListingList.clListingsMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
inherited;
if Button <> mbRight then
Exit;
if clListings.HotItemIndex <> -1 then
clListings.ItemIndex := clListings.HotItemIndex;
end;
This mostly works, but if you already have a TPopupmenu visible already, then the HotItemIndex is -1. This means that consecutive right-clicks don't popup for the correct item - but I can live with this. I think a MousePosToItemIndex or ItemIndexUnderMouse method would be required to fix this properly.

Related

How to show the hint of a first-level TMainMenu item on MouseOver without opening the menu?

In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I have a TMainMenu with a hint on each menu item:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.AppEvnts;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
mFile: TMenuItem;
mEdit: TMenuItem;
mOpen: TMenuItem;
ApplicationEvents1: TApplicationEvents;
procedure ApplicationEvents1Hint(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
CodeSiteLogging;
procedure TForm1.ApplicationEvents1Hint(Sender: TObject);
begin
CodeSite.Send('TForm1.ApplicationEvents1Hint: Application.Hint', Application.Hint);
end;
end.
This is the DFM:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 366
ClientWidth = 639
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Segoe UI'
Font.Style = []
Menu = MainMenu1
Position = poScreenCenter
ShowHint = True
PixelsPerInch = 120
TextHeight = 20
object MainMenu1: TMainMenu
Left = 248
Top = 144
object mFile: TMenuItem
Caption = 'File'
Hint = 'Click here to open the File menu'
object mOpen: TMenuItem
Caption = 'Open'
Hint = 'Click here to open a File'
end
end
object mEdit: TMenuItem
Caption = 'Edit'
Hint = 'Click here to open the Edit menu'
end
end
object ApplicationEvents1: TApplicationEvents
OnHint = ApplicationEvents1Hint
Left = 248
Top = 160
end
end
When I hover the mouse pointer over the "File" menu item, there is NO Application hint! Only after OPENING the File menu, I do get an Application.Hint when hovering the mouse pointer over the "File" menu item.
So how can I get notified when hovering the mouse pointer over the mFile menu item without opening the menu?
If you only want to react on mouse cursor movements (and not on keyboard input) then process the WM_NCMOUSEMOVE message:
interface
TfrmMain= class( TForm )
mnuMain: TMainMenu; // The menu, containing at least one top item
protected
procedure WmNcMouseMove(var vMsg: TWMNCMouseMove); message WM_NCMOUSEMOVE;
end;
implementation
procedure TfrmMain.WmNcMouseMove(var vMsg: TWMNCMouseMove);
var
iItem: Integer;
vR: TRect;
vP: TPoint;
oItem: TMenuItem;
begin
inherited;
// Only react to menu related mouse cursor moves
if vMsg.HitTest= HTMENU then begin
oItem:= nil; // Not found yet
for iItem:= 0 to self.mnuMain.Items.Count- 1 do begin // All topmost items
if GetMenuItemRect( self.Handle, self.mnuMain.Handle, iItem, vR ) then begin
// Also checking Y is needed, as a menu can have more than 1 line.
// Consider sizing your window width to the minimum to see this effect.
vP.X:= vMsg.XCursor;
vP.Y:= vMsg.YCursor;
if PtInRect( vR, vP ) then begin // X>= left< right; Y>= top< bottom
oItem:= self.mnuMain.Items[iItem]; // Found the item under the mouse cursor
break;
end;
end else break; // Makes no sense to continue on any error
end;
// Now get the .hint or otherwise display empty text
if oItem<> nil then self.Caption:= oItem.Hint else self.Caption:= '';
end else self.Caption:= ''; // Any other NC area should reset text, too
end;
Successfully tested with Delphi 7 on Windows 7 x64 with themes disabled (Windows 95 look), 1 monitor only and the menu having 1 or even 2 lines (very short window width): hovering the mouse cursor over the menu items without clicking them will display the correct hint.
Maybe a desktop spanning multiple monitors needs additional work.
Covering keyboard input (Alt or F10) is already done with your code (TApplicationEvents.OnHint).
Discovered GetMenuItemRect() in Delphi: Menu Hint bug and WM_NCMOUSEMOVE in Not receiving WM_NCHitTest on title bar.
PtInRect() compares X and Y accurately: X >= left of the rectangle, but X < right (not <=); same for Y. Only left/top are inclusive, while right/bottom are exclusive.
I changed the logic of your idea which is great!
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus,
Vcl.AppEvnts, Vcl.ComCtrls;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
mFile: TMenuItem;
mEdit: TMenuItem;
mOpen: TMenuItem;
StatusBar1: TStatusBar;
ApplicationEvents1: TApplicationEvents;
procedure ApplicationEvents1Hint(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
procedure WmNcMouseMove(var vMsg: TWMNCMouseMove); message WM_NCMOUSEMOVE;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
CodeSiteLogging;
procedure TForm1.ApplicationEvents1Hint(Sender: TObject);
begin
StatusBar1.SimpleText := Application.Hint;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
StatusBar1.SimpleText := '';
end;
procedure TForm1.WmNcMouseMove(var vMsg: TWMNCMouseMove);
var
iItem: Integer;
vR: TRect;
oItem: TMenuItem;
begin
inherited;
StatusBar1.SimpleText := '';
if vMsg.HitTest = HTMENU then
begin
oItem := nil;
for iItem := 0 to Self.MainMenu1.Items.Count - 1 do
begin
if GetMenuItemRect(Self.Handle, Self.MainMenu1.Handle, iItem, vR) then
begin
if (vMsg.XCursor >= vR.Left) and (vMsg.XCursor <= vR.Right) and (vMsg.YCursor >= vR.Top) and (vMsg.YCursor <= vR.Bottom) then
begin
oItem := Self.MainMenu1.Items[iItem];
BREAK;
end;
end
else
BREAK;
end;
if Assigned(oItem) then
StatusBar1.SimpleText := oItem.Hint;
end;
end;
end.
What do you think?

How to capture KeyDown when focused controls interfere?

I have a form with KeyPreview=true and want to capture the arrow keys, unless we are in a control that should handle those.
The issue is: focus is always on one of those controls.
How can I adapt/design this to work?
.PAS file
unit uKeyDownTests;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TFrmKeyDownTests = class(TForm)
PnlBottom: TPanel;
PnlClient: TPanel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
public
end;
var
FrmKeyDownTests: TFrmKeyDownTests;
implementation
{$R *.dfm}
type
THackWinControl = class(TWinControl);
procedure TFrmKeyDownTests.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var lActiveControl: TControl;
begin
// Earlier code, but that did not work either:
// if Edit1.Focused or Edit2.Focused or Edit3.Focused then Exit;
lActiveControl := ActiveControl;
if Assigned(lActiveControl) then
begin
if lActiveControl = Edit1 then
begin
THackWinControl(Edit1).KeyDown(Key,Shift);
Exit;
end;
if lActiveControl = Edit2 then
begin
THackWinControl(Edit2).KeyDown(Key,Shift);
Exit;
end;
if lActiveControl = Edit3 then
begin
THackWinControl(Edit3).KeyDown(Key,Shift);
Exit;
end;
end;
if (Key = VK_RIGHT) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'R';
Key := 0;
Exit;
end;
if (Key = VK_LEFT) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'L';
Key := 0;
Exit;
end;
if (Key = VK_UP) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'U';
Key := 0;
Exit;
end;
if (Key = VK_DOWN) then
begin
PnlBottom.Caption := PnlBottom.Caption + 'D';
Key := 0;
Exit;
end;
end;
end.
.DFM file
object FrmKeyDownTests: TFrmKeyDownTests
Left = 0
Top = 0
Caption = 'Keydown tests'
ClientHeight = 336
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
KeyPreview = True
OldCreateOrder = False
OnKeyDown = FormKeyDown
PixelsPerInch = 96
TextHeight = 13
object PnlBottom: TPanel
Left = 0
Top = 295
Width = 635
Height = 41
Align = alBottom
TabOrder = 0
end
object PnlClient: TPanel
Left = 0
Top = 0
Width = 635
Height = 295
Align = alClient
TabOrder = 1
object Edit1: TEdit
Left = 40
Top = 32
Width = 121
Height = 21
TabOrder = 0
Text = 'Edit1'
end
object Edit2: TEdit
Left = 40
Top = 72
Width = 121
Height = 21
TabOrder = 1
Text = 'Edit1'
end
object Edit3: TEdit
Left = 40
Top = 112
Width = 121
Height = 21
TabOrder = 2
Text = 'Edit1'
end
end
end
(Answering my own question for my specific situation, slightly different from the one in the 'Possible dupe', but based on the answers there)
In my case, the easiest solution was:
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY; which only calls inherited
KeyPreview=true for the form
A FormKeydown that handles what I want to do with arrow keys
Result:
The controls that have focus as well as the form handle the arrow keys
It does not matter if the controls have an OnKeyDown handler (the Edit2 control) or not (the others)
Modified code:
unit uKeyDownTests;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TFrmKeyDownTests = class(TForm)
PnlBottom: TPanel;
PnlClient: TPanel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY; public
end;
var
FrmKeyDownTests: TFrmKeyDownTests;
implementation
{$R *.dfm}
procedure TFrmKeyDownTests.DialogKey(var Msg: TWMKey);
begin
inherited;
end;
procedure TFrmKeyDownTests.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
case Key of
VK_RIGHT: PnlBottom.Caption := PnlBottom.Caption + 'R';
VK_LEFT : PnlBottom.Caption := PnlBottom.Caption + 'L';
VK_UP : PnlBottom.Caption := PnlBottom.Caption + 'U';
VK_DOWN : PnlBottom.Caption := PnlBottom.Caption + 'D';
end;
end;
{ TFrmKeyDownTests }
procedure TFrmKeyDownTests.Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
PnlBottom.Caption := PnlBottom.Caption + '-kd-';
end;
end.

Uncheckable dynamically created TCheckBox in Delphi XE4

I want to create a property editor because a lot of things does not supported by TValueListEditor. So I use a TStringGrid and other controls placed on it when the user enter the cells. When I place a TCheckBox for boolean values, the dynamically created TCheckBox is uncheckable. The onClick event handler does not fiered by the clicks (the grid fiered) and the caption of the TCheckBox lost its opacity. I set its parent and bring it to the front. By this time I used TEdit and TComboBox controls as well and they work fine. Somebody can help to use it in the expected way?
Here is an example to recreate the situation.
pas:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids,
StdCtrls;
type
TForm1 = class(TForm)
StringGrid1: TStringGrid;
CheckBox1: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
procedure onCheckBoxClicked( sender_ : TObject );
public
{ Public declarations }
fCheckBox : TCheckBox;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.onCheckBoxClicked( sender_ : TObject );
begin
if ( TCheckBox( sender_ ).checked ) then
TCheckBox( sender_ ).caption := 'true'
else
TCheckBox( sender_ ).caption := 'false';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
fCheckBox := TCheckBox.create( NIL );
fCheckBox.Parent := stringGrid1;
fCheckBox.caption := 'Dynamic checkbox';
fCheckBox.left := 70;
fCheckBox.top := 30;
fCheckBox.onClick := onCheckBoxClicked;
fCheckBox.BringToFront;
stringgrid1.cells[1,1] := 'fgfgfgfgfgf';
stringgrid1.cells[1,2] := 'fgfgfgfgfgf';
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
fCheckBox.Free;
end;
end.
The dfm:
object Form1: TForm1
Left = 358
Top = 183
Caption = 'Form1'
ClientHeight = 601
ClientWidth = 854
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object StringGrid1: TStringGrid
Left = 120
Top = 72
Width = 320
Height = 120
TabOrder = 0
end
object CheckBox1: TCheckBox
Left = 192
Top = 128
Width = 97
Height = 17
Caption = 'Static checkbox'
TabOrder = 1
end
end
This does not work with a checkbox because the string grid intercepts processing of the WM_COMMAND message. When you click the checkbox, a WM_COMMAND notification is sent to its parent - which is the string grid. The grid, in TCustomGrid.WMCommand of 'Vcl.Grids', checks if the notification is from its inplace editor and discards the message otherwise.
You can modify the processing of the message on the grid to change the behavior. One way is to derive a new control. E.g.
type
TStringGrid = class(vcl.grids.TStringGrid)
protected
procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;
end;
TForm1 = class(TForm)
StringGrid1: TStringGrid;
....
...
procedure TStringGrid.WMCommand(var Message: TWMCommand);
var
Control: TWinControl;
begin
inherited;
Control := FindControl(Message.Ctl);
if Assigned(Control) and (Control <> InplaceEditor) then
Control.Perform(Message.Msg, MakeWParam(Message.ItemID, Message.NotifyCode),
Message.Ctl);
end;
Then the OnClick will fire. You don't need BringToFront, it works among sibling controls.
Regarding opacity, it's the checkbox's default appearance. You can verify this by placing a checkbox overlapping a label on the form itself.

Dynamically created TPopup menu does not call the OnClick event handlers of its items in Delphi XE4

I want to test something and created a simple dynamic popup menu example. I had to realize that the popup menu appears normally but it does not call the onClick event handler. I tried to rename everything to avoid the name collisions, made the event handler virtual, public, but it did not solve the problem. I have restarted the IDE (I think this code should work fine) but it is the same. The compiler options are the new project defaults. The statically created (placed on the form) popupmenu works fine, just the dynamiically created one take a rest.
Which property of the dynamic menu should I fill up with some value? Somebody explain the reason, please!
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Menus;
type
TForm1 = class(TForm)
Button1: TButton;
staticPopupMenu: TPopupMenu;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure addMenuItem( popupmenu_ : TPopupMenu; caption_ : string; tag_ : integer; onClick_ : TNotifyEvent );
procedure onmenuitemclick1( sender_ : TObject );
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.addMenuItem( popupmenu_ : TPopupMenu; caption_ : string; tag_ : integer; onClick_ : TNotifyEvent );
var
menuitem1 : tmenuitem;
begin
menuitem1 := tmenuitem.create( popupmenu_ );
menuitem1.caption := caption_;
menuitem1.Tag := tag_;
menuitem1.onclick := onclick_;
popupmenu_.items.add( menuitem1 );
end;
procedure TForm1.onmenuitemclick1( sender_ : TObject );
var
id : integer;
begin
id := tmenuitem( sender_ ).Tag;
showmessage( 'menuitem.onclick called! (' + intToStr( id ) + ')' );
end;
procedure TForm1.Button1Click(Sender: TObject);
var
dynamicPopupMenu : tpopupmenu;
begin
dynamicPopupMenu := tpopupmenu.create( self );
try
addMenuItem( dynamicPopupMenu, 'aaa', 1, onmenuitemclick1 );
addMenuItem( dynamicPopupMenu, 'bbb', 2, onmenuitemclick1 );
dynamicPopupMenu.popup( 500, 500 );
finally
dynamicPopupMenu.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
staticPopupMenu.items.Clear;
addMenuItem( staticPopupMenu, 'aaa', 1, onmenuitemclick1 );
addMenuItem( staticPopupMenu, 'bbb', 2, onmenuitemclick1 );
staticPopupMenu.popup( 500, 500 );
end;
end.
The dfm:
object Form1: TForm1
Left = 339
Top = 270
Caption = 'Form1'
ClientHeight = 601
ClientWidth = 854
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 96
Top = 128
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object Button2: TButton
Left = 177
Top = 128
Width = 75
Height = 25
Caption = 'Button2'
TabOrder = 1
OnClick = Button2Click
end
object staticPopupMenu: TPopupMenu
Left = 280
Top = 128
end
end
Since you are creating your dynamicPopupMenu with the current TForm1 instance
as Owner, I'm not really sure you need all the rigmarole of your garbage collector,
as the TForm1 instance will destroy it anyway when it itself is destroyed.
Try this:
Set a breakpoint on TComponent.Destroy in Classes.Pas and a watch on Tag.
Change your Button1Click as shown below, disable your garbage collector,
compile, run and observe.
{code}
procedure TForm1.Button1Click(Sender: TObject);
var
dynamicPopupMenu : tpopupmenu;
AForm : TForm;
begin
AForm := TForm.Create(Nil);
dynamicPopupMenu := tpopupmenu.create(AForm);
try
dynamicPOpUpMenu.Tag := 666;
addMenuItem( dynamicPopupMenu, 'aaa', 1, onmenuitemclick1 );
addMenuItem( dynamicPopupMenu, 'bbb', 2, onmenuitemclick1 );
dynamicPopupMenu.popup( 600, 600 );
finally
AForm.Release;
end;
end;
Here's my take on the issue. A user defined message should do just fine with very little overhead. So...
Define a Windows message:
const
WM_FREE_MY_DYNAMENU = WM_USER + 0;
Move the dynamicPopupMenu variable to the form and define a handler for the message:
TForm12 = class(TForm)
...
protected
dynamicPopupMenu: TPopupMenu;
procedure FreeMyDynaMenu(var Message: TMessage); message WM_FREE_MY_DYNAMENU;
Implement it:
procedure TForm12.FreeMyDynaMenu(var Message: TMessage);
begin
dynamicPopupMenu.Free;
end;
Finally in the Button1Click replace the call to Free with posting the message:
finally
// dynamicPopupMenu.Free;
PostMessage(self.Handle, WM_FREE_MY_DYNAMENU, 0, 0);
You should also NOT assign the form as the owner when you create the menu:
dynamicPopupMenu := tpopupmenu.create( nil ); // self replaced with nil
And an even more simple alternative to the above (although not to my liking) would be to use a TTimer to delay the call to Free.

Delphi XE2 VCL styles, updating caption blocks other controls invalidation

Found a glitch with VCL styles: when you update the form caption, other controls previously redrawn within the same procedure don't get repainted, and you are forced to call Repaint, losing valuable processing time to redraw.
Example: (set project options/vcl style manually)
unit Unit11;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm11 = class(TForm)
Button1: TButton;
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form11: TForm11;
implementation
{$R *.dfm}
procedure TForm11.Button1Click(Sender: TObject);
begin
Panel1.Caption := 'test';
caption := 'glitch';
end;
end.
object Form11: TForm11
Left = 0
Top = 0
Caption = 'Form11'
ClientHeight = 89
ClientWidth = 352
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 8
Top = 8
Width = 121
Height = 57
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object Panel1: TPanel
Left = 135
Top = 8
Width = 185
Height = 57
Caption = 'Panel1'
TabOrder = 1
end
end
program Project10;
uses
Vcl.Forms,
Unit11 in 'Unit11.pas' {Form11},
Vcl.Themes,
Vcl.Styles;
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
TStyleManager.TrySetStyle('Cobalt XEMedia');
Application.CreateForm(TForm11, Form11);
Application.Run;
end.
Set the caption calls in the sequence.
First form.caption, then child.caption.
Once you've called the wrong sequence, then stopped working the correct sequence. That's why I use here, the "set default" button.
This proceed, as long as there is no fix for it, I can live with that.
procedure TForm11.Button1Click(Sender: TObject);
begin // wrong order
Panel1.Caption := 'test';
caption := 'glitch';
end;
procedure TForm11.Button2Click(Sender: TObject);
begin // right order
caption := 'glitch';
Panel1.Caption := 'test';
end;
procedure TForm11.Button3Click(Sender: TObject);
var
i:integer;
begin // count no refresh
for i:= 1 to 300 do begin
caption := 'glitch '+intToStr(i);
Panel1.Caption := 'test '+intToStr(i);
end;
end;
procedure TForm11.Button4Click(Sender: TObject);
var
i:integer;
begin // count with refresh
for i:= 1 to 300 do begin
caption := 'glitch '+intToStr(i);
Panel1.Caption := 'test '+intToStr(i);
Panel1.Refresh;
end;
end;
procedure TForm11.Button5Click(Sender: TObject);
begin // set default
caption := 'Form11';
Panel1.Caption := 'Panel1';
Panel1.Refresh;
end;
end.
If you found another solution. Please tell me.

Resources