Delphi在应用程序运行时更改主窗体

时间:2014-09-04 13:20:53

标签: forms delphi vcl taskbar delphi-xe6

我有这个问题。当我隐藏我的主窗体时,我的应用程序的任务栏图标也被隐藏。我也看到了一个关于这个问题的新问题,答案并没有真正帮助。他们建议尽量减少它,但我不想最小化应用程序。

是否可以在应用程序运行时更改主窗体?

例如

。我有两种形式。当我想隐藏一个表单并显示另一个表单时,任务栏图标应该保留在任务栏上,主表单应该切换到另一个表单。

我正在使用Delphi XE6,它是一个VCL Forms应用程序。

我还看到了一个关于在运行时更改主窗体的另一个老问题,但它已经很老了,仍然基于Delphi 6。

9 个答案:

答案 0 :(得分:6)

  

是否可以在应用程序运行时更改主窗体?

在程序运行时无法更改VCL主窗体。程序启动时,该属性一劳永逸地确定。

一种可行的方法是安排辅助表单(非主表单的表单)在任务栏上有一个按钮。通过使其无主,或使用WS_EX_APPWINDOW扩展窗口样式来实现。

<强>更新

好吧,您可以更改Application.MainForm,但它要求您销毁当前的主窗体,然后创建一个新窗体。

答案 1 :(得分:3)

正如David Heffernan已经说过无法更改已经运行的应用程序的主窗体。这是Windows本身的限制。

你可以做的就是作弊而且从不真正改变主表格,只是让它看起来像你做的那样 你是如何实现这一目标的?

第1步:将代码添加到第二个表单以创建自己的任务栏按钮

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

第2步:在切换到第二个表单之前动态创建第二个表单。创建之后,添加的代码将为您的第二个表单创建一个新的任务栏按钮。

第3步:现在隐藏您的实际主表单。隐藏它也会隐藏属于它的任务栏按钮。因此,您最终仍然显示一个任务栏按钮,它是属于您的第二个表单的那个。

第4步:为了让您的第二个表单在其关闭时终止您的应用程序,请从第二个Forms OnClose或OnFormCloseQuery事件中关闭您的真正主表单的方法。
如果您希望能够切换回真正的主窗体,请调用主窗体的显示方法而不是关闭方法。

这种方法允许我们快速交换表单,因此只有大多数敏锐的用户会注意到任务栏按钮的简短动画。
注意:如果你的第二个是复杂的,因为这需要一些时间来创建你可能想隐藏它创建它然后一旦它的创建过程完成显示它并进行交换。否则你最终可能会同时显示两个任务栏按钮,我相信你想避免使用它们。

这是一个简短的例子:
- LoginForm是在应用程序启动时创建的真正主窗体 - WorkForm是用户在登录后将花费大部分时间的表单,并且这是在登录过程中创建的

登录表格代码:

unit ULoginForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TLoginForm = class(TForm)
    BLogIn: TButton;
    procedure BLogInClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  LoginForm: TLoginForm;

  //Global variable to tell us if we are only logging out or closing our program
  LoggingOut: Boolean;

implementation

uses Unit2;

{$R *.dfm}

procedure TLoginForm.BLogInClick(Sender: TObject);
begin
  //Create second Form
  Application.CreateForm(TWorkForm, WorkForm);
  //Hide Main Form
  Self.Hide;
  //Don't forget to clear login fields
end;

end.

工作表格代码:

unit UWorkForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TWorkForm = class(TForm)
    BLogOut: TButton;
    //Used in overriding forms creating parameters so we can add its own Taskbar button
    procedure CreateParams(var Params: TCreateParams); override;
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure BLogOutClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  WorkForm: TWorkForm;

implementation

uses Unit1;

{$R *.dfm}

procedure TWorkForm.BLogOutClick(Sender: TObject);
begin
  //Set to true so we know we are in the process of simply logging out
  LoggingOut := True;
  //Call close method to begin closing the current Form
  Close;
end;

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

procedure TWorkForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  //Check to see if we are in the process of simply logging out
  if not LoggingOut then
  begin
    //If we are not in the process of logging out close the Main Form
    LoginForm.Close;
    //and then allow closing of current form
    CanClose := True;
  end
  else
  begin
    //But if we are in the process of simply logging out show the Main Form
    LoginForm.Show;
    //Reset the LoggingOut to false
    LoggingOut := False;
    //and then alow closing of current form
    CanClose := True;
  end;
end;

end.

答案 2 :(得分:3)

一旦分配Application.MainForm,就无法更改TForm。但是,您也不需要。解决此问题的最简单方法是创建一个空白隐藏Application.MainForm以充当真实 TForm并让它正常管理任务栏,然后您可以显示/隐藏任何辅助MainForm对象,在需要的地方,你想要的&#34; MainForm&#34;是次要形式而不是真实的{{1}}。

答案 3 :(得分:3)

我以同样的方式实现了这个@DavidHeffernan已经建议并且还没有遇到任何问题,它可能不是最好的方式,但它对我和我想要实现的目标有效,这有&# 34;正常&#34;当用户最小化其MainWork表单时的感觉行为。

代码与此类似:

procedure TfrmLogin.btnLoginClick(Sender: TObject);
begin
    frmMainWork := TfrmMain.Create(Application);
    try
        Pointer((@Application.MainForm)^) := frmMainWork;
        frmLogin.Hide;
        frmMainWork.ShowModal;
    finally
        Pointer((@Application.MainForm)^) := frmLogin;
        frmLogin.Show;
        FreeAndNil(frmMainWork);
    end;

end;

希望这会有所帮助: - )

答案 4 :(得分:2)

如果在启动例程中将Application.MainFormOnTaskbar设置为false(在.dpr文件中),则VCL将创建一个隐藏表单,其唯一目的是提供任务栏图标。这是一种较旧的方法,通常不推荐使用,但只要其他窗口可见,它就会让您隐藏主窗体而不会使应用程序从任务栏中消失。

您还可以提供Application.OnGetMainFormHandle事件处理程序,以便在运行时更改Application.MainFormHandle(但 Application.MainForm)。 MainFormHandle会影响模态弹出对话框的所有者。

有关Application.MainFormOnTaskbar的更多信息以及禁用它的缺点:这很复杂。简短版本位于VCL docs,这解释了Vista中引入的几个Windows新功能(如实时任务栏缩略图)需要MainFormOnTaskbar := True。在this SO discussion中有更多的背景阅读。

答案 5 :(得分:1)

您可以更改主表单。 做一个变量F:^ TForm,然后将它设置为@ Application.MainForm。之后,您可以通过F ^:= YourAnotherForm设置主窗体。

答案 6 :(得分:0)

我还有一个额外的问题是使用也是COM服务器的Delphi XE2 MDI应用程序。我的主要表单Main调用辅助表单MenuForm,其中包含整个应用程序的共享图标和弹出菜单。

在我的示例中,该应用程序在独立运行时运行良好。

创建Main,然后在FormCreate的末尾创建一个MenuForm。 一切都好。

但是当从COM服务器调用时,首先调用Main.FormCreate,但不知何故,MenuForm被分配给Application.MainForm。基础RTL中存在竞争条件。这在尝试创建第一个SDI子项时会造成严重破坏,因为Application.MainForm不是MDI。

我尝试通过Main.FormCreate解决此问题,将消息发回给自己,以便在Main.FormCreate返回之后延迟创建MenuForm。

但是这里仍然存在竞争条件 - 仍然将MenuForm分配给Application.MainForm。

我最终能够使用代码来解决这个问题,每隔10毫秒轮询一次Application.MainForm,最多10秒。我还必须删除Main中的任何引用到MenuForm的图标列表(在.dfm中),直到明确创建MenuForm之后 - 否则将在MainForm.create的末尾隐式创建MenuForm。

希望这有助于某人!

const
  CM_INITIAL_EVENT = WM_APP + 400;


TmainForm = class(TForm)
  ...
  procedure afterCreate(var Message: TMessage); message CM_INITIAL_EVENT;
  ...
end;


procedure TmainForm.FormCreate(Sender : TObject);
begin
  ...
  ...standard init code
  ...

  postmessage( handle, CM_INITIAL_EVENT, 0, 0 );
End;


procedure TmainForm.AfterCreate(var Message: TMessage);
var
  i: Integer;
begin

  //must assign these AFTER menuform has been created
  if menuForm = nil then
  begin
    //wait for mainform to get assigned
    //wait up to 10*1000ms = 10 seconds
    for i := 0 to 1000 do
    begin
      if Application.Mainform = self then break;
      sleep(10);
    end;

    Application.CreateForm(TmenuForm, menuForm);
    menuForm.parent := self;
  end;

  //NOW we can assign the icons
  Linktothisfilterfile1.SubMenuImages := menuForm.treeIconList;
  ActionManager.Images := menuForm.treeIconList;
  allFilters.Images := menuForm.treeIconList;
  MainMenu.Images := menuForm.treeIconList;
  ...
end;

答案 7 :(得分:0)

在当前的Delphi实现中,我确信使用指针更改Application.MainForm没有任何后果。

如果查看TApplication类,可以看到FMainForm仅用于检查应用程序是否至少有一个表单,并在FMainForm存在时迭代TApplication.Run方法内的主循环。如果您不想使用指针来破解此属性,您可以实现自己的TApplication类,如TMyApplication,复制其中的所有例程并定义MainForm属性以进行读写

答案 8 :(得分:0)

删除 *.dproj 文件。删除 *.dpr 文件中的以下行。你摆脱了所有的烦恼:) Application.MainFormOnTaskbar := True; 抱歉我的英语不好。