How to detect right-click on a TMenuItem with WM_MENURBUTTONUP? - delphi

In a 32-bit Delphi 11 VCL Application on Windows 10, I use a TApplicationEvents component to catch Windows Messages. Unfortunately, TApplicationEvents seems to not react to the WM_MENURBUTTONUP message when I RIGHT-CLICK on a TPopupMenu MenuItem:
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
case Msg.message of
Winapi.Messages.WM_MENURBUTTONUP: CodeSite.Send('TForm1.ApplicationEvents1Message: WM_MENURBUTTONUP');
end;
end;
The Microsoft documentation says:
WM_MENURBUTTONUP message
Sent when the user releases the right mouse button while the cursor is on a menu item.
As an alternative, WM_COMMAND is sent with BOTH Left- and Right-Clicks. However, for a specific purpose, I need to react only when RIGHT-Clicking a menu item.

The quoted part of the documentation explains why you aren't seeing this message:
Sent when the user [...]
The TApplicationEvents.OnMessage event is only able to detect posted messages, not sent messages.
TMainMenu
So if you want to detect this message, you can add
protected
procedure WndProc(var Message: TMessage); override;
to your form class, implemented as follows:
procedure TForm1.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_MENURBUTTONUP then
ShowMessage('rbu')
else
inherited
end;
Try, for instance:
procedure TForm1.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_MENURBUTTONUP then
begin
var MI := Menu.FindItem(Message.LParam, fkHandle);
if Assigned(MI) and InRange(Message.WParam, 0, MI.Count - 1) then
ShowMessageFmt('Menu item "%s" right clicked.', [MI.Items[Message.WParam].Caption]);
end
else
inherited
end;
TPopupMenu
For a TPopupMenu, you need to write your own TPopupList descendant:
type
TPopupListEx = class(TPopupList)
protected
procedure WndProc(var Message: TMessage); override;
end;
{ TPopupListEx }
procedure TPopupListEx.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_MENURBUTTONUP then
ShowMessage('rbu')
else
inherited
end;
initialization
FreeAndNil(PopupList);
PopupList := TPopupListEx.Create;
And make sure to set the TPopupMenu's TrackButton to tbLeftButton.
If you have several popup menus, you might try something like this (not fully tested):
procedure TPopupListEx.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_MENURBUTTONUP then
begin
for var X in PopupList do
if TObject(X) is TPopupMenu then
begin
OutputDebugString(PChar(TPopupMenu(X).Name));
var MI: TMenuItem;
if TPopupMenu(X).Handle = HMENU(Message.LParam) then
MI := TPopupMenu(X).Items
else
MI := TPopupMenu(X).FindItem(HMENU(Message.LParam), fkHandle);
if Assigned(MI) and InRange(Message.WParam, 0, MI.Count - 1) then
begin
ShowMessageFmt('Menu item "%s" right clicked.', [MI.Items[Message.WParam].Caption]);
Break;
end;
end;
end
else
inherited
end;

Related

Synchronize 5 ListBoxes together

im working on a little Project right now and i want to synchronize 5 listBoxes scrolling together. The names of the listboxes are:
KidList
PointList
NoteList
CommentList
CommentListKid
How can i do it?
You could try the following technique.
First, add a private field
private
SyncBoxes: TArray<TListBox>;
to your form and initialise it when the form is created:
procedure TForm1.FormCreate(Sender: TObject);
begin
SyncBoxes := [ListBox1, ListBox2, ListBox3, ListBox4];
end;
Then define the following interposer class:
type
TListBox = class(Vcl.StdCtrls.TListBox)
strict private
procedure Sync;
protected
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;
procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
end;
implemented as
procedure TListBox.CNCommand(var Message: TWMCommand);
begin
inherited;
if Message.NotifyCode = LBN_SELCHANGE then
Sync;
end;
procedure TListBox.Sync;
var
LB: TListBox;
begin
for LB in Form1.SyncBoxes do
if LB <> Self then
LB.TopIndex := Self.TopIndex;
end;
procedure TListBox.WMMouseWheel(var Message: TWMMouseWheel);
begin
inherited;
Sync;
end;
procedure TListBox.WMVScroll(var Message: TWMVScroll);
begin
inherited;
Sync;
end;
Of course, in a real app you would refactor this.
The result is possibly good enough:
The list box's scrolling animation makes synchronisation a little bit delayed, however.

Implementing a TLabel with TabStop and FocusRect?

I'm using Delphi7 and I'm trying to implement a LinkLabel like the ones you can find under the Control Panel on Windows Vista and above.
Changing the cursor/color on hover is really simple, the only thing I need to do is to make the TLabel receive tab stops and to draw a focus rectangle around it.
Any ideas on how to do this? I understand that the TLabel doesn't receive tabs because of its nature. There is also TStaticText which does receive tabs, but it also doesn't have a focus rectangle.
Here's a derived static that draws a focus rectangle when focused. 'TabStop' should be set, or code that checks should be added. Doesn't look quite nice (the control doesn't actually have room for lines at all edges), but anyway:
type
TStaticText = class(stdctrls.TStaticText)
private
FFocused: Boolean;
protected
procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
end;
...
procedure TStaticText.WMSetFocus(var Message: TWMSetFocus);
begin
FFocused := True;
Invalidate;
inherited;
end;
procedure TStaticText.WMKillFocus(var Message: TWMKillFocus);
begin
FFocused := False;
Invalidate;
inherited;
end;
procedure TStaticText.WMPaint(var Message: TWMPaint);
var
DC: HDC;
R: TRect;
begin
inherited;
if FFocused then begin
DC := GetDC(Handle);
GetClipBox(DC, R);
DrawFocusRect(DC, R);
ReleaseDC(Handle, DC);
end;
end;

Delphi, How to show an overlayed control on mouse move

I use Delphi 7 and I have a TFrame (hosted by a TForm) with three panels that span over the whole surface, in a "upside down T" layout.
The panels should be resizeable, so I could use 2 splitters, but I want to give a better user experience: I'd like to have a single "size grip" in the T junction.
This "handle" should appear only when the user hovers the junction area.
So here is my question: what is the best way to have a control show on top of any other on mouse move?
TFrame.OnMouseMove don't get called (obviously) because there are the panels inside and possibly any sort of other controls inside them.
I also strongly want to keep all the code inside the frame.
I see 2 solutions:
Install a local Mouse Hook and go with it. But there could be some
performance issues (or not?)
Handle TApplication.OnMessage inside
the frame, but adding some other code in order to simulate a "chain"
of event handlers. This is because other parts of the application
could need to handle TApplication.OnMessage for their own purposes.
Any other idea?
Thank you
To make a mouse move event notifier for the whole frame, no matter which child control is hovered, you can write a handler for the WM_SETCURSOR message as I've learnt from TOndrej in this post. From such event handler you can then determine which control is hovered and bring it to front.
Please note, I have done quite commonly used mistake here. The GetMessagePos result must not be read this way. It's even explicitly mentioned in docs. I don't have Windows SDK to see the MAKEPOINTS macro, so I'll fix this later:
type
TFrame1 = class(TFrame)
// there are many controls here; just pretend :-)
private
procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR;
end;
implementation
procedure TFrame1.WMSetCursor(var Msg: TWMSetCursor);
var
MsgPos: DWORD;
Control: TWinControl;
begin
inherited;
MsgPos := GetMessagePos;
Control := FindVCLWindow(Point(LoWord(MsgPos), HiWord(MsgPos)));
if Assigned(Control) then
Control.BringToFront;
end;
I'll post this self-answer just because it works and it could be useful in some cases, but I marked TLama's as the best answer.
This is the solution 2) of the question:
TMyFrame = class(TFrame)
// ...design time stuff...
private
FMouseHovering: Boolean;
FPreviousOnAppMessage: TMessageEvent;
procedure DoOnAppMessage(var Msg: TMsg; var Handled: Boolean);
protected
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
implementation
constructor TMyFrame.Create(AOwner: TComponent);
begin
inherited;
FMouseHovering := False;
FPreviousOnAppMessage := Application.OnMessage;
Application.OnMessage := DoOnAppMessage;
end;
destructor TMyFrame.Destroy;
begin
Application.OnMessage := FPreviousOnAppMessage;
inherited;
end;
procedure TRiascoFrame.CMMouseEnter(var Message: TMessage);
begin
FMouseHovering := True;
end;
procedure TRiascoFrame.CMMouseLeave(var Message: TMessage);
begin
FMouseHovering := False;
end;
procedure TMyFrame.DoOnAppMessage(var Msg: TMsg; var Handled: Boolean);
begin
if (Msg.message = WM_MOUSEMOVE) and FMouseHovering then
DoHandleMouseMove(Msg.hwnd, Integer(LoWord(Msg.lParam)), Integer(HiWord(Msg.lParam)));
if Assigned(FPreviousOnAppMessage) then
FPreviousOnAppMessage(Msg, Handled);
end;
procedure TMyFrame.DoHandleMouseMove(hWnd: HWND; X, Y: Integer);
var
ClientPoint: TPoint;
begin
ClientPoint := Point(X, Y);
Windows.ClientToScreen(hwnd, ClientPoint);
Windows.ScreenToClient(Self.Handle, ClientPoint);
if PtInRect(ClientRect, ClientPoint) then
begin
// ...do something...
end;
end;

Catch WM_COPYDATA from Delphi component

I'm trying to write a component, to send string messages between applications by WM_COPYDATA.
I'd like trap the WM_COPYDATA, but this doesn't work:
TMyMessage = class(TComponent)
private
{ Private declarations }
…
protected
{ Protected declarations }
…
procedure WMCopyData(var Msg : TMessage); message WM_COPYDATA;
…
end;
Searching Google a lot, found some reference using wndproc. I tried it, but it isn't working either.
TMyMessage = class(TComponent)
…
protected
{ Protected declarations }
…
procedure WMCopyData(var Msg : TMessage); message WM_COPYDATA;
procedure WndProc(var Msg: TMessage);
…
end;
…
procedure TMyMessage.WndProc(var Msg: TMessage);
begin
//inherited;
if Msg.Msg = WM_COPYDATA then
WMCopyData(Msg);
end;
Please help, what is wrong?
What you have so far is fine, but you need to arrange for messages to be delivered to your component in the first place. That requires a window handle. Call AllocateHWnd and pass it your component's WndProc method. It will return a window handle, which you should destroy as your component is destroyed.
constructor TMyMessage.Create(AOwner: TComponent);
begin
inhreited;
FHandle := AllocateHWnd(WndProc);
end;
destructor TMyMessage.Destroy;
begin
DeallocateHWnd(FHandle);
inherited;
end;
Rather than testing for each message directly, you can let TObject do that for you. That's what the Dispatch method is for. Pass it a TMessage record, and it will find and call the corresponding message-handler method for you. If there is no such handler, it will call DefaultHandler instead. Override that can call DefWindowProc.
procedure TMyMessage.WndProc(var Message);
begin
Dispatch(Message);
end;
procedure TMyMessage.DefaultHandler(var Message);
begin
TMessage(Message).Result := DefWindowProc(Self.Handle, TMessage(Message).Msg,
TMessage(Message).WParam, TMessage(Message).LParam);
end;
Your problem is that TComponent is not a windowed component. WM_COPYDATA is a windows message and is delivered via a window procedure. Hence you need a window handle. Use AllocateHwnd to get hold of one of these.
type
TMyComponent = class(TComponent)
private
FWindowHandle: HWND;
procedure WndProc(var Msg: TMessage);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FWindowHandle := AllocateHwnd(WndProc);
end;
destructor TMyComponent.Destroy;
begin
DeallocateHwnd(FWindowHandle);
inherited;
end;
procedure TMyComponent.WndProc(var Msg: TMessage);
begin
if Msg.Msg=WM_COPYDATA then
//do domething
else
Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
Whatever is sending the messages will need to find a way to get hold of the window handle.
I did it this way:
My web modules which are running in a thread need to send strings to a memo on the main form.
FReceiverFromWS is a THandle
On create:
procedure TWebModuleWebServices.WebModuleCreate(Sender: TObject);
begin
FReceiverFromWS := FindWindow(PChar('TFormWebServices'),PChar(cFormCaption + FormWebServices.Instance)); // Search by class name and caption of receiving form
// ==> you could to that without form caption, but I need to distinguish between running services
if FReceiverFromWS = 0 then
begin
Assert(False,'CopyData receiver NOT found!'); // Probably TFormWebServices not yet created
Exit;
end;
end;
To send messages:
procedure TWebModuleWebServices.SendAMessage(Msg: String);
// Windows will guarantee that the data sent in the COPYDATASTRUCT will exist until after the WM_COPYDATA message
// has been carried out. As such, we must use SendMessage() to send a WM_COPYDATA message. We cannot use PostMessage().
var
lCopyDataStruct: TCopyDataStruct;
begin
lCopyDataStruct.dwData := 0;
lCopyDataStruct.cbData := 1 + Length(Msg);
lCopyDataStruct.lpData := PChar(Msg);
SendMessage(FReceiverFromWS, WM_COPYDATA, wParam(FReceiverFromWS), lParam(#lCopyDataStruct));
end;
In the main form, public method
procedure WMCopyData(var Msg : TWMCopyData) ; message WM_COPYDATA;
is:
procedure TFormWebServices.WMCopyData(var Msg: TWMCopyData);
var
i : integer;
s : string;
begin
i := Msg.CopyDataStruct.dwData;
case i of
0: begin // Message to display
s := String(PChar(Msg.CopyDataStruct.lpData));
AddMemoLine(s);
end;
1: begin // Statistical data
s := String(PChar(Msg.CopyDataStruct.lpData));
FrmWebServiceStats.CollectStats(s);
end;
end;
end;
(As you can see, I actually use dwData to signal the kind of message and handle these differently)

In my custom component, how can I augment the mouse-enter and -leave events?

I am making a custom Panel component which derives TPanel.
I want for my new component to have some code executed on the OnMouseEnter and OnMouseLeave events, however, i do not know how to implement it.
I see that TPanel has published properties OnMouseEnter, OnMouseLeave.
How do i override those and add some of my own code?
The example of my idea:
Default behaviour of TMyPanel which should be in component itself.
on event OnMouseEnter do: Color := NewColor;
on event OnMouseLeave do: Color := OldColor;
And then, i want to be able to assign some function to these events at run time.
This assignment is done in the application.
.. TButton1.Click ..
begin
MyPanel1.OnMouseEnter := DoSomethingMore;
MyPanel1.OnMouseLeave := DoSomethingElse;
end;
so in the end, when mouse is over new panel, it should change color AND do some other actions written in DoSomethingMore procedure.
Thanks
Anoher approach is to handle the windows messages yourself:
type
TMyPanel = class(TPanel)
private
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
published
end;
implementation
{ TMyPanel }
procedure TMyPanel.CMMouseEnter(var Message: TMessage);
begin
// Do whatever your want before the event
if Assigned(OnMouseEnter) then OnMouseEnter(Self);
end;
procedure TMyPanel.CMMouseLeave(var Message: TMessage);
begin
// Do whatever your want before the event
if Assigned(OnMouseLeave) then OnMouseLeave(Self);
end;
EDIT: See below for better VCL compliant version.
If they are available, you should override DoMouseEnter and DoMouseLeave. Otherwise, catch the corresponding messages, like the other answer demonstrates. Don't forget to call inherited, as this will call the events.
Here's a VCL compliant version (tested D2010)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TMyPanel = class(TPanel)
private
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
published
end;
TForm1 = class(TForm)
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Procedure OnMEnter(Sender: TObject);
Procedure OnMLeave(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
With TMyPanel.Create(Form1) do
Begin
Parent := Form1;
Caption := 'Test';
OnMouseEnter := OnMEnter;
OnMouseLeave := OnMLeave;
End;
end;
procedure TForm1.OnMEnter(Sender: TObject);
begin
Form1.Caption := 'Entered';
end;
procedure TForm1.OnMLeave(Sender: TObject);
begin
Form1.Caption := 'Left';
end;
{ TMyPanel }
procedure TMyPanel.CMMouseEnter(var Message: TMessage);
begin
// Do whatever your want before the event
Self.Caption := 'Custom Enter';
// Call inhertied method handler
Inherited;
end;
procedure TMyPanel.CMMouseLeave(var Message: TMessage);
begin
// Do whatever your want before the event
Self.Caption := 'Custom Left';
// Call inhertied method handler
Inherited;
end;
end.

Resources