我正在尝试编写一个包含类TMainWindow
的简单单元,以提高我对本机Windows API的了解。
我想像这样使用这个类:
var
MainWindow: TMainWindow;
begin
MainWindow := TMainWindow.Create;
try
MainWindow.ShowModal;
finally
MainWindow.Free;
end;
end.
我有一个差不多工作的原型,但我找不到问题,这是我到目前为止编写的代码:
unit NT.Window;
interface
uses
Windows, Messages, Classes, SysUtils;
type
PObject = ^TObject;
TMainWindow = class(TObject)
private
FChild : HWND; { Optional child window }
FHandle : HWND;
procedure WMCreate (var Msg: TWMCreate); message WM_CREATE;
procedure WMDestroy (var Msg: TWMDestroy); message WM_DESTROY;
procedure WMNcCreate (var Msg: TWMNCCreate); message WM_NCCREATE;
procedure WMPaint (var Msg: TWMPaint); message WM_PAINT;
procedure WMPrintClient (var Msg: TWMPrintClient); message WM_PRINTCLIENT;
procedure WMSize (var Msg: TWMSize); message WM_SIZE;
procedure PaintContent(const APaintStruct: TPaintStruct);
function HandleMessage(var Msg: TMessage): Integer;
public
constructor Create;
procedure DefaultHandler(var Message); override;
function ShowModal: Boolean;
end;
implementation
var
WindowByHwnd: TStringList;
function PointerToStr(APointer: Pointer): string;
begin
Result := IntToStr(NativeInt(APointer));
end;
function StrToPointerDef(AString: string; ADefault: Pointer): Pointer;
begin
Result := Pointer(StrToIntDef(AString, Integer(ADefault)));
end;
function GetWindowByHwnd(hwnd: HWND): TMainWindow;
begin
Result := TMainWindow(StrToPointerDef(WindowByHwnd.Values[IntToStr(hwnd)], nil));
end;
procedure StoreWindowByHwnd(hwnd: HWND; AWindow: TMainWindow);
begin
AWindow.FHandle := hwnd;
WindowByHwnd.Add(IntToStr(hwnd) + '=' + PointerToStr(Pointer(AWindow)));
end;
function WndProc(hwnd: HWND; uiMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
Msg : TMessage;
Window : TMainWindow;
begin
Msg.Msg := uiMsg;
Msg.WParam := wParam;
Msg.LParam := lParam;
Msg.Result := 0;
if uiMsg = WM_NCCREATE then begin
StoreWindowByHwnd(hwnd, TMainWindow(TWMNCCreate(Msg).CreateStruct.lpCreateParams))
end;
Window := GetWindowByHwnd(hwnd);
if Window = nil then begin
Result := DefWindowProc(hwnd, Msg.Msg, Msg.WParam, Msg.LParam);
end else begin
Result := Window.HandleMessage(Msg);
end;
end;
{ TMainWindow }
constructor TMainWindow.Create;
var
wc: WNDCLASS;
begin
inherited Create;
wc.style := 0;
wc.lpfnWndProc := @WndProc;
wc.cbClsExtra := 0;
wc.cbWndExtra := 0;
wc.hInstance := HInstance;
wc.hIcon := 0;
wc.hCursor := LoadCursor(0, IDC_ARROW);
wc.hbrBackground := HBRUSH(COLOR_WINDOW + 1);
wc.lpszMenuName := nil;
wc.lpszClassName := 'Scratch';
if Windows.RegisterClass(wc) = 0 then begin
raise Exception.Create('RegisterClass failed: ' + SysErrorMessage(GetLastError));
end;
if CreateWindow(
'Scratch', { Class Name }
'Scratch', { Title }
WS_OVERLAPPEDWINDOW, { Style }
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT), { Position }
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT), { Size }
0, { Parent }
0, { No menu }
HInstance, { Instance }
@Self { No special parameters }
) = 0 then begin
raise Exception.Create('CreateWindow failed: ' + SysErrorMessage(GetLastError));
end;
end;
procedure TMainWindow.DefaultHandler(var Message);
var
Msg: TMessage;
begin
Msg := TMessage(Message);
Msg.Result := DefWindowProc(FHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
function TMainWindow.HandleMessage(var Msg: TMessage): Integer;
begin
// Dispatch(Msg);
case Msg.Msg of
WM_CREATE : WMCreate( TWMCreate(Msg));
WM_DESTROY : WMDestroy( TWMDestroy(Msg));
WM_NCCREATE : WMNcCreate( TWMNCCreate(Msg));
WM_PAINT : WMPaint( TWMPaint(Msg));
WM_PRINTCLIENT : WMPrintClient(TWMPrintClient(Msg));
WM_SIZE : WMSize( TWMSize(Msg));
else
// DefaultHandler(Msg);
Msg.Result := DefWindowProc(FHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
Result := Msg.Result;
end;
procedure TMainWindow.PaintContent(const APaintStruct: TPaintStruct);
begin
end;
function TMainWindow.ShowModal: Boolean;
var
msg_ : MSG;
begin
ShowWindow(FHandle, CmdShow);
while GetMessage(msg_, 0, 0, 0) do begin
TranslateMessage(msg_);
DispatchMessage(msg_);
end;
Result := True;
end;
procedure TMainWindow.WMCreate(var Msg: TWMCreate);
begin
Msg.Result := 0;
end;
procedure TMainWindow.WMDestroy(var Msg: TWMDestroy);
begin
PostQuitMessage(0);
end;
procedure TMainWindow.WMNcCreate(var Msg: TWMNCCreate);
begin
Msg.Result := Ord(True);
end;
procedure TMainWindow.WMPaint(var Msg: TWMPaint);
var
ps: PAINTSTRUCT;
begin
BeginPaint(FHandle, ps);
PaintContent(ps);
EndPaint(FHandle, ps);
end;
procedure TMainWindow.WMPrintClient(var Msg: TWMPrintClient);
var
ps: PAINTSTRUCT;
begin
ps.hdc := Msg.DC;
GetClientRect(FHandle, ps.rcPaint);
PaintContent(ps);
end;
procedure TMainWindow.WMSize(var Msg: TWMSize);
begin
if FChild <> 0 then begin
MoveWindow(FChild, 0, 0, Msg.Width, Msg.Height, True);
end;
end;
initialization
WindowByHwnd := TStringList.Create;
finalization
WindowByHwnd.Free;
end.
该代码部分基于Raymond Chen的临时程序: http://blogs.msdn.com/b/oldnewthing/archive/2003/07/23/54576.aspx
我正在使用TStringList
在WndProc函数中查找TMainWindow的实例,效率很低,但应该可以正常工作。
当我在Dispatch
函数中使用HandleMessage
时,程序会按原样崩溃并崩溃。
为什么在离开构造函数后或在Dispatch
调用中的修改版本中它会立即崩溃?
答案 0 :(得分:6)
您可以像这样致电CreateWindow
:
CreateWindow(
'Scratch', { Class Name }
'Scratch', { Title }
WS_OVERLAPPEDWINDOW, { Style }
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT), { Position }
Integer(CW_USEDEFAULT),
Integer(CW_USEDEFAULT), { Size }
0, { Parent }
0, { No menu }
HInstance, { Instance }
@Self { No special parameters }
)
除了对最终参数错误的评论外,值是错误的。表达式@Self
是指向本地Self
变量的指针。指向局部变量的指针。这一定很糟糕。您以为您正在传递指向正在创建的对象的指针,但这是由Self
的值直接给出的。 删除@
。
还有一些更直接的方法可以将对象引用与窗口句柄相关联,而不是将句柄和对字符串的引用都转换为进行name = value查找。
对于初学者,您可以使用更加类型安全的关联容器,例如TDictionary<HWnd, TMainWindow>
。这至少可以让你远离所有的字符串转换。
您可以使用SetWindowLongPtr
和GetWindowLongPtr
将对象引用直接与窗口句柄相关联。您可以按如下方式修改代码:
constructor TMainWindow.Create;
// ...
wc.cbWndExtra := SizeOf(Self);
function GetWindowByHwnd(hwnd: HWnd): TMainWindow;
begin
Result := TMainWindow(GetWindowLongPtr(hwnd, 0));
end;
procedure StoreWindowByHwnd(hwnd: HWND; AWindow: TMainWindow);
begin
AWindow.FHandle := hwnd;
SetWindowLongPtr(hwnd, 0, IntPtr(AWindow));
end;
由于您使用的是“额外窗口字节”,因此您需要确保窗口类的后代不会尝试将相同的空间用于其他内容。您希望为后代提供某种机制来“注册”他们想要的空间,将所有后代的请求相加,并将总数放在cbWndExtra
字段中。然后让后代在他们保留的插槽中加载和存储数据。
您可以使用window properties。将对象引用存储在wm_NCCreate
消息中带有SetProp
的属性值中,并使用wm_NCDestroy
消息中的RemoveProp
将其删除。
选择一个不太可能被后代类使用的属性名称。
最后,您可以执行VCL所做的事情,即为每个对象分配一个新的“存根”窗口过程。它有一个模板程序跳转到常规窗口程序的地址;它为新存根分配内存,使用当前对象引用填充模板,然后在调用RegisterClassEx
时使用该存根指针。