Delphi,可以使表单仅对特定父表单进行模式化吗?

问题描述 投票:0回答:6

我有一个应用程序,其中有一个主后台表单,从那里用户只能使用维护系统不同部分的非模式表单。非模态表单会覆盖 CreateParams 方法,因此每个表单都会在启动任务栏中显示一个按钮:

procedure TfmMaterialsPlanning.CreateParams(var Params: TCreateParams);
begin
   inherited;
   //create a new window on the task bar when this form is created
   Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;

实际上,用户可以打开一个维护“Apples”的非模态表单,另一个维护“Oranges”的非模态表单,并使用开始菜单栏在两者之间轻松切换。

但是,如果他们从“Apples”表单打开模态表单,例如设置选项、首选项等,那么他们在关闭模式表单之前无法使用“Oranges”表单。

是否可以使模态表单仅对父表单进行模态?那么如果他们打开苹果的选项表,他们就不能使用苹果的维护表,但仍然可以使用橙子的维护表?

delphi forms modal-dialog
6个回答
7
投票

如果您查看 TCustomForm.ShowModal() 的源代码,您会发现 VCL 不使用 Windows API 调用来显示模式对话框,但它会在模式窗体显示时禁用应用程序中的所有其他窗体显示。您当然可以尝试相同的方法,只需 Show() 表单模式对话框,然后禁用父级,然后在关闭表单模式对话框后重新启用它。需要有一个中心位置来跟踪表单模式对话框、需要重新启用的表单等。然而,您应该彻底测试代码是否确实按照您想要的方式执行,即使在应用程序之间来回切换、最小化应用程序等时也是如此。

话虽如此,我认为这根本不是一个好主意。它打破了 Windows 用户对应用程序行为的所有假设。与 Mac OS X 不同,Windows 中的应用程序模式对话框和表单模式对话框之间没有区别,您应该坚持与您正在编程的平台一致的行为。

很可能有更好的方法来构建用户界面。查看“Windows 用户体验交互指南”中对话框的相关页面。最好尽可能避免模态对话框,链接的指南显示了许多用例的更好替代方案。如果您限制模式对话框的使用,也许您不再需要表单模式对话框。


3
投票

这篇文章有一个很好的技巧来满足您的需求:http://blogs.teamb.com/deepakshenoy/2006/08/21/26864

总结就是当模态窗口禁用了你想要的非模态窗口时,重新启用它。


0
投票

通过阻止“Apples”窗体在其子窗体打开时接受焦点,难道不能达到相同的效果吗?


0
投票

顺便说一句(尽管这会是一项巨大的工作量),解决这个问题的另一种方法是 Google 的 chrome 的方式,其中每个“选项卡”都是一个单独的进程,但对用户来说似乎是一个单独的集成应用程序。

即使这种方法可以实现你想要的,我也必须同意上面的评论,即这会打破用户对模态行为的假设和期望。


0
投票

TeamB 网站早在 2006 年就有 Deepak Shenoy 发布的解决方案,但该网站已关闭。这是该页面的副本:


是的。浏览Forms.pas中的Delphi源代码,在TForm.ShowModal()中调用“DisableThreadWindows”以确保所有非模态窗体处于非活动状态,并且当模态窗体关闭时调用“EnableThreadWindows”。它们的作用只是禁用所有其他窗口,然后重新启用它们。

你能做的是:

  1. 处理发送到表单的 WM_ENABLE 消息以设置启用状态。
  2. 使用“PostMessage”将自定义消息(例如 WM_REENABLE)发布回您的表单。
  3. 处理 WM_REENABLE 自定义消息并在处理程序中启用您的窗口。

因此,在您的 Delphi 表单中创建一个如下所示的表单:

type
 TForm2 = class(TForm)
…
   procedure WMEnable(var Message: TWMEnable); message WM_ENABLE;
…

procedure TForm2.WMEnable(var Message: TWMEnable);
begin
 if not Message.Enabled then
 begin
   PostMessage(Self.Handle, WM_REENABLE, 1, 0);
 end;
end;

其中 WM_REENABLE 先前定义为:

const
 WM_REENABLE = WM_USER + $100;

然后添加一个 WM_REENABLE 处理程序,如下所示:

type
 TForm2 = class( TForm )
…
   procedure WMReenable(var Message: TMessage); message WM_REENABLE;
…
procedure TForm2.WMReenable(var Message: TMessage);
begin
  EnableWindow(Self.Handle, True);
end;

现在,即使显示模式窗口,您的表单也将处于活动状态。

注意:我已尝试以下方法,但不起作用:

  1. 在 WM_ENABLE 处理程序中调用 EnableWindow()。
  2. 在 WM_ENABLE 处理程序中发布 WM_ENABLE 消息。
  3. 尝试将我的窗口的窗口样式更改为保持在顶部。

注意:不要调用“SendMessage”而不是 PostMessage,它不会一致工作。


-1
投票

如果您在自己的线程中创建每个非模态表单,这是可能的。每个模态形式都会阻塞它所属的线程。

编辑:这应该是可能的,即使 vcl 不是线程安全的。请看一下 Alexeys 的解释,了解如何做到这一点:

因此,如果您有一组应该存在于单独线程中的表单,然后将它们放入一个 dll 中,请在不使用包的情况下编译它并使用!它会工作并且是线程安全的。

© www.soinside.com 2019 - 2024. All rights reserved.