Implementation of normal windows in Delphi
Summary In Delphi's VCL library, for the convenience of use and implementation, the application object application creates a hidden window for processing message responses. It is this window that makes the programs developed with VCL appear to be somewhat deformed, such as not being able to arrange and tile normally with other windows. Through in-depth analysis of VCL, this article provides a solution that can solve the problem by only modifying 3 lines of code to the application project file, without any changes to the original programming method.
Keyword VCL, normal window, normalization
1 Introduction
Windows applications written in the VCL class library provided by Delphi have a distinct feature that is obviously different from the standard Windows window - the system menu of the main window is different from the system menu on the taskbar. Generally speaking, the system menu in the main window has six menu items, while the taskbar system menu has only three menu items. In actual use, we found that programs developed with VCL have the following embarrassment:
1) Not beautiful enough. This is certain, if it does not match the standards, it will naturally appear a bit deformed.
2) There is no animation effect when the main window is minimized.
3) The window cannot be arranged and tile normally with other windows.
4) The taskbar system menu has the highest priority. In the presence of a modal window, the entire program can still be minimized, contrary to the design of the modal window.
The problem of minimizing animation effects in the main window has been solved by the ShowWinNoAnimate function in Forms.pas in versions after Delphi 5.0, but the remaining problems have always existed. Although this will not have any impact on the application in most cases, it is indeed unacceptable in some situations where professional effects are pursued. Since C++ Builder and Delphi use the same set of class libraries, the above problems also exist in Windows applications written using C++ Builder.
I have discussed this issue in previous articles (can be found at Forrest Gump's home), and the narrative at that time seemed basically a trick, and I found that method by chance. The task of this article is to analyze the VCL class library to explain the principle of doing this, and then give a method that only uses 3 lines of code to completely solve the problem of this "abnormal window" in Delphi.
2 Principle
2.1 Application creation process
Here is a typical application Delphi project file. We noticed that there was a reference to the Initialize method of the Application object from the beginning, and our analysis started from here:
PRogram Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
Begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end .
The hidden window is created by the Application object, so where does the Application object come from? Press and hold Ctrl in Delphi's code editing window and click Application, you will find that the Application object is one of several global objects defined in the Forms.pas unit. This is not enough, what we want to know is where the Application object was created, because an instance of the TApplication class must be successfully created before we can refer to it.
Think about it, is there any code that will be executed before Application.Initialize? By the way, it is the code in the initialization code segment. After carefully debugging the VCL source code, you can know that many units in VCL have initialization code segments. When starting the Delphi program, first execute the initialization code segment code in each unit in the order of uses, and then execute the Application after completing all initialization actions. The Initialize method is to initialize the Application, so it is obvious that the Application object is created in the initialization snippet of a certain unit.
Searching in the VCL source code directory with the keyword "TApplication.Create", we found the code to create the Application object in the Controls.pas unit. In the initialization code segment of the Controls.pas unit, there is a call to the InitControls procedure, and the implementation of InitControls is as follows:
Unit Controls;
…
Initialization
...
InitControls;
procedure InitControls;
Begin
...
Mouse := TMouse.Create;
Screen := TScreen.Create( nil );
Application := TApplication.Create( nil );
...
end ;
OK, at this point our analysis has completed the first step, because to solve the problem of abnormal windows, we must do one thing before initializing the Application object, so it is very important to understand the initialization process of the application.
2.2 IsLibrary variables
The IsLibrary variable is one of the global flag variables defined in the System.pas unit. If the value of IsLibrary is true, it means that the program module is a dynamic link library, otherwise it is an executable program. Some processes in the VCL class library complete different actions based on different values of this flag variable. That is, this variable plays a key role in solving the abnormal window problem of Delphi.
As mentioned earlier, for convenience, an invisible window was created when the Application object was initialized (that is, the window with "TApplication" as the class name seen with tools like Spy++), but it is also because of this invisible The window makes the programs developed with Delphi show many abnormalities. OK, if we can remove this invisible window (and remove the taskbar system menu at the same time) and replace it with our application main window, wouldn’t all the problems be solved?
It’s simple to say, but does it require major surgery to implement the VCL source code? Wouldn't that be a bit of putting the cart before the horse? Of course the answer is no, otherwise this article would not have been available. What I want to say here is that in the next analysis, we will see that the so-called "the way of programming lies in one mind", the practice of unintentional willow planting in TApplication design actually leaves us with the solution to this problem. The interface. If you don’t do source code analysis, you may have to go around in circles, but in fact we will see that the genius design leaves us with no more or less, just right.
Open the constructor Create of the TApplication class and we will find such a line of code.
constructor TApplication.Create(AOwner: TComponent);
Begin
...
if not IsLibrary then CreateHandle;
...
end ;
What we are talking about here is that if the program module is not a dynamic link library, then execute CreateHandle, and the work done by CreateHandle is as follows in the help: "If there is no application window, create a ", here" application Program window" is the invisible window mentioned above, which is the culprit. In the TApplication class, the FHandle variable is used to save its window handle. This is to complete different actions according to the value of IsLibrary, because in dynamic link libraries, message loops are generally not required, but to use Application objects to develop dynamic link libraries with VCL, so here is the design. OK, we just need to trick the Application object, assign IsLibrary to true before it is created, and filter out the execution of CreateHandle and remove this annoying window.
The code assigned to IsLibrary should obviously be placed in the initialization code segment of a certain unit. Moreover, since the code in the initialization code segment is executed in the order of the included units, in order to ensure that IsLibrary is assigned to true before the Application object is created, In the project file, we must place the unit containing the assignment code before the Forms unit, as follows (assuming that the unit is named UnitDllExe.pas):
program template;
uses
UnitDllExe in 'UnitDllExe.pas',
Forms,
FormMain in 'FormMain.pas' {MainForm},
...
The UnitDllExe.pas code list is as follows:
unit UnitDllExe;
interface
Implementation
Initialization
IsLibrary := true;
//Tell the Applciation object that this is a dynamic link library and does not need to create hidden windows.
end .
Okay, compile and run it. We see that since no hidden window was created, the system menu on the original taskbar disappeared and was replaced with the system menu of the main window. The main window can also be arranged and tile normally with other Windows windows. But the problem is that the window cannot be minimized. What's going on? It's still the old way, follow it.
2.3 Main window minimization
Minimization belongs to system commands, and ultimately, it must be called the API function DefWindowProc to minimize the window, so we found the function WMSysCommand in TCustomForm that responds to the WM_SYSCOMMAND message without any difficulty, which clearly writes to redirect the minimized message to Application.WndProc to handle:
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 ;
In Application.WndProc, the Minimize method of Application is called in response to the minimized message, so the crux of the problem must be in the Minimize process.
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 ;
Finally, find TApplication.Minimize and you will understand everything. The call to the DefWindowProc function here does not produce any effect, why? Since we deceived the Application object before, filtered out the call of CreateHandle and did not create the window needed to respond to the message of the Application object, the handle FHandle is 0, and the call is of course unsuccessful. If you can point FHandle to our main application window, it will solve the problem.
procedure TApplication.Minimize;
Begin
...
DefWindowProc(FHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
//The FHandle value here is 0
...
end ;
3 Implementation
The unintentional design of Borland's geniuses once again allowed us to solve the problem. From the previous analysis, we know that in the dynamic link library developed with VCL, there is no hidden window to receive Windows messages (CreateHandle does not execute), but in the dynamic link library, if you want to display the window, you need a parent window. How to solve this problem? The VCL designer designs the FHandle variable that holds invisible window handles as writable, so we can actually simply assign a value to FHandle to provide a parent window for the child window that needs to be displayed. For example, in a dynamic link library plug-in to display a form, we usually pass the handle of the Application object through a function of the dynamic link library in the main module executable file and assign it to the Application.Handle of the dynamic link library in the main module executable file and assign it to the Application.Handle of the dynamic link library in the application. , similar to:
procedure SetApplicationHandle(MainAppWnd: HWND)
Begin
Application.Handle := MainAppWnd;
end ;
OK, since Aplication.Handle is actually just a window handle that is used internally to respond to messages, and the invisible window that should have been created has been removed by us, we only need to give a window handle to replace that Isn't it enough to just hide the handle of the originally unnecessary window? Where can I find such a window? The main window of the application is the best choice, so the following code is available.
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 .
So, all problems were solved. You do not need to make any modifications to the VCL source code, and do not need to make any modifications to the original program. Just add two lines of code to the project file, add one line in UnitDllExe.pas, and a total of three lines of code to make your The application window is exactly as normal as any standard Windows window.
1) The taskbar and the window title bar have consistent system menus.
2) There is an animation effect when the main window is minimized.
3) The window can be arranged and tiled normally with other windows.
4) When there is a modal window, it cannot operate on its parent window.
The above implementation code is used in all versions of Delphi.