【问题标题】:How to not have a MainForm in Delphi?如何在 Delphi 中没有 MainForm?
【发布时间】:2011-04-28 15:19:38
【问题描述】:

我一直在尝试让我的应用程序中的一些非模式表单出现在任务栏上 - 利用 Windows 7 中新的有用任务栏。

在任务栏上存在表单之前需要撤消 VCL 的许多问题。

但最后一个问题是,最小化 VCL 指定的 主窗体 的窗体会导致应用程序中的所有窗口消失。

Ten years ago, Peter Below (TeamB) documented these problems,并尝试解决它们。但是有些问题是无法解决的。这些问题在 VCL 本身中根深蒂固,实际上不可能使 Delphi 应用程序正常运行。

这一切都源于你在工具栏上看到的按钮并不代表应用程序的窗口;它代表TApplications 窗口,它是隐藏的,从未见过。然后是应用程序的MainForm,它被赋予了特殊的能力,如果被最小化,那么它会指示应用程序隐藏自己。

在我看来,如果我能做到的话

Application.MainForm := nil;

那么所有这些错误都会消失。应用程序可以有它的隐藏窗口,同时我将覆盖应用程序中的每个 other 表单,包括 my 主表单, :

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

但在 Delphi 中,Application.MainForm 属性是只读的。

我怎么能在 Delphi 中没有MainForm

另见

【问题讨论】:

  • 如果只读属性是阻碍您实现目标的唯一问题,您还可以尝试修改 Forms.pas 使其成为可读写属性。

标签: delphi windows-7 taskbar delphi-5


【解决方案1】:

如果没有分配 MainForm,您将无法运行 GUI 项目。主消息循环将立即退出,没有一个。但是,这并不意味着 MainForm 必须运行您的 UI。您可以使用一个空白的隐藏 TForm 作为分配的 MainForm,然后让它将您的真实 MainForm 实例化为辅助 TForm。例如:

HiddenMainFormApp.dpr:

project HiddenMainFormApp;

uses
  ..., Forms, HiddenMainForm;

begin
  Application.Initialize;
  Application.CreateForm(THiddenMainForm, MainForm);
  Application.ShowMainForm := False;
  Application.Run;
end.

HiddenMainForm.cpp:

uses
  ..., RealMainForm;

procedure THiddenMainForm.FormCreate(Sender: TObject);
begin
  RealMainForm := TRealMainForm.Create(Self);
  RealMainForm.Show;
end;

RealMainForm.cpp:

procedure TRealMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
  Application.Terminate;
end;

或者:

HiddenMainFormApp.dpr:

project HiddenMainFormApp;

uses
  ..., Forms, HiddenMainForm, RealMainForm;

begin
  Application.Initialize;
  Application.CreateForm(THiddenMainForm, MainForm);
  Application.ShowMainForm := False;

  RealMainForm := TRealMainForm.Create(Application);
  RealMainForm.Show;
  RealMainForm.Update;

  Application.Run;
end.

RealMainForm.cpp:

procedure TRealMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
  Application.Terminate;
end;

【讨论】:

  • 何时以及如何触发 real MainForm 的创建?您将谁设置为表单的所有者? Application? BlankHiddenForm? nil?您如何显示新的 MainForm 以及何时显示?你什么时候打电话给BlankHiddenForm.Hide?我不能在OnShow 期间隐藏我的虚拟表格(这是不允许的)。以及如何将注意力集中在我的辅助 MainForm 上?
  • 随时创建。分配您想要的任何所有者。随心所欲地展示它。这些都不重要,因为您的代码正在管理该表单,因此您可以做任何适合您的事情。最简单的做法是自动创建它(这会将应用程序设置为所有者),然后在准备好时显示()它。至于隐藏分配的MainForm,只需在调用TApplication.Run()之前将TApplication.ShowMainForm属性设置为False即可,不要尝试手动Hide()
  • 一个人设法在这里回答了这个问题stackoverflow.com/questions/6228966/… 这实际上非常棘手而且一点也不明显。我应该在 OnCreate 期间创建我的表单吗? 演出开启激活?我应该启动 0ms 计时器吗?我是否在 OnCreate 期间启动它? 演出开启激活?真正的表单应该创建为 Application.CreateForm 吗? .创建(应用程序).Create(Self)? .Create(nil)?我打电话给 .Show 吗?我什么时候释放它?我打电话给 .ShowModal; .免费?没有新奇的问题。
  • 最重要的是,现在我的主窗体是不可见的:用户如何关闭应用程序?
  • sigh 显然我必须为你写一些代码。我已编辑我的回复以包含代码示例。
【解决方案2】:

你不能,尤其是在 Delphi 5 中。

您关于 TApplication 窗口是在任务栏上看到的那个的引述对于现在的几个 Delphi 版本来说都是不正确的(我相信 D2007 改变了它)。

因为您使用的是 Delphi 5,所以您使用的是过时的 Delphi 副本;当前版本几乎不再包含您正在写的任何内容。我建议您升级到更高版本的 Delphi(D5 非常旧);如果您需要避免使用 Unicode,请使用 Delphi 2007,如果您可以在 VCL 和 RTL 中使用(或不介意拥有)Unicode 支持,请使用 Delphi XE。

你所描述的东西不是 错误,顺便说一句。它们是在设计 Delphi 1 时有意做出的设计决策,并且通过 Delphi 7 可以很好地与可用的 Windows 版本配合使用。 Windows 更高版本(XP/Vista/Win7 和等效的服务器版本)中的更改使该体系结构发生了必要的更改,并且随着 Delphi 与 Windows 一起进步而进行了更改。因为您选择不继续使用您的 Delphi 版本以使其保持更新,所以您所写的内容不会神奇地变成错误。 :-)

【讨论】:

  • 我很想每一两年花几千美元——但这不会发生。我们大多切换到免费的 Visual Studio。我希望 Borland 发布 Delphi 的免费版本。但是接受的答案你不能,因为它是一个有效的(我假设是正确的)答案。
  • Ian:如果你升级了(D5天),每年就不会几千美元了; Pro 版升级成本大约是 300 美元。当前的 Pro 版本已经涨到了 400 美元左右,但是软件保障(大约相同的价格)可以让你一次免费升级 12 个月。如果 Embarcadero 有来自其他产品线的数十亿收入来支持它,那么免费版本会很棒。因为他们不是 MS,所以他们没有。
  • 在新的 delphi 版本中转换和测试应用程序也有非零成本(例如,使用 TThread 和同步的更改、新组件、dsgnintf.pas/dsgnintf.dcu/DesignInterface/DesignerInterfaces/DesigntimeInterfaces , 类型/变体, 字符串/AnsiString/WideString/UnicodeString)
  • 转换和测试的非零成本远低于将整个开发从一种语言/IDE(Delphi)切换到全新的语言(VS/无论您使用哪种语言)的非零成本选择)。 :-)
  • 显然我们没有切换所有内容(仅限新项目),因为我仍在询问 Delphi 5。
【解决方案3】:

分配 Application.MainForm 似乎不是问题,因为在任务栏上显示另一个无模式表单同时最小化 MainForm。

Project1.dpr:

program Project1;

uses
  Forms,
  Windows,
  Unit1 in 'Unit1.pas' {MainForm},
  Unit2 in 'Unit2.pas' {Form2};

{$R *.res}

var
  MainForm: TMainForm;

begin
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  ShowWindow(Application.Handle, SW_HIDE);
  Application.Run;
end.

Unit1.pas:

unit Unit1;

interface

uses
  Windows, Messages, Classes, Controls, Forms, StdCtrls, Unit2;

type
  TMainForm = class(TForm)
    ShowForm2Button: TButton;
    ShowForm2ModalButton: TButton;
    procedure ShowForm2ButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ShowForm2ModalButtonClick(Sender: TObject);
  private
    FForm2: TForm2;
    procedure ApplicationActivate(Sender: TObject);
    procedure Form2Close(Sender: TObject; var Action: TCloseAction);
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  end;

implementation

{$R *.dfm}

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Visible := True; //Required only for MainForm, can be set designtime
  Application.OnActivate := ApplicationActivate;
end;

procedure TMainForm.ApplicationActivate(Sender: TObject);
{ Necessary in case of any modal windows dialog or modal Form active }
var
  TopWindow: HWND;
  I: Integer;
begin
  TopWindow := 0;
  for I := 0 to Screen.FormCount - 1 do
  begin
    Screen.Forms[I].BringToFront;
    if fsModal in Screen.Forms[I].FormState then
      TopWindow := Screen.Forms[I].Handle;
  end;
  Application.RestoreTopMosts;
  if TopWindow = 0 then
    Application.BringToFront
  else
    SetForegroundWindow(TopWindow);
end;

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

procedure TMainForm.WMSysCommand(var Msg: TWMSysCommand);
begin
  if Msg.CmdType = SC_MINIMIZE then
    ShowWindow(Handle, SW_MINIMIZE)
  else
    inherited;
end;

{ Testing code from here }

procedure TMainForm.ShowForm2ButtonClick(Sender: TObject);
begin
  if FForm2 = nil then
  begin
    FForm2 := TForm2.Create(Application); //Or: AOwner = nil, or Self
    FForm2.OnClose := Form2Close;
  end;
  ShowWindow(FForm2.Handle, SW_RESTORE);
  FForm2.BringToFront;
end;

procedure TMainForm.Form2Close(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
  FForm2 := nil;
end;

procedure TMainForm.ShowForm2ModalButtonClick(Sender: TObject);
begin
  with TForm2.Create(nil) do
    try
      ShowModal;
    finally
      Free;
    end;
end;

end.

Unit2.pas:

unit Unit2;

interface

uses
  Windows, Messages, Classes, Controls, Forms;

type
  TForm2 = class(TForm)
  private
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  end;

implementation

{$R *.dfm}

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

procedure TForm2.WMSysCommand(var Msg: TWMSysCommand);
begin
  if Msg.CmdType = SC_MINIMIZE then
    ShowWindow(Handle, SW_MINIMIZE)
  else
    inherited;
end;

end.

(在 XP 和 Win7 上使用 D5 和 D7 测试。)

(是的,您可以将其标记为不是答案,因为它不是:仍然存在 MainForm。但我认为这回答了问题背后的问题......)

【讨论】:

    【解决方案4】:

    我不能代表 Delphi 5,但在 Delphi 7 中,如果您愿意亲自动手,您绝对可以在没有主窗体的情况下运行。我在另一个答案here 中介绍了很多细节。

    由于 Delphi 5 没有 MainFormOnTaskbar 属性,您需要在 dpr 中执行以下操作:

    // Hide application's taskbar entry
    WasVisible := IsWindowVisible(Application.Handle);
    if WasVisible then
      ShowWindow(Application.Handle, SW_HIDE);
    SetWindowLong(Application.Handle, GWL_EXSTYLE,
      GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
    if WasVisible then
      ShowWindow(Application.Handle, SW_SHOW);
    // Hide the hidden app window window from the Task Manager's
    // "Applications" tab.  Don't change Application.Title since
    // it might get read elsewhere.
    SetWindowText(Application.Handle, '');
    

    这将隐藏应用程序窗口,并且只要您覆盖表单的 CreateParams 以设置Params.WndParent := 0,它们每个人都会有自己的任务栏条目。 Application.MainForm 没有被分配,所以像最小化覆盖这样的事情不是问题,但是你必须小心任何假设 MainForm 有效的代码。

    【讨论】:

      【解决方案5】:

      您可以将您的无模式表单放入 dll 中,然后它们几乎可以自行运行。 (如果您在创建它们时不使用 dll 的 Application 实例 (Application.CreateForm),则 Application.Mainform 在 dll 中为零)。

      当然,这可能不可行,具体取决于表单可能需要做什么。

      【讨论】:

      • 从 dll 中暴露一个类会带来可怕的危险 - 您不能添加或删除任何私有成员或方法。
      • 我不太明白你所说的添加私有成员是什么意思,但是这种设计当然会施加限制。正如我在上一段中所说,这可能是不可行的。顺便说一句,我有一些旧项目 (D3),可执行文件中只有 one 行代码:ShowFormInDll;。然后,对于该项目,“Application.MainForm”实际上是 nil。
      • 这是在 DLL 中拥有类的危险。任何对从 dll 公开的对象调用方法的人都必须知道 dll 中的表单声明。这样当它调用一个方法时,它会调用虚拟方法表 (VMT) 中的正确偏移量。如果您的表单添加了一个私有成员(例如一个整数,它是四个字节),那么任何调用该表单的人现在都会崩溃,因为他们在查看他们认为是 VMT 时会减少四个字节。应用程序和 dll 必须始终一起编译。如果不这样做,您将遇到崩溃。这些问题就是 COM 被发明的原因。
      • @Ian - 好的,然后将您的表单放入 COM dll 中。 说真的,我从没想过直接在应用程序中对 dll 对象进行操作,谢谢您的解释。
      • 呃,现在每次发布版本时都必须注册 COM 类? Delphi 的单一可执行文件是一个巨大的 功能 - 不幸的是,微软没有分享。
      【解决方案6】:

      实际上,您所抱怨的大部分实际上是 Windows 的设计,而不是 VCL。详情请见Windows Features

      问题的症结在于owner属性,我指的是windows的拥有者而不是VCL的拥有者。

      拥有的窗口在以下情况下被隐藏 它的所有者被最小化了。

      如果您希望能够在不隐藏其他窗口的情况下最小化主窗体,那么您需要了解拥有的窗口是如何工作的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-19
        • 2012-01-14
        • 1970-01-01
        相关资源
        最近更新 更多