如何停止我的应用程序在任务栏上显示?

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上一切正常。

有什么想法我漏掉了什么吗?

我应该补充说,我知道这很可能是与在Delphi 2009应用程序中隐藏主窗体相同的问题,但是我已经设置了MainFormOnTaskBar,所以那个答案似乎不适用。

[编辑:]为了更加具体,我在此添加了其他信息。此应用程序有两种模式:在任务栏中显示和在系统托盘中显示。

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

第二种模式的行为与第一种完全相同,但任务栏图标仅存在于系统托盘中。因此,当用户最小化应用程序时,我会拦截该消息,获取“Shell_TrayWnd” /“TrayNotifyWnd”的TRect,并调用DrawAnimatedRects()来模拟最小化到托盘。然后我隐藏主窗体。收到来自系统托盘的消息后,我会以相反的方式绘制相同的动画矩形,并使其再次可见。在窗体可见时,它不会显示在任务栏中。
所有Windows版本中这都可以正常工作。
我遇到的具体问题是,当显示任何其他窗体时,Windows XP会在任务栏中创建应用程序图标。Windows 7不会这样做。因此,如果Windows XP用户仅使用应用程序主窗体,则不会出现任何问题,并且两个查看模式都可以正常工作。如果他们打开另一个窗口,则应用程序图标将出现,并且即使关闭该窗口后,图标也会保留在那里。Windows 7不会这样做,图标会消失。

尝试从主窗口(application.handle)和您显示的那些窗口中删除AppWindow标志。http://msdn.microsoft.com/ru-RU/library/windows/desktop/ff700543.aspx - Arioch 'The
@Arioch 正确设置窗口所有者是首选方法。 - David Heffernan
@David - 我是在 D5 时代学习这些技巧的,当时 Application 的属性要少得多。制作一个空的 MainForm 只是为了隐藏它,这也是一种有趣的方法。 - Arioch 'The
@Arioch,即使在D5中,通过所有权和可见性完全可以实现您所需的功能。这并不是非常困难的事情。 - David Heffernan
@David 好的,那么我可能是来自“在任务栏上创建多个窗口”的一方,把移除看作是相反的问题 :-) - Arioch 'The
2个回答

9

您应该设置

Application.MainFormOnTaskBar := True;

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

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

MainForm.Hide;

当您需要再次显示主表单时,请编写以下代码:

MainForm.Show;

就是这样。

当你展示和隐藏主窗体时,自然会想要同时显示和隐藏通知区域图标。


HideWindowFromTaskbar 中的代码不是必需的,你应该将其删除。当你的应用程序处于 MainFormOnTaskBar 等于 True 模式时,主窗体是一个未拥有的顶级窗口。因此,只要它可见,它就会出现在任务栏上。所以,你可以通过隐藏主窗体来将其从任务栏中移除。

你的应用程序中的其他窗体将是拥有者顶级窗口。通常情况下,它们将由你的主窗体拥有。作为所有者,它们将不会出现在任务栏中。

总的来说,你应该尽量避免调整窗口样式。通常情况下,你可以使应用程序按照你需要的方式运行而无需这样做。更重要的是,如果你必须调整窗口样式,你必须在 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.

在您的评论中,您明确表示希望能够隐藏任务栏窗口而不隐藏主窗体。在这种情况下,我建议您将 MainFormOnTaskbar 设置为 False。这意味着 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.

运行此程序并单击切换按钮。现在您将看到主窗体和其他窗体正在显示。任务栏上没有任何内容。我加入了切换按钮以显示您可以在程序运行时在两种操作模式之间切换。无需重新启动它。
关键是使除可见窗体之外的窗口成为与任务栏相关联的窗口。一旦这样做,您就可以通过显示和隐藏该窗口来再次控制任务栏的存在。在这种情况下,该窗口是应用程序窗口。因为那是任务栏上的窗口,所以您需要设置其 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;

另一种方法是通过TApplicationOnMinimize事件处理程序来抑制应用程序窗口显示。
class procedure TEventHandlerClass.ApplicationMinimize(Sender: TObject);
begin
  ShowWindow(Application.Handle, SW_HIDE);
end;

1
我不想隐藏窗口!我只是想让应用程序从任务栏中消失。 - Paul
然后使其归另一个隐藏窗口所有。也许你可以在问题中明确所需的行为。去掉那些无用的代码,我可以向你展示需要什么。 - David Heffernan
@Arioch'The 实际上,这正是我在第二个代码示例中所做的。 - David Heffernan
@Paul 我能理解你的意思。你打算如何处理这个问题? - David Heffernan
好的,我有一个关于这个问题的解决方案,但我不是100%有信心。这就是它变得棘手的地方,因为VCL现在开始妨碍了。 - David Heffernan
显示剩余5条评论

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,并调用此处引用的其他函数来将应用程序最小化。 如果为true,则允许关闭。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接