5

我的应用程序有一个选项供用户仅在系统托盘中运行,而不是在任务栏中运行。当我的应用程序由 Delphi 6 构建时,这工作正常。切换到 Delphi XE2 后,它不再起作用。

我已经搞砸了一些,我在 Windows 7 上运行它,但是在 Windows XP 上运行时我仍然遇到问题。该应用程序正确地从任务栏中隐藏,并显示在系统托盘中。但是当我创建并显示任何其他表单时,该图标会显示在 Windows XP 中。

procedure TfrmAppointment.HideWindowFromTaskbar;
var
   TaskbarList: ITaskbarList;
begin
Application.MainFormOnTaskBar := False;

// Windows 7 seems to behave differently.  This seems to fix it.
if (CheckWin32Version(6, 1)) then
    begin
    // We are in Win7, and we requested the tray.
    TaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList;
    TaskbarList.HrInit;
    TaskbarList.DeleteTab(Application.Handle);
    end
else
   begin
   // Previous code from D6 days
   ShowWindow(Application.Handle, SW_HIDE);
   SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
   ShowWindow(Application.Handle, SW_SHOWNOACTIVATE);
   end;
end;

如果用户选择在系统托盘中显示应用程序的选项,则运行该代码。它适用于我测试过的所有 Windows 版本。但是,在 Windows XP 上,当我显示任何子窗体时,应用程序会立即显示在任务栏中。在 Windows 7 中一切正常。

有什么我想念的想法吗?

我应该补充一点,我知道这可能与Hide the Main Form in a Delphi 2009 Application 的问题相同,但是我已经设置了 MainFormOnTaskBar,因此该答案似乎不适用。

[编辑:] 更具体地说,我在这里添加更多信息。该应用程序有两种模式:在任务栏中显示和在系统托盘中显示。

第一种模式与任何普通应用程序相同。该应用程序仅存在于任务栏中。它最小化到任务栏。它从任务栏恢复。

第二种模式的行为完全相同,但该任务栏图标仅存在于系统托盘中。因此,当用户最小化应用程序时,我截取该消息,获取“Shell_TrayWnd”/“TrayNotifyWnd”的 TRect,并调用 DrawAnimatedRects() 来模拟托盘的最小化。然后我隐藏主窗体。在来自系统托盘的消息中,我反向绘制相同的动画矩形,并使其再次可见。虽然表单可见,但它不会显示在任务栏中。

这在所有 Windows 版本中都可以正常工作。

我遇到的具体问题是,当显示任何其他表单时,Windows XP 正在任务栏中创建应用程序图标。Windows 7 不这样做。因此,如果 Windows XP 用户只使用应用程序主窗体,则不会出现问题,两种查看模式都可以正常工作。如果他们打开另一个窗口,则应用程序图标会出现,并且即使在该窗口关闭后也会保留在那里。Windows 7 不这样做,并且图标保持消失。

4

2 回答 2

9

你应该设置

Application.MainFormOnTaskBar := True;

在您的 .dpr 文件中,然后永远不要修改该设置。

然后,当您想从任务栏中删除主窗体时,您只需编写

MainForm.Hide;

当您需要再次隐藏主窗体时,请编写

MainForm.Show;

就是这样。

自然地,您会希望显示和隐藏通知区域图标以及隐藏和显示主窗体。


中的代码HideWindowFromTaskbar不是必需的,您应该将其删除。当您的应用程序处于MainFormOnTaskBarequalsTrue模式时,主窗体是一个未拥有的顶级窗口。因此,只要它可见,它就会出现在任务栏上。因此,您可以简单地将主窗体从任务栏中删除。

应用程序中的其他窗体将拥有顶级窗口。通常,它们将归您的主窗体所有。由于是所有者,它们不会出现在任务栏上。

总的来说,您应该尽量避免摆弄窗口样式。您通常可以使您的应用程序按照您需要的方式运行,而无需这样做。更重要的是,如果您必须调整窗口样式,您必须在CreateParams. 这样,当重新创建窗口时,窗口样式将保持不变。但我重申,尽可能避免修改窗口样式。

关键的 MSDN 参考资料是:


这是我可以制作的最小程序来证明这一点:

program MainFormHiding;

uses
  Forms, StdCtrls;

var
  MainForm, OtherForm: TForm;
  Button: TButton;

type
  TEventHandlerClass = class
    class procedure ToggleMainFormVisible(Sender: TObject);
  end;

class procedure TEventHandlerClass.ToggleMainFormVisible(Sender: TObject);
begin
  MainForm.Visible := not MainForm.Visible;
end;

begin
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm, MainForm);
  OtherForm := TForm.Create(Application);
  MainForm.Caption := 'Main Form';

  OtherForm.Visible := True;
  OtherForm.Caption := 'Other Form';
  Button := TButton.Create(OtherForm);
  Button.Caption := 'Toggle';
  Button.Parent := OtherForm;
  Button.OnClick := TEventHandlerClass.ToggleMainFormVisible;

  Application.Run;
end.

在评论中,您明确表示您希望能够隐藏任务栏窗口而不隐藏主窗体。在这种情况下,我建议您设置MainFormOnTaskbarFalse. 这意味着这Application.Handle将是与任务栏按钮关联的窗口。然后,您可以隐藏该窗口以将其从任务栏中删除。

您现在需要PopupParent为任何辅助表单显式设置。如果您希望这些窗口归主窗体所有,那么您可以设置它。

这是针对这种情况调整的示例:

program MainFormHiding;

uses
  Forms, StdCtrls, Windows;

var
  MainForm, OtherForm: TForm;
  Button: TButton;

type
  TEventHandlerClass = class
    class procedure ToggleTaskbarButton(Sender: TObject);
  end;

class procedure TEventHandlerClass.ToggleTaskbarButton(Sender: TObject);
begin
  if IsWindowVisible(Application.Handle) then
    ShowWindow(Application.Handle, SW_HIDE)
  else
    ShowWindow(Application.Handle, SW_SHOW);
end;

begin
  Application.MainFormOnTaskbar := False;
  Application.CreateForm(TForm, MainForm);
  OtherForm := TForm.Create(Application);
  OtherForm.PopupParent := MainForm;
  MainForm.Caption := 'Main Form';
  Application.Title := MainForm.Caption;

  OtherForm.Visible := True;
  OtherForm.Caption := 'Other Form';
  Button := TButton.Create(OtherForm);
  Button.Caption := 'Toggle';
  Button.Parent := OtherForm;
  Button.OnClick := TEventHandlerClass.ToggleTaskbarButton;

  Application.Run;
end.

运行此程序并单击切换按钮。现在您将看到主窗体和其他窗体显示。而且任务栏中什么都没有。我包括了切换按钮,以表明您可以在程序运行时在两种操作模式之间切换。无需重新启动它。

这里的关键是使可见表单以外的窗口成为与任务栏关联的窗口。完成此操作后,您可以通过显示和隐藏该窗口再次控制任务栏的存在。在这种情况下,该窗口是应用程序窗口,Application.Handle. 因为那是任务栏上的窗口,所以你需要设置它的Title属性来控制它的文本。

最后,我再次强调,与任务栏的交互最好通过窗口所有者和可见性来控制。ITaskbarList始终使用这些方法而不是扩展窗口样式等来搜索解决方案。


更新

希望关于这个主题的最后一句话。正如您所注意到的,当主窗体被最小化时,上面的代码表现不佳。发生这种情况时,应用程序窗口将再次可见,并再次出现在任务栏中。

在抑制这种行为方面,我不太确定自己。出现这种行为是因为TApplication.Minimize当主窗体最小化时显示应用程序句柄的代码。我拥有的最佳解决方案是将主窗体最小化转换为隐藏。

procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;

....

procedure TMainForm.WMSysCommand(var Msg: TWMSysCommand);
begin
  if (Msg.CmdType and $FFF0)=SC_MINIMIZE then 
  begin
    Hide;
    exit;
  end;
  inherited;
end;

OnMinimize或者另一种方法是通过TApplication.

class procedure TEventHandlerClass.ApplicationMinimize(Sender: TObject);
begin
  ShowWindow(Application.Handle, SW_HIDE);
end;
于 2013-01-29T16:36:05.297 回答
4

大卫的回答是正确的。它有几个小问题,但我用它运行并让一切正常。在我弄清楚这一点时,他发布了他的最后一次更新。我在这里发布了一些额外的代码示例,并接受了他的回答。首先我分配:

Application.OnMessage := AppMessage;

然后程序如下:

procedure TfrmAppointment.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
// This first check decides if we are minimizing via the upper right button OR
// The context menu in the upper left hand corner of the window. 
// Minimizing twice restores, so this can be a restore as well.
if ((((Msg.message = WM_NCLBUTTONDOWN) and (Msg.wParam = HTMINBUTTON)) or
     ((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_MINIMIZE))) and
    (Screen.ActiveForm = Self)) then
   begin
   // This function is defined as (bool, bool) where the variables are:
   // Param1: Mimimizing (true), Restoring (false)
   // Param2: Draw animation rectangles for doing this or not
   Handled := MinimizeOrRestore(Self.WindowState <> wsMinimized, True);
   end
else if ((Msg.message = WM_SYSCOMMAND) and 
         (Msg.wParam = SC_RESTORE) and 
         (Screen.ActiveForm = Self)) then
   begin
   // Specifically, restore has been asked for
   Handled := MinimizeOrRestore(False, True); // Minimize with animation
   end
else if ((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_CLOSE)) then
   begin
   // The user just used the system menu to close the application
   ApplicationIsClosing := True; // see below for this
   end
end;

然后在我的 FormCloseQuery 中,检查“ApplicationIsClosing”是否为真。如果它是 FALSE,那么我知道用户点击了 X,我只需最小化调用此处引用的其他函数的应用程序。如果是真的,我允许关闭。

最后,MinimizeOnRestore 获取窗体本身的 TRect,以及系统托盘,然后执行 DrawAnimatedRects。这在 Vista 或更高版本上并不总是有效,但也不会出错。接下来,它隐藏主应用程序窗口或使其可见。除非遇到错误,否则它始终返回 true。然后它返回假。

于 2013-01-29T20:43:44.990 回答