运行时切换应用程序主窗体并在任务栏上调用主窗体会导致Windows任务栏闪烁。

7
我将使用Delphi 2010来构建一个运行在Windows XP/Vista和Windows 7上的Win32 GUI应用程序。
基本上,Application.MainForm是只读属性,一旦通过Application.CreateForm创建了第一个窗体,在运行时就无法更改:
begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TForm2, Form2);
  Application.Run;
end.

以上示例将使Form1成为应用程序的主窗体。它将显示在Windows 7任务栏的缩略图预览中。

在运行时设置Application.MainFormOnTaskBar为true,允许我们享受Windows aero主题功能。

我需要在运行时切换应用程序的主窗体。例如,将Form2设置为主窗体。我使用以下代码使其工作:

procedure SetAsMainForm(aForm:TForm);
var
  P:Pointer;
begin
  Application.MainFormOnTaskBar := False;
  try
    P := @Application.Mainform;
    Pointer(P^) := aForm;
    aForm.Show;
  finally
    Application.MainFormOnTaskBar := True;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetAsMainForm(Form2);
end;

点击Button1按钮会将Form2设置为主窗体,并更新Windows任务栏缩略图预览。但是,在切换过程中,任务栏可能会闪烁。

我的问题是:

  1. 有没有办法消除这种闪烁?
  2. 在运行时设置Application.MainformOnTaskBar := False并再次设置为True是否安全?
2个回答

9
主窗体在 Delphi 中是不允许更改的。你找到了一个看起来能够半途而废的方法,但这是一种 hack。请停止这样做。
应用程序只有一个主窗体,它是通过 CreateForm 创建的第一个完成创建的窗体。如果你需要两种截然不同的主窗体行为,则可以尝试以下几种技术:
- 将两个主窗体定义为帧,将所有功能放在其中。然后定义一个单独的窗体,作为其中一个帧的父级。而不是切换主窗体,只需销毁该帧并替换为新的帧即可。 - 与帧解决方案类似,但使用窗体。创建窗体,并将 Parent 属性设置为 "真正的" 主窗体。这可能会有一个较低的初始成本,因为你已经拥有了这两个窗体,但根据我的经验,重新设置窗体的父级比帧更脆弱,后者被设计为子控件,因此应优先选择帧技术。
任务栏上的闪烁来自一个窗体消失和另一个窗体出现。使用上述任何技术,始终只有一个窗体,而不是两个,因此没有什么可以闪烁的。

此外,您还可以创建两个不同的进程并在它们之间交换数据。当然,这会更加昂贵,并且如果您必须共享数据,则可能不是理想的选择。 - Jens Mühlenhoff
那将是防止任务栏闪烁的相反情况,@Jens。任务栏会将来自同一进程的按钮分组,这就是为什么 Chau 的代码似乎会用另一个按钮替换一个按钮 - 它们占据了任务栏上的同一位置。但如果这些按钮属于两个不同的进程,那么任务栏将不会将它们放在一起。你不仅会看到闪烁的按钮,还会看到从一个位置跳到另一个位置的按钮。 - Rob Kennedy
取决于它们是否应该同时可见以及它们是否足够分离以供两个不同的可执行文件使用。问题是为什么Chau需要改变主窗体呢?我猜想最好的做法可能是使用相同可执行文件的两个进程,甚至是两个不同的可执行文件。 - Jens Mühlenhoff
我的应用程序有3-4个表单。用户可以通过按热键或其他方式从一个表单跳转到另一个表单。然而,用户在任何时候只能看到一个表单。关闭任何一个表单都将终止应用程序。这就是我想在运行时切换主表单的原因。我认为主表单切换方法使设计和编码变得简单。 - Chau Chee Yang
然后使用我上面的建议之一,@Chau。使用单个表单的多个视图。一次只能看到一个视图,但由于仍然只有一个顶级表单,操作系统不会对正在发生的事情感到困惑。 - Rob Kennedy

5

考虑的另一个选项是设置 MainFormOnTaskbar=False,然后在进程的整个生命周期中创建一个隐藏的主窗体,并在需要时动态创建并释放 Form1 Form2 辅助窗体,通过重写 TForms.CreateParams() 方法为它们单独创建任务栏按钮,例如:

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
  Params.WndParent := GetDesktopWindow;
end;

1
请参考《The Old New Thing》中的文章,了解为什么将桌面设置为应用程序窗口的父级是错误的做法。文章链接:http://blogs.msdn.com/b/oldnewthing/archive/2004/02/24/79212.aspx。 - Ian Goldby

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