当用户在模态对话框外点击时,是否可以触发事件?
好的,Windows通过发出“bonk”声音或闪烁应用程序的任务栏按钮来提供它自己的线索,但我想为声音不可用的情况提供某种额外的线索和/或者用户无法识别任务栏闪烁的原因。此外,我想尝试使用它作为一种方式,如果它隐藏在主窗体后面,将模态对话框放在前面。
答案 0 :(得分:5)
当鼠标移出对话框时,或者在显示对话框之外时,您可以捕获鼠标。然后,您可以抓住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;
这很有效。对于可见和混淆的对话框。但正如大卫感激地评论的那样,这对依赖鼠标捕获的控制产生了影响。我所知道的并不多,大多数控件如备忘录或菜单栏都能正常运行。但是拿一个组合框:当一个组合框被放下时,列表框会捕获鼠标。当它丢失鼠标时,列表就会被包裹起来。因此,当用户将鼠标移到对话框外时(请注意,下拉列表可能位于对话框外),组合框将显示非默认行为。
此外,该问题特别指出了在隐藏对话框中对此事件的需求。好吧,上面的鼠标离开和输入代码取决于对话框是否可见,所以让我们忘记所有这些,摆脱缺点并将代码减少到:
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;
现在,这仍然不适用于消息或系统对话框(尽管你可以使用these nice dialogs),我必须同意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;
在所有其他情况下,模态对话框的消失表明你正在做一些与VCL无法处理的异常事情。
答案 1 :(得分:3)
你要求的东西并不容易实现。我用两种形式创建了一个简单的项目,一个主窗体和一个模态窗体。然后,当模态表单处于活动状态时,单击主表单时,我跟踪发送到每个表单的消息(使用Spy ++)。请记住,主窗体被禁用作为显示模态窗体的协议的一部分。这意味着Windows知道主窗体无法获得焦点,窗口管理器不会将单击转发到任一窗体。发送的消息是为了执行模态形式的闪烁效果。
模态表单消息
S 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
消息流,但我真的不会尝试这个。
在我看来,你需要更仔细地研究基本问题。你说模态形式有时在主形式之下。在这种情况下,您对window ownership做错了。主要形式应该是你的模态形式的最终所有者,如果是这样,那么它永远不会在主形式之下。在我看来,你只需修复破损的窗口所有权结构,问题就会消失。
答案 2 :(得分:2)
我不确定如何在delphi中执行此操作,但使用C ++可以执行以下操作:
// 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()和Subclassing Controls。