Delphi FindVCLWindow returning nil - windows

My application uses mouse wheel scrolling in a number of places.
Thus I’ve written a mouse wheel handler, and this handler works out where the mouse is located before calling the appropriate object method.
On most PCs this works fine, but I have one laptop here where it does not. Despite the handler receiving the correct mouse co-ordinates from Windows, calls to FindVCLWindow are returning nil. This is however only happening when I use the laptop’s internal touch pad. External USB mice work fine.
I’ve updated the laptop’s touch pad driver to the latest available from the manufacturer's web site, but to no avail.
How else can I fix this?
Here’s the code:
unit Mouse_Wheel_Testing;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Grids;
type
TForm1 = class(TForm)
Panel1: TPanel;
StringGrid1: TStringGrid;
Mouse_Coordinates: TEdit;
Control_Name: TEdit;
Button1: TButton;
procedure MouseWheelHandler(var Message: TMessage); override;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.MouseWheelHandler(var Message: TMessage);
var
Target_Control: TWinControl;
begin
with TWMMouseWheel(Message) do
begin
Mouse_Coordinates.Text := IntToStr(XPos) + ', ' + IntToStr(YPos);
Target_Control := FindVCLWindow(Point(XPos, YPos));
if Target_Control <> nil then
Control_Name.Text := Target_Control.Name
else
Control_Name.Text := 'nil';
end;
end;
end.

The reason why FindVCLWindow was returning nil was that WindowFromPoint was returning an incorrect handle. This in turn was the result of a setting in the laptop relating to the behavior of its touch pad when in scrolling mode. This option needed to be set correctly for the correct handle to be returned.
Since my application cannot rely on the user having their laptop set correctly, I have now written a new FindComponent function which is based upon ChildWindowFromPointEx. The following function now resides within the mouse wheel handler:
function Find_Control: TWinControl;
var
Parent: HWND;
Child: HWND;
Position: TPoint;
begin { Find_Control }
Result := nil;
Parent := Self.Handle;
with TWMMouseWheel(Message) do
Position := ScreenToClient(Point(XPos, YPos));
Child := ChildWindowFromPointEx(Parent, Position, CWP_SKIPINVISIBLE);
while (Child <> 0) and (Child <> Parent) do
begin
Result := FindControl(Child);
Position := Point(Position.X - Result.Left, Position.Y - Result.Top);
Parent := Child;
Child := ChildWindowFromPointEx(Parent, Position, CWP_SKIPINVISIBLE);
end; { while (Child <> 0) and (Child <> Parent) }
end; { Find_Control }

Related

Windows slowing down with mouse pointer on destop

I've a strange problem. I started approx. 160 processes. Now, if the mouse pointer is on the Desktop, some actions which used to take 100ms, now take 10 seconds although the total load of the system is 13-16%. Even thrid party programs like processhacker slowing down and doesn't refresh their gui. If I move the mouse pointer over some window no matter which one (could be notepad) even the taskbar can help all goes back to normal. Processhacker is refreshing his lists and the responsivness is back to 100ms.
Since Microsoft-Support won't help use - since or processes are programmed in Borland-Delphi we have no idea how to find out what's going on here.
A colleague tries to reproduce the effect with this little test program:
unit Unit1;
interface
uses
Forms,
ExtCtrls,
Classes,
Controls,
StdCtrls;
const
DEFAULT_INTERVAL = 31;
MOD_VALUE = 5;
MOD_INTERVAL = DEFAULT_INTERVAL * MOD_VALUE;
DEVIATION_BLACK = 2;
DEVIATION_RED = 10;
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
Timer: TTimer;
lastTime: TDateTime;
procedure OnTimer(Sender: TObject);
procedure SetLabel(lbl: TLabel);
end;
var
Form1: TForm1;
GCounterT: Integer;
implementation
uses
Windows,
Graphics,
SysUtils,
DateUtils;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Self.DoubleBuffered := True;
Timer := TTimer.Create(nil);
Timer.Interval := DEFAULT_INTERVAL;
Timer.OnTimer := OnTimer;
GCounterT := 0;
lastTime := Now();
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Timer.Free;
end;
procedure TForm1.OnTimer(Sender: TObject);
begin
Inc(GCounterT);
if (GCounterT mod MOD_VALUE) = 0 then begin
SetLabel(Label1);
GCounterT := 0;
end;
end;
procedure TForm1.SetLabel(lbl: TLabel);
var
newValue: string;
nowTime: TDateTime;
msDiff: Integer;
newColor: TColor;
begin
if IsIconic(Application.Handle) then Exit;
nowTime := Now();
msDiff := MilliSecondsBetween(nowTime, lastTime);
lastTime := nowTime;
newValue := Format('TTimer: %s dev: %d',[FormatDateTime('ss.zzz', nowTime), msDiff - MOD_INTERVAL]);
if (msDiff <= (MOD_INTERVAL + DEVIATION_BLACK))
and (msDiff >= (MOD_INTERVAL - DEVIATION_BLACK)) then
newColor := clGreen
else if (msDiff <= (MOD_INTERVAL + DEVIATION_RED))
and (msDiff >= (MOD_INTERVAL - DEVIATION_RED)) then
newColor := clBlack
else
newColor := clRed;
try
lbl.Font.Color := newColor;
lbl.Caption := newValue;
except
end;
end;
end.
The effect in not as strong as with the original processes, but it's reproduceable.
If one starts 180 of this you can see the same effect only the slowdown is not that severe.
Update Aug 04:
I've added a screenshot from a WPA-Analyze-Session. Here one can see the sequence. Starting with mouse on a Window, then Desktop, Window, Desktop and ending with mouse on Window.
You can see, that the Thread: CSwitch count is going nearly half if the mouse is on the Desktop. What you also could see is that the system load is between 10-17% the whole time.
After we managed to add debug-symbols to some of our processes, we found the issue in the Delphi-VCL/Forms.pas.
In a new trace, with debug-symbols, we saw that the Application.DoMouseIdle method spends a lot of time finding VCLWindows, get Parents of these and so on.
The source of the slowdown is the "FindDragTarget" method. Our processes need no drag'n'drop functionality and they need no hint showing somewhere. So we cut this function call out of the code, which was not easy.
Now everything is running fast undependend from the mouse position.

Display directory from delphi

I would like to display the contents of a directory using DOS commands from Delphi(7). Using Win10 - 64
The following program displays the DOS shell but does not display the directory contents. What is wrong with my code ?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, shellapi;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var i : integer;
begin
i := ShellExecute(Handle, nil, 'cmd.exe', PChar(' dir'), nil, SW_SHOW);
caption := inttostr(i);
end;
end.
Running your code on Windows 10 returns 2, which is ERROR_FILE_NOT_FOUND.
I got it to work, on both 32- and 64-bit target platforms, by changing it to
this:
var
ComSpec: string;
retval: HINSTANCE;
begin
ComSpec := GetEnvironmentVariable('comspec');
retval := ShellExecute(Handle, nil, PChar(comspec), '/k dir', nil, SW_SHOW);
if retval <= 32 then
Caption := Format('Error, return value = %d', [retval])
else
Caption := 'Success';
end;
The /k says to run a new instance of cmd.exe and keep the window open. For more details, run cmd /? from a command prompt.
Note that the error handling of ShellExecute is very limited. If you wish to check for errors comprehensively then you must use ShellExecuteEx instead.

Does FindWindow work in FMX?

I try to exchange data between two applications in windows. I use an example from Zarko Gajic. It uses windows messaging and the example works great. There are a sender and a receiving application and some shared data: all coded for VCL. The code is shown below.
unit SenderMain;
{ How to send information (String, Image, Record) between two Delphi applications
http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm
Learn how to send the WM_CopyData message between two Delphi
applications to exchange information and make two applications
communicate. The accompanying source code demonstrates how to
send a string, record (complex data type) and even graphics
to another application.
~Zarko Gajic
About Delphi Programming
http://delphi.about.com
}
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, TlHelp32,
shared_data;
type
TSenderMainForm = class(TForm)
Button_Send_Data: TButton;
Log: TListBox;
procedure Button_Send_DataClick (Sender: TObject);
protected
procedure Loaded; override;
procedure SendString (send_string: aString);
end; // Class: TSenderMainForm //
var
SenderMainForm: TSenderMainForm;
implementation
{$R *.dfm}
{**************** NextWindow ****************}
function NextWindow (wnd: Thandle; list: Tstringlist):boolean; stdcall;
{This is the callback function which is called by EnumWindows procedure
for each top-level window. Return "true" to keep retrieving, return
"false" to stop EnumWindows from calling}
var
title: array [0..255] of char;
receiverHandle: HWND;//THandle;
win_name: PChar;
s: AnsiString;
begin
getwindowtext (wnd, title, 256);
s := AnsiString (pchar(#title));
if (s <> '') and (list.indexof (string (s)) < 0) then
begin
win_name := PaString (s);
receiverHandle := FindWindow (win_name, nil); // Find receiving app
s := AnsiString (Format ('%s (%d)', [s, receiverHandle]));
list.add (string (s));
end; // if
result:=true;
end;
procedure TSenderMainForm.Loaded;
begin
inherited Loaded;
enumwindows (#nextwindow, lparam (Log.Items)); {pass the list as a parameter}
end;
procedure TSenderMainForm.SendString (send_string: aString);
var copyDataStruct: TCopyDataStruct; { Declared in Windows.pas: TCopyDataStruct}
receiverHandle: THandle;
res: integer;
begin
// Copy string to CopyDataStruct
copyDataStruct.dwData := 1; //use it to identify the message contents
copyDataStruct.cbData := (1 + Length (send_string)) * SizeOf (Char);
copyDataStruct.lpData := PaString (send_string);
receiverHandle := FindWindow (PaString (cClassName), nil); // Find receiving app
if receiverHandle = 0 then // not found
begin
Log.Items.Add ('CopyData Receiver NOT found!');
end else // found, send message
begin
res := SendMessage (receiverHandle, WM_COPYDATA, Integer(Handle), Integer(#copyDataStruct));
Log.Items.Add (Format ('String sent, len = %d, result = %d', [copyDataStruct.cbData, res]));
Log.Items.Add ('"' + PaString (copyDataStruct.lpData) + '"');
end; // if
end; // SendString
procedure TSenderMainForm.Button_Send_DataClick (Sender: TObject);
begin
SendString (ParamStr (0));
end;
====================== Unit copyDataReceiver ================
unit ReceiverMain;
{ How to send information (String, Image, Record) between two Delphi applications
http://delphi.about.com/od/windowsshellapi/a/wm_copydata.htm }
interface
uses
Windows, Messages,
SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, shared_data;
type
TReceiverMainForm = class (TForm)
Log: TListBox;
procedure FormCreate(Sender: TObject);
private
procedure WMCopyData (var Msg: TWMCopyData); message WM_COPYDATA;
procedure WMSignalClose (var Msg: TMessage); message WM_SIGNAL_CLOSE;
procedure HandleCopyDataString (copyDataStruct: PCopyDataStruct);
end;
var ReceiverMainForm: TReceiverMainForm;
implementation
{$R *.dfm}
procedure TReceiverMainForm.FormCreate (Sender: TObject);
begin
Log.Clear;
end;
procedure TReceiverMainForm.WMSignalClose (var Msg: TMessage);
var pfn: PaString;
fn: aString;
begin
Log.Items.Add (Format ('Signal received, WParam = %d, LParam = %d', [Msg.WParam, Msg.LParam]));
pfn := PaString (Msg.LParam);
fn := aString (pfn);
Log.Items.Add (fn);
end;
procedure TReceiverMainForm.WMCopyData (var Msg: TWMCopyData);
var copyDataType: Int32;
begin
copyDataType := Msg.CopyDataStruct.dwData;
//Handle of the Sender
Log.Items.Add (Format ('WM_CopyData (type: %d) from: %d', [copyDataType, msg.From]));
HandleCopyDataString (Msg.CopyDataStruct);
//Send something back
msg.Result := Log.Items.Count;
end;
procedure TReceiverMainForm.HandleCopyDataString (copyDataStruct: PCopyDataStruct);
var mess: aString;
begin
mess := aString (PaString (copyDataStruct.lpData));
Log.Items.Add (Format ('Received string of length %d at %s', [Length (mess), DateToStr (Now)]));
Log.Items.Add ('"' + mess + '"');
end;
end.
================ unit shared_data ==========================
unit shared_data;
interface
uses Messages;
const
WM_SIGNAL_CLOSE = WM_APP + 2012;
ARG_AMI_1 = 285;
ARG_AMI_2 = 1;
cClassName = 'TReceiverMainForm';
type
aString = string;
PaString = PChar;
implementation
end.
The crux of the sender application is that it sends a WM_COPYDATA to the receiver. In order to find the receiver, FindWindow is used with the name of the receiving application (hard-coded) which returns a handle to the window. If the handle is zero, an error is shown.
When I duplicate this in an FMX application there are troubles. The FMX receiving part does not work, while the VCL receiver can receive messages from either the VCL sender or the FMX sender. The code of the FMX receiver is shown below.
Because I wasn't sure about the name of the windows I enumerated all windows, added the numeric handle to each window name and showed it in a listbox in the sender. All handles are zero. I have two questions:
Why are all handles zero in the enumeration?
Why can't I send a message to the FMX receiving applation?
Any help would be greatly appreciated.
unit copyDataReceiver;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts, FMX.ListBox,
Windows, Messages, shared_data;
type
TReceiverMainForm = class (TForm)
Log: TListBox;
procedure FormCreate(Sender: TFMXObject);
private
procedure WMCopyData (var Msg: TWMCopyData); message WM_COPYDATA;
procedure WMSignalClose (var Msg: TMessage); message WM_SIGNAL_CLOSE;
procedure HandleCopyDataString (copyDataStruct: PCopyDataStruct);
end;
var ReceiverMainForm: TReceiverMainForm;
implementation
{$R *.fmx}
procedure TReceiverMainForm.FormCreate (Sender: TFMXObject);
begin
Log.Clear;
end;
procedure TReceiverMainForm.WMSignalClose (var Msg: TMessage);
var pfn: PaString;
fn: aString;
begin
Log.Items.Add (Format ('Signal received, WParam = %d, LParam = %d', [Msg.WParam, Msg.LParam]));
pfn := PaString (Msg.LParam);
fn := aString (pfn);
Log.Items.Add (fn);
end;
procedure TReceiverMainForm.WMCopyData (var Msg: TWMCopyData);
var copyDataType: Int32;
begin
copyDataType := Msg.CopyDataStruct.dwData;
//Handle of the Sender
Log.Items.Add (Format ('WM_CopyData (type: %d) from: %d', [copyDataType, msg.From]));
HandleCopyDataString (Msg.CopyDataStruct);
//Send something back
msg.Result := Log.Items.Count;
end;
procedure TReceiverMainForm.HandleCopyDataString (copyDataStruct: PCopyDataStruct);
var mess: aString;
begin
mess := aString (PaString (copyDataStruct.lpData));
Log.Items.Add (Format ('Received string of length %d at %s', [Length (mess), DateToStr (Now)]));
Log.Items.Add ('"' + mess + '"');
end;
end.
Why can't I send a message to the FMX receiving applation?
For VCL-Forms the ClassName is derived from the name of the form by simply
adding a leading 'T' to then Name.
e.g. If you have a Form named MyForm the ClassName is TMyForm.
Self.ClassName returns this name and a call to
Winapi.Windows.FindWindow(PChar(Self.ClassName), nil) returns the correct
Handle.
With FMX-Forms you will receive a ClassName builded in similar way.
For FMX-Forms the ClassName is derived from the name of the form by
adding leading 'FMT' to the name of the Form.
The ClassName returned by Self.ClassName, however, is the same as for VCL-Forms.
e.g. If you have a Form named MyFMXForm the ClassName is FMTMyFMXForm but
Self.ClassName returns TMyFMXForm.
Therefore an attempt to get the window-handle with that ClassName fails.
The correct call is
Winapi.Windows.FindWindow(PChar('FMTMyFMXForm'), nil)); .
FindWindow works just the same under FMX. The problem is that sending messages to the window that you find will not result in them being routed to the form's message handlers.
Instead you should do what you should always have done, even with the VCL. That is use a known window whose lifetime you control. Remember that VCL windows are subject to recreation. In other words, you might have a window handle for a window in another process, but that window may be destroyed before you get a chance to send your message to it.
Resolve this by using AllocateHWnd or CreateWindow to create a window that will not be recreated. A window whose lifetime you control. You'll have to devise a way for the other process to discover your window. Personally I would use CreateWindow with a known class name, and then enumerate top level windows with EnumWindows looking for windows that that class name.

Delphi XE and Trapping Arrow Key with OnKeyDown

I want my form to handle the arrow keys, and I can do it -- as long as there is no button on the form. Why is this?
Key messages are processed by the controls themselves who receives these messages, that's why when you're on a button the form is not receiving the message. So normally you would have to subclass these controls, but the VCL is kind enough to ask the parenting form what to do if the form is interested:
type
TForm1 = class(TForm)
..
private
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
..
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
if not (Msg.CharCode in [VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT]) then
inherited;
end;
François editing: to answer the OP original question, you need to call onKeyDown somehow so that his event code would work (feel free to edit; was too long for a comment).
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
case Msg.CharCode of
VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
if Assigned(onKeyDown) then
onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
else
inherited
end;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DOWN: Top := Top + 5;
VK_UP: Top := Top - 5;
VK_LEFT: Left := Left - 5;
VK_RIGHT: Left := Left + 5;
end;
end;
Arrow keys are used to navigate between buttons on a form. This is standard Windows behaviour. Although you can disable this standard behaviour you should think twice before going against the platform standard. Arrow keys are meant for navigation.
If you want to get the full low down on how a key press finds its way through the message loop I recommend reading A Key's Odyssey. If you want to intercept the key press before it becomes a navigation key, you need to do so in IsKeyMsg or earlier. For example, Sertac's answer gives one such possibility.
Only the object that has the focus can receive a keyboard event.
To let the form have access to the arrow keys event,
declare a MsgHandler in the public part of the form.
In the form create constructor, assign the Application.OnMessage to this MsgHandler.
The code below intercepts the arrow keys only if they are coming from a TButton descendant. More controls can be added as needed.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := Self.MsgHandler;
end;
procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
var
ActiveControl: TWinControl;
key : word;
begin
if (Msg.message = WM_KEYDOWN) then
begin
ActiveControl := Screen.ActiveControl;
// if the active control inherits from TButton, intercept the key.
// add other controls as fit your needs
if not ActiveControl.InheritsFrom(TButton)
then Exit;
key := Msg.wParam;
Handled := true;
case Key of // intercept the wanted keys
VK_DOWN : ; // doStuff
VK_UP : ; // doStuff
VK_LEFT : ; // doStuff
VK_RIGHT : ; // doStuff
else Handled := false;
end;
end;
end;
Because they are preempted to deal with setting the focus on the next available WinControl.
(I'm pretty sure that if you put an Edit instead of a Button you see the same thing).
If you want to handle them yourself, you can provide the Application with an OnMessage event that will filter those before they are processed and handle them yourself there.
var
KBHook: HHook; {this intercepts keyboard input}
implementation
{$R *.dfm}
function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt; stdcall;
begin
case WordParam of
vk_Space: ShowMessage ('space') ;
vk_Right:ShowMessage ('rgt') ;
vk_Left:ShowMessage ('lft') ;
vk_Up: ShowMessage ('up') ;
vk_Down: ShowMessage ('down') ;
end; {case}
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc,HInstance,GetCurrentThreadId());
end;
This code will work even when a control is focused (buttons , listboxes), so be careful some controls may loose their keyboard events (Read David haffernans answer) .
keyboard events with Focused controls
eg: If you are having textbox in your app and want to recive text(if focused) also , then
add an applicationevent1
procedure TForm4.ApplicationEvents1Message(var Msg: tagMSG;var Handled: Boolean);
begin
if Msg.message = WM_KEYFIRST then
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc,HInstance,GetCurrentThreadId());
end;
add the following code at the bottom of the function KeyboardHookProc
UnhookWindowsHookEx(KBHook);
and remove
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc, HInstance,
GetCurrentThreadId());
from oncreate event.

Can overriding the CreateParams procedure allow me to still have full access to the WS_SYSMENU?

Complete source code can be found here:
http://www.eyeClaxton.com/download/delphi/SkinProject.zip
I'm trying to create a skinned form with no "Caption or Borders", but still leaving me with the full access to System Menu (I.E: Move, Minimize, Maximize, Restore and Size). I can achieve all of the menu items by overriding the CreateParams procedure by using WS_SYSMENU, WS_MAXIMIZEBOX, WS_MINIMIZEBOX. Using the WS_SIZEBOX gives me access to the menu "Size" command but paints a border I do not want. I have included a complete (Delphi 7) example in the link above. If more information is needed, please feel free to ask.
procedure TMainFrm.CreateParams(var Params: TCreateParams);
begin
FormStyle := fsNormal;
try
if (BorderIcons <> []) then BorderIcons := [];
if (BorderStyle <> bsNone) then BorderStyle := bsNone;
inherited CreateParams(Params);
Params.ExStyle := (Params.ExStyle and (not WS_EX_WINDOWEDGE)
and (not WS_EX_STATICEDGE) and (not WS_EX_DLGMODALFRAME) and (not WS_EX_CLIENTEDGE));
Params.Style := (Params.Style and (not WS_CAPTION) and (not DS_MODALFRAME)
and (not WS_DLGFRAME) and (not WS_THICKFRAME));
Params.Style := (Params.Style or WS_SYSMENU or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_SIZEBOX);
finally
Position := poScreenCenter;
end;
end;
SOLUTION:
unit WndProcUnit;
interface
uses
Windows, Messages, Classes, Controls, Forms, SysUtils;
type
EWndProc = class(Exception);
TWndProcMessages = class(TComponent)
private
{ Private declarations }
FOwnerWndProc: TFarProc;
FNewWndProc: TFarProc;
protected
{ Protected declarations }
procedure WndProc(var theMessage: TMessage); virtual;
public
{ Public declarations }
constructor Create(theOwner: TComponent); override;
destructor Destroy(); override;
procedure DefaultHandler(var theMessage); override;
end;
TWndProc = class(TWndProcMessages)
private
{ Private declarations }
protected
{ Protected declarations }
procedure Loaded(); override;
public
{ Public declarations }
constructor Create(theOwner: TComponent); override;
destructor Destroy(); override;
published
{ Published declarations }
end;
implementation
{ TWndProcMessages }
constructor TWndProcMessages.Create(theOwner: TComponent);
var
X, I: Integer;
begin
inherited Create(theOwner);
if (not (Owner is TForm)) then
raise EWndProc.Create('TWndProc parent must be a form!');
I := 0;
for X := 0 to (Owner.ComponentCount - 1) do
begin
if (Owner.Components[X] is TWndProc) then Inc(I);
if (I > 1) then Break;
end;
if (I > 1) then
begin
raise EWndProc.Create('The form already contains a TWndProc!');
end
else begin
FOwnerWndProc := TFarProc(GetWindowLong((Owner as TForm).Handle, GWL_WNDPROC));
FNewWndProc := Classes.MakeObjectInstance(WndProc);
if (not (csDesigning in ComponentState)) then
SetWindowLong((Owner as TForm).Handle, GWL_WNDPROC, LongInt(FNewWndProc));
end;
end;
destructor TWndProcMessages.Destroy();
begin
if Assigned(FNewWndProc) then
try
Classes.FreeObjectInstance(FNewWndProc);
finally
if (Pointer(FNewWndProc) <> nil) then Pointer(FNewWndProc) := nil;
end;
if Assigned(FOwnerWndProc) then Pointer(FOwnerWndProc) := nil;
inherited Destroy();
end;
procedure TWndProcMessages.DefaultHandler(var theMessage);
begin
if ((Owner as TForm).Handle <> 0) then
begin
case TMessage(theMessage).Msg of
WM_DESTROY:
SetWindowLong((Owner as TForm).Handle, GWL_WNDPROC, LongInt(FOwnerWndProc));
WM_INITMENU:
EnableMenuItem(TMessage(theMessage).WParam, SC_SIZE, MF_BYCOMMAND or MF_ENABLED);
else
with TMessage(theMessage) do
Result := CallWindowProc(FOwnerWndProc, (Owner as TForm).Handle, Msg, WParam, LParam);
end;
end
else
inherited DefaultHandler(theMessage);
end;
procedure TWndProcMessages.WndProc(var theMessage: TMessage);
begin
Dispatch(theMessage);
end;
{ TWndProc }
constructor TWndProc.Create(theOwner: TComponent);
begin
inherited Create(theOwner);
end;
destructor TWndProc.Destroy();
begin
inherited Destroy();
end;
procedure TWndProc.Loaded();
begin
inherited Loaded();
if (not (csDesigning in ComponentState)) then
GetSystemMenu((Owner as TForm).Handle, False);
end;
end.
Complete "updated" source code can be found here:
http://www.eyeClaxton.com/download/delphi/SkinProject.zip
Instead of having a border-less form and faking borders and caption all in the client area, the correct way to do this would be to handle WM_NCPAINT and draw your caption and border in the non-client area. Then, you wouldn't have to use an undocumented message to show the system menu on a caption-less window, or try to have the 'size' system menu item enabled on a window without a sizing border.
Anyway, if you want a quick workaround, enable the item yourself:
type
TMainFrm = class(TForm)
[...]
procedure FormCreate(Sender: TObject);
private
procedure WmInitMenuPopup(var Msg: TWMInitMenuPopup); message WM_INITMENUPOPUP;
[...]
procedure TMainFrm.FormCreate(Sender: TObject);
begin
GetSystemMenu(Handle, False); // force a copy of the system menu
[...]
end;
procedure TMainFrm.WmInitMenuPopup(var Msg: TWMInitMenuPopup);
begin
inherited;
if Msg.SystemMenu then
EnableMenuItem(Msg.MenuPopup, SC_SIZE, MF_BYCOMMAND or MF_ENABLED);
end;
PS:
In the code sample in the question, you're excluding WS_THICKFRAME, but including WS_SIZEBOX. They're, in fact, the same flag.
You've got a bit of a weird try-finally in your CreateParams. Form positioning have got nothing to do with the preceding code, you can put the 'Position := ' statement just before or after setting 'FormStyle' and drop the try-finally.

Resources