Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/json/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi-当用户在模式对话框外单击时,如何生成事件?_Delphi_Delphi 2006 - Fatal编程技术网

Delphi-当用户在模式对话框外单击时,如何生成事件?

Delphi-当用户在模式对话框外单击时,如何生成事件?,delphi,delphi-2006,Delphi,Delphi 2006,当用户在模式对话框外单击时是否可能触发事件 好的,Windows通过发出“砰”的声音或闪烁应用程序的任务栏按钮提供了自己的提示,但我想提供一些额外的提示,用于声音不可用和/或用户无法识别任务栏闪烁的原因的情况。此外,我想尝试使用这个方法把模态对话框放到前面,如果它已经隐藏在主窗体后面。 我不知道如何在Delphi中实现这一点,但是用C++你可以做这样的事情: // The message loop for our modal dialogbox BOOL CALLBACK DialogPro

当用户在模式对话框外单击时是否可能触发事件


好的,Windows通过发出“砰”的声音或闪烁应用程序的任务栏按钮提供了自己的提示,但我想提供一些额外的提示,用于声音不可用和/或用户无法识别任务栏闪烁的原因的情况。此外,我想尝试使用这个方法把模态对话框放到前面,如果它已经隐藏在主窗体后面。

我不知道如何在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;
 }

你也可以看看,也许这会为你指明正确的方向。

你所要求的并不容易实现。我创建了一个包含两种形式的简单项目,一种是主形式,另一种是模态形式。然后,我跟踪了在模式窗体处于活动状态时单击主窗体时发送到每个窗体的消息(使用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
主要表单消息

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\u NCACTIVATE
消息流,但我真的不会尝试这样做

在我看来,你需要更仔细地审视这个根本问题。你说情态形式有时在主形式之下。在这种情况下,你是做错了你的工作。主形态应该是你的模态形态的最终拥有者,如果是这样的话,那么它永远不可能在主形态之下。在我看来,您只需修复损坏的窗口所有权结构,问题就会消失。

首先,回答以下问题: 您可以在鼠标移动到对话框外部时捕获鼠标,或者在显示时鼠标已经在对话框外部时捕获鼠标。然后,您可以捕获WM_CAPTURECHANGED以触发鼠标单击外部的
事件:

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;
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;
现在,如果事件触发怎么办?对话框仍然隐藏,对
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
    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;
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_topst
作为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可能无法处理的异常操作。

模态表单不会收到
WM\u ACTIVATE
,因为应用程序中的所有其他表单都被禁用。因此,窗口管理器知道它不需要激活模式窗口。至少这就是我的分析告诉我的原因。@DavidHeffernan-我没有说以上是正确的解决方案,只是它可以给他一个提示,告诉他如何通过处理为模式窗口生成的一些消息来解决问题。您可以从我的回答中看到哪些消息到达模式窗口。在这种情况下,OP在Windows中遇到了一个设计缺陷,Delphi 2006尚未修改以解决该缺陷。在windows XP及更高版本中,启用过程窗口重影时,会导致应用程序中的窗口丢失Z顺序,除非它们已正确标记为窗口父项,而在windows中的此错误变得至关重要之前,Delphi应用程序通常不会这样做。微软的雷达上几乎没有Delphi,而这个Windows故障在最近的Delphi版本中早已得到解决。@WarrenP MS不能因为不正确的窗口所有权而受到责备。像这样捕获鼠标肯定会有后果吗?@David不,不是真的。几乎每次单击鼠标都会发生鼠标捕获更改。这就是为什么当鼠标远离备忘录或组合框时,它仍然会滚动。对。因此,如果模式对话框打开了备忘录,这将失败,因为它将捕获鼠标?@David否,它仅在鼠标离开对话框时捕获鼠标,当鼠标离开对话框边界时捕获鼠标。@David我已更新了答案,并在其中解释了
DoShow
的功能。但是谢谢你的第一个评论:组合框(以及其他可能的组合框)确实受到了影响!如果你是