当用户点击模态对话框外部时,是否可以触发事件?
好的,Windows 提供了一些提示,例如制造“咚”的声音或闪烁应用程序的任务栏按钮,但是在没有声音和/或用户无法理解任务栏闪烁原因的情况下,我想提供某种额外的提示。此外,如果模态对话框被隐藏在主窗体后面,我还想尝试使用这种方式将其放到最前面。
当用户点击模态对话框外部时,是否可以触发事件?
好的,Windows 提供了一些提示,例如制造“咚”的声音或闪烁应用程序的任务栏按钮,但是在没有声音和/或用户无法理解任务栏闪烁原因的情况下,我想提供某种额外的提示。此外,如果模态对话框被隐藏在主窗体后面,我还想尝试使用这种方式将其放到最前面。
当鼠标移出对话框时或已经在对话框外部移动时,您可以捕获鼠标。 接着,您可以捕捉 WM_CAPTURECHANGED
事件来触发一个 OnMouseClickOutside
事件:
type
TDialog = class(TForm)
private
FMouseInDialog: Boolean;
FOnMouseClickOutside: TNotifyEvent;
procedure WMCaptureChanged(var Message: TMessage);
message WM_CAPTURECHANGED;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
protected
procedure DoShow; override;
public
property OnMouseClickOutside: TNotifyEvent read FOnMouseClickOutside
write FOnMouseClickOutside;
end;
...
procedure TDialog.CMMouseLeave(var Message: TMessage);
begin
// CM_MOUSELEAVE is also send to the dialog when the mouse enters a control that
// is within the dialog:
if not PtInRect(BoundsRect, Mouse.CursorPos) then
begin
// Now the mouse is really outside the dialog. Start capturing it:
MouseCapture := True;
FMouseInDialog := False;
end;
inherited;
end;
procedure TDialog.CMMouseEnter(var Message: TMessage);
begin
FMouseInDialog := True;
// Only release capture when it had, otherwise it might affect another control:
if MouseCapture then
MouseCapture := False;
inherited;
end;
procedure TDialog.DoShow;
begin
inherited DoShow;
// When mouse is outside the dialog when it should become visible, CM_MOUSELEAVE
// isn't send because the mouse hasn't been inside yet. So also capture mouse
// when the dialog is shown:
MouseCapture := True;
end;
procedure TDialog.WMCaptureChanged(var Message: TMessage);
begin
// When the dialog loses mouse capture and the mouse is outside the dialog, fire:
if (not FMouseInDialog) and Assigned(FOnMouseClickOutside) then
FOnMouseClickOutside(Self);
inherited;
end;
这个方法适用于可见和混淆的对话框。但正如David所评论的那样,这会影响依赖鼠标捕获的控件。我不知道有多少这样的控件,大多数控件(如备忘录或菜单栏)将正常工作。但是拿下拉框为例:当下拉框被打开时,列表框会捕获鼠标。当它失去鼠标时,列表会被收起来。因此,当用户将鼠标移出对话框时(请注意,下拉列表可能在对话框外),组合框将表现出非默认行为。
此外,问题明确指出需要在隐藏对话框的情况下使用此事件。好吧,上述鼠标离开和进入代码取决于对话框可见,所以让我们忘记所有这些,摆脱缺点并将代码简化为:
type
TDialog = class(TForm)
private
FOnMouseClickOutside: TNotifyEvent;
procedure WMCaptureChanged(var Message: TMessage);
message WM_CAPTURECHANGED;
protected
procedure DoShow; override;
public
property OnMouseClickOutside: TNotifyEvent read FOnMouseClickOutside
write FOnMouseClickOutside;
end;
...
procedure TDialog.DoShow;
begin
inherited DoShow;
MouseCapture := True;
end;
procedure TDialog.WMCaptureChanged(var Message: TMessage);
begin
if Assigned(FOnMouseClickOutside) then
FOnMouseClickOutside(Self);
inherited;
end;
BringToFront
无效。(相信我,我已经测试过了,尽管重现一个隐藏的对话框非常困难)。你应该使用 SetWindowPos
将对话框置于所有其他窗口之上:procedure TAnyForm.MouseClickOutsideDialog(Sender: TObject);
begin
if Sender is TDialog then
SetWindowPos(TWinControl(Sender).Handle, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE or SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
end;
但是由于对话框应该始终在其他所有窗口的顶部显示,因此您可以完全消除事件并修改代码如下:
type
TDialog = class(TForm)
private
procedure CMShowingChanged(var Message: TMessage);
message CM_SHOWINGCHANGED;
end;
...
procedure TDialog.CMShowingChanged(var Message: TMessage);
begin
if Showing then
SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE
or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
inherited;
end;
现在,这仍然不能用于消息或系统对话框(虽然您可以使用这些漂亮的对话框),我必须同意David的看法,找出为什么模态对话框会变得模糊不清。如果您有带有FormStyle = fsStayOnTop
(或任何具有HWND_TOPMOST
作为Z顺序的窗口)的表单,则应使用以下适当的应用程序方法来暂时补偿这些窗口:
procedure TAnyForm.Button1Click(Sender: TObject);
var
Dialog: TDialog;
begin
Application.NormalizeAllTopMosts;
Dialog := TDialog.Create(Application);
try
Dialog.ShowModal;
finally
Dialog.Free;
Application.RestoreTopMosts;
end;
end;
DoShow
的功能。但感谢您的第一条评论:组合框(可能还有其他)确实受到了影响! - NGLNS WM_WINDOWPOSCHANGING lpwp:0018EDA8
R WM_WINDOWPOSCHANGING
S WM_NCACTIVATE fActive:False
R WM_NCACTIVATE fDeactivateOK:True
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:False
R WM_NCACTIVATE fDeactivateOK:True
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:False
R WM_NCACTIVATE fDeactivateOK:True
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:False
R WM_NCACTIVATE fDeactivateOK:True
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:False
R WM_NCACTIVATE fDeactivateOK:True
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:False
R WM_NCACTIVATE fDeactivateOK:True
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:False
R WM_NCACTIVATE fDeactivateOK:True
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
P message:0x0118 [Unknown] wParam:0000FFF8 lParam:001A9ECC
S WM_NCACTIVATE fActive:True
R WM_NCACTIVATE
主要表单消息
nHittest:FFFE wMouseMsg:WM_LBUTTONDOWN
S WM_WINDOWPOSCHANGING lpwp:0018EDA8
R WM_WINDOWPOSCHANGING
R WM_SETCURSOR fHaltProcessing:False
nHittest:FFFE wMouseMsg:WM_LBUTTONUP
R WM_SETCURSOR fHaltProcessing:False
WM_NCACTIVATE
消息流,但我真的不建议这么做。 // The message loop for our modal dialogbox
BOOL CALLBACK DialogProc(HWND hwndDlg,
UINT uMsg,
WPARAM wParam,
LPARAM lParam) {
switch(uMsg) {
case WM_INITDIALOG:
return TRUE;
break;
case WM_COMMAND:
switch(wParam) {
case IDOK:
EndDialog(hwndDlg, 0);
return TRUE;
break;
}
break;
case WM_ACTIVATE:
// message sent when the window if being activated/deactivated
if(wParam == WA_INACTIVE) {
// the window is being inactivated so beep once
Beep(750, 300);
// bring dialog to the foreground
SetForegroundWindow(hwndDlg);
}
break;
}
return FALSE;
}
int main(int argc,char** argv) {
// create a modal dialog
DialogBox(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_MYDIALOG),
HWND_DESKTOP,
DialogProc);
return 0;
}
您还可以查看SetWindowsHookEx()和子类化控件,这可能会指引您正确的方向。
WM_ACTIVATE
消息,因为应用程序中的所有其他窗体都被禁用了。因此,窗口管理器知道它不需要激活模态窗口。至少这是我的分析告诉我的原因。 - David Heffernan
modalform.PopupMode := pmAuto
或PopupMode := pmExplicit; Popupparent := MainForm;
。不幸的是,在Delphi 2006中,您没有这个选项。但是您可以了解其他内容,例如Delphi 2006中的Application.PopupMode
。为什么要使用这样糟糕的Delphi版本呢?(至少升级到2007年吧?) - Warren P