国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Delphi > 正文

Delphi中正常窗口的實現

2019-11-18 18:05:08
字體:
來源:轉載
供稿:網友
 

Delphi中正常窗口的實現

摘要 在Delphi的VCL庫中,為了使用以及實現的方便,應用對象application創建了一個用來處理消息響應的隱藏窗口。而正是這個窗口,使得用VCL開發出來的程序存在著與其他窗口不能正常排列平鋪等顯得有些畸形的問題。本文通過對VCL的深入分析,給出了一個只需要對應用程序項目文件作3行代碼的修改就能解決問題的方案,且不需要原有的編程方式作任何改變。

關鍵字 VCL,正常窗口,正常化

1 引言
用Delphi所提供的VCL類庫編寫的Windows應用程序,有一個明顯不同于標準Windows窗口的特點--主窗口的系統菜單與任務欄上的系統菜單不相同。一般情況下,主窗口的系統菜單有六個菜單項而任務欄系統菜單只有三個菜單項。實際使用中我們發現用VCL開發的程序有以下幾個方面的尷尬:

1)不夠美觀。這是肯定的,與標準不符自然會顯得有些畸形。
2)主窗口最小化時沒有動畫效果。
3)窗口不能正常與其它窗口排列平鋪。
4)任務欄系統菜單具有最高的優先級。在存在模態窗口的情況下整個程序仍然可以被最小化,與模態窗口的設計相違背。

主窗口最小化動畫效果的問題在Delphi 5.0以后的版本中已通過Forms.pas中的ShowWinNoAnimate函數解決,但其余幾個問題則一直存在。盡管多數情況下這不會對應用程序帶來什么影響,但在一些追求專業效果的場合確實不可接受的。由于C++ Builder與Delphi使用的是同一套類庫,所以上述問題同樣存在于使用C++ Builder編寫的Windows應用程序中。
在以前的文章里(阿甘的家中可以找到),我已討論過這個問題,當時的敘述看起來基本上是一種取巧的方法,而我也是在偶然之中才找到那個方法的。本文的任務就是通過對VCL類庫作一些分析,說明那樣做的原理,其次再給出一個只用3行代碼的方法,完完全全地解決Delphi中這個"非正常窗口"的問題。

2 原理
2.1 應用程序的創建過程
下面是一個典型的應用程序的Delphi工程文件,我們注意到一開始就有一個對Application對象的Initialize方法的引用,我們的分析也就從這里開始:

PRogram Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

隱藏的窗口是由Application對象創建的,那么Application對象又從何而來呢?在Delphi的代碼編輯窗口中按住Ctrl點擊Application就會發現,Application對象是在Forms.pas單元中定義的幾個全局對象之一。這還不夠,我們想要知道的是Application對象是在什么地方創建的,因為必須成功創建了TApplication類的實例我們才能引用它。
想一下,有什么代碼會在Application.Initialize之前執行呢?對了,是initialization代碼段中的代碼。認真調試過VCL

Unit Controls;

initialization
    ...
    InitControls;

procedure InitControls;
begin
...
  Mouse := TMouse.Create;
  Screen := TScreen.Create(nil);
  Application := TApplication.Create(nil);
...
end;

好,到這里我們的分析就完成了第一步,因為要解決非正常窗口的問題,我們必須要在Application對象初始化之前做一件事,因此了解應用程序的初始化過程就非常重要了。

2.2 IsLibrary變量
IsLibrary變量是在System.pas單元中定義的全局標志變量之一。如果IsLibrary的值為true則表明程序模塊是一個動態鏈接庫,反之就是一個可執行程序。VCL類庫中的某些過程就根據這個標志變量的不同值完成不同的動作。也就是這個變量,在解決Delphi的非正常窗口問題中起到了關鍵性的作用。
前面說過,為了方便,Application對象初始化時創建了一個看不見的窗口(也就是用Spy++之類的工具看到的那個以"TApplication"為類名的窗口),但也正是因為這個看不見的窗口,才使得用Delphi開發出來的程序呈現諸多畸形。好了,如果我們能夠去掉這個看不見的窗口(同時去掉任務欄系統菜單),代之以我們的應用程序主窗口,豈不是所有的問題都解決了?
說說簡單,但實現起來需要對VCL源代碼動大手術嗎?如果那樣豈不是有點本末倒置了?答案當然是不會,否則也不會有這篇文章了。在此我想說的是,在接下來的分析中,我們將會看到,所謂"編程之道,存乎一心",TApplication設計中無心插柳的做法,實則為我們解決這一問題留下了接口。不做源代碼的分析,你可能要繞打圈子,而實際上我們會看到,天才的設計留給我們用的東西,不多也不少,剛剛好。
打開TApplication類的構造函數Create,我們會發現這樣一行代碼。

constructor TApplication.Create(AOwner: TComponent);
begin
    ...
    if not IsLibrary then CreateHandle;
    ...
end;

這里說的是,如果程序模塊不是動態鏈接庫,那么就執行CreateHandle,而CreateHandle所做的工作在幫助中是這樣說的:"如果不存在應用程序窗口,那就創建一個",這里的"應用程序窗口"就是上面所說的看不見的窗口,也即是罪魁禍首之所在,在TApplication類中用FHandle變量來保存其窗口句柄。這里就是根據IsLibrary的值完成了不同的動作,因為在動態鏈接庫中一般并不需要消息循環的,但用VCL開發動態鏈接庫還是要用到Application對象,所以有了這里的設計。好,我們只需要欺騙一下Application對象,在它創建之前把IsLibrary賦值為true,即可濾掉CreateHandle的執行,去掉這個討厭的窗口了。
為IsLibrary賦值的代碼顯然也應該放在某個單元的initialization代碼段中,而且由于initialization代碼段中的代碼是按照包含的單元的順序執行的,為了保證在Application對象創建之前把IsLibrary賦值為true,在工程文件中我們必需將包含賦值代碼的單元放在Forms單元之前,如下(假設該單元名為UnitDllExe.pas):

program Template;

uses
  UnitDllExe in 'UnitDllExe.pas',
  Forms,
  FormMain in 'FormMain.pas' {MainForm},
  ...

UnitDllExe.pas代碼清單如下:

unit UnitDllExe;

interface

implementation

initialization
  IsLibrary := true;
  //告訴Applciation對象,這是一個動態鏈接庫,不需要創建隱藏窗口。
end.

好了,編譯運行一下,我們看到,由于沒有創建隱藏窗口,原先任務欄上的系統菜單消失了,換成了主窗口的系統菜單,主窗口也能夠與其它Windows窗口正常排列平鋪。但帶來的問題是窗口無法最小化。怎么回事呢?還是老方法,跟蹤一下。

2.3 主窗口最小化
最小化屬于系統命令,最終必定是調用API函數DefWindowProc來將窗口最小化,所以我們毫無困難地就找到了TCustomForm中響應WM_SYSCOMMAND消息的函數WMSysCommand,其中清楚地寫到將最小化的消息重定向到Application.WndProc去處理:

procedure TCustomForm.WMSysCommand(var Message: TWMSysCommand);
begin
  with Message do
  begin
    if (CmdType and $FFF0 = SC_MINIMIZE) and (Application.MainForm = Self) then
      Application.WndProc(TMessage(Message))
  ...
  end;
end;

而在Application.WndProc中,響應最小化消息時又調用了Application的Minimize方法,所以癥結一定是在Minimize過程。

procedure TApplication.WndProc(var Message: TMessage);
  ...
begin
  ...
    with Message do
      case Msg of
        WM_SYSCOMMAND:
          case WParam and $FFF0 of
            SC_MINIMIZE: Minimize;
            SC_RESTORE: Restore;
          else
            Default;
  ...
end;

最后,找到TApplication.Minimize,就一切都明白了。這里對于DefWindowProc函數的調用沒有產生任何效果,為什么呢?由于前面我們欺騙Application對象,濾掉了CreateHandle的調用,沒有創建Application對象響應消息所需要的窗口,因此導致其句柄FHandle為0,調用當然不成功了。如果能將FHandle指向我們的應用程序主窗口就能解決問題。

procedure TApplication.Minimize;
begin
  ...
      DefWindowProc(FHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
      //這里FHandle值為0
  ...
end;

3 實現
Borland的天才們無心插柳的設計再一次讓我們找到了解決問題的辦法。由前面的分析我們知道,在用VCL開發的動態鏈接庫中并沒有創建隱藏的窗口來接收Windows消息(CreateHandle不執行),但在動態鏈接庫中如果要顯示窗口的話又需要一個父窗口。如何解決這個問題呢?VCL的設計者將保存看不見的窗口句柄的FHandle變量設計為可寫,于是我們實際上可以簡單地給FHandle賦一個值來為需要顯示的子窗口提供一個父窗口。例如,在某個動態鏈接庫插件中要顯示窗體,我們通常會在主模塊可執行文件中將Application對象的句柄通過動態鏈接庫的某個函數傳入并賦值給動態鏈接庫的Application.Handle,類似于:

procedure SetApplicationHandle(MainAppWnd: HWND)
begin
  Application.Handle := MainAppWnd;
end;

好了,既然Aplication.Handle實際上只是一個在內部用來響應消息的窗口句柄,而原本應該創建的看不見的窗口被我們去掉了,那我們只需要給出一個窗口的句柄,用來代替那個原本多余的隱藏窗口的句柄不就行了?這樣的窗口去哪里找?應用程序的主窗口正是上上之選,于是有了下面的代碼。

program Template;

uses
  UnitDllExe in 'UnitDllExe.pas',
  Forms,
  FormMain in 'FormMain.pas' {MainForm};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TFormMain, FormMain);
  Application.Handle := FormMain.Handle;
  Application.Run;
end.

于是,一切問題都解決了。你不需要對VCL源碼作任何修改,不需要對原有的程序作任何修改,只要在工程文件中增加兩行代碼,加上UnitDllExe.pas中的一行,共三行代碼,即可使得你的應用程序窗口完全和任何一個標準Windows窗口一樣正常。

1)任務欄和窗口標題欄擁有一致的系統菜單。
2)主窗口最小化時有動畫效果。
3)窗口能夠正常與其它窗口排列平鋪。
4)存在模態窗口時不能對其父窗口進行操作。

以上實現代碼使用于Delphi的所有版本。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 邢台县| 太白县| 城市| 会理县| 镶黄旗| 武邑县| 东方市| 台南市| 平陆县| 肃宁县| 扎兰屯市| 古田县| 田林县| 夹江县| 凌源市| 靖宇县| 岢岚县| 元谋县| 田阳县| 房山区| 郁南县| 古田县| 武穴市| 特克斯县| 三门峡市| 绥芬河市| 津南区| 铜山县| 南京市| 尉氏县| 平乡县| 介休市| 南和县| 龙川县| 定远县| 合作市| 赤城县| 石楼县| 土默特左旗| 南郑县| 镇赉县|