Controls located in the Windows.Forms namespace are to .NET languages the same thing VCL is to Delphi and C++Builder languages — a collection of controls that allow us to build GUI applications.
Building Windows.Forms and VCL.NET applications inside the IDE is pretty much the same. Figure 29-15 and the following points illustrate this:
Figure 29-15: Building Windows.Forms applications in Delphi
The center of a VCL.NET application is the TForm class (located in the Borland.VCL.Forms unit), and the center of a Windows.Forms application (either Delphi for .NET or C#) is the Form class, located in the System.Windows.Forms namespace, and physically in the System.Windows.Forms assembly.
Both VCL.NET and Windows.Forms applications are built by dropping components on the Designer Surface (form).
To build either VCL.NET or Windows.Forms applications, you have to write code in response to various events fired by the components.
Both frameworks are built on top of the Win32 API.
There are also differences between the two frameworks:
The VCL framework can be used to build applications that run on Windows and Linux (if you install CrossKylix you can build both Windows and Kylix applications on Windows), while Windows.Forms applications only run on the .NET platform.
With minor changes, VCL applications can be converted to VCL.NET and target the .NET platform. Windows.Forms applications cannot target any platform besides .NET.
VCL applications can currently be built only by Delphi and C++Builder developers, and VCL.NET applications can only be built by Delphi developers. Windows.Forms applications can be created with any .NET language.
Custom VCL/VCL.NET components can only be created by Delphi and C++Builder developers (Win32 version). .NET components can be created in any .NET language.
VCL objects are stored in separate files (*.dfm files in VCL, *.nfm files in VCL.NET). Windows.Forms controls are stored in the main source file as a series of statements that create them and initialize their properties. (Listing 29-23 shows the source code of the main form from a Delphi for .NET Windows.Forms application.)
Windows.Forms does not have anything similar to actions and the TActionList/TActionManager components; however, VCL doesn't have an ErrorProvider component. :)
Windows.Forms applications are easier to deploy because the System.Windows.Forms assembly ships with the .NET framework. VCL.NET applications can be "standalone" (require only the .NET framework) but take up approximately 2 MB, or they can reference VCL assemblies and be much smaller (in this case, you'll have to deploy Borland.Delphi.dll, Borland.Vcl.dll, and Borland.VclRtl.dll assemblies alongside your application).
Listing 29-23: The source code of a basic Windows.Forms form with a single Label component
unit MainForm; interface uses System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data; type TMainForm = class(System.Windows.Forms.Form) { $REGION 'Designer Managed Code' } strict private /// <summary> /// Required designer variable. /// </summary> Components: System.ComponentModel.Container; Label1: System.Windows.Forms.Label; /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> procedure InitializeComponent; { $ENDREGION } strict protected /// <summary> /// Clean up any resources being used. /// </summary> procedure Dispose(Disposing: Boolean); override; private { Private Declarations } public constructor Create; end; [assembly: RuntimeRequiredAttribute(TypeOf(TMainForm))] implementation { $AUTOBOX ON } { $REGION 'Windows Form Designer generated code' } /// <summary> /// Required method for Designer support -- do not modify /// the contents of this method with the code editor. /// </summary> procedure TMainForm.InitializeComponent; begin Self.Label1 := System.Windows.Forms.Label.Create; Self.SuspendLayout; // // Label1 // Self.Label1.Location := System.Drawing.Point.Create(32, 32); Self.Label1.Name := 'Label1'; Self.Label1.TabIndex := 0; Self.Label1.Text := 'Label1'; // // TMainForm // Self.AutoScaleBaseSize := System.Drawing.Size.Create(5, 13); Self.ClientSize := System.Drawing.Size.Create(448, 270); Self.Controls.Add(Self.Label1); Self.Name := 'TMainForm'; Self.Text := 'Delphi for .NET Windows.Forms Application'; Self.ResumeLayout(False); end; { $ENDREGION } procedure TMainForm.Dispose(Disposing: Boolean); begin if Disposing then begin if Components <> nil then Components.Dispose(); end; inherited Dispose(Disposing); end; constructor TMainForm.Create; begin inherited Create; // // Required for Windows Form Designer support // InitializeComponent; // // TODO: Add any constructor code after InitializeComponent call // end; end.
As in .NET console applications, the entry point of a Windows.Forms application is the Main method. With Windows.Forms applications, the Main method contains a single line of code that creates the main form and calls the Run method of the global .NET Application object (instance of System.Windows.Forms.Application) to start the application and display the main form on screen. Listings 29-24A and 29-24B show the Main methods from a Delphi for .NET Windows.Forms application and a C# Windows.Forms application.
Listing 29-24A: The entry point of a Delphi for .NET Windows.Forms application
[STAThread] begin Application.Run(TWinForm.Create); end.
Listing 29-24B: The entry point of a C# Windows.Forms application
[STAThread] static void Main() { Application.Run(new WinForm()); }
Among other differences, Windows.Forms and VCL frameworks differ in the fact that Windows.Forms controls can call multiple handlers in response to an event, while VCL components traditionally only fire a single event handler in response to an event.
In VCL applications, you can assign event handlers to an event using the assignment operator. If you assign the nil value to an event handler, you are removing the event handler from the event.
In Windows.Forms applications, you can add and remove multiple event handlers from an event using the Include and Exclude procedures. To see how this is done, take a look at Figure 29-16 and the example in Listing 29-25.
Figure 29-16: Windows.Forms application that illustrates multicast events
The Button1 and Button2 components in this example have their own event handlers that use the Show method of the MessageBox class to display a message dialog box. Button3 doesn't have its own Click event handler but calls the Click event handlers of Button1 and Button2.
The two event handlers are added to the Click event using the Include procedure in the form's OnLoad event (equivalent to the OnCreate event in VCL applications).
Listing 29-25: Assigning multiple event handlers to an event in a Delphi for .NET Windows.Forms application
procedure TWinForm.TWinForm_Load(sender: System.Object; e: System.EventArgs); begin // add event handlers of Button1 and Button2 to the // Click event of Button3 Include(Button3.Click, Button1_Click); Include(Button3.Click, Button2_Click); end; procedure TWinForm.Button2_Click(sender: System.Object; e: System.EventArgs); begin MessageBox.Show('Button2.Click', 'Message', MessageBoxButtons.OK, MessageBoxIcon.Information); end; procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs); begin MessageBox.Show('Button1.Click', 'Message', MessageBoxButtons.OK, MessageBoxIcon.Information); end;
In C#, you use the += to add event handlers to an event and –= to remove an event handler from an event (see Listing 29-26). Besides using these two operators, you have to instantiate the System.EventHandler delegate (dele- gates are covered in the next chapter) and pass to the EventHandler's constructor the name of the event handler you wish to add to the event.
Listing 29-26: Assigning multiple event handlers to an event in a C# Windows.Forms application
private void button1_Click(object sender, System.EventArgs e) { MessageBox.Show("button1.Click", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void button2_Click(object sender, System.EventArgs e) { MessageBox.Show("button2.Click", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void WinForm_Load(object sender, System.EventArgs e) { button3.Click += new System.EventHandler(button1_Click); button3.Click += new System.EventHandler(button2_Click); }
When creating controls dynamically, you have to manually set the control's parent, because the control is displayed on its parent control. In VCL, you display a control by setting a container control, like the main form, to its Parent property. In Windows.Forms applications, you add the component to the container control by calling the Add method of the container control's Controls collection.
Listing 29-27 shows how to dynamically create a Windows.Forms.Button control and how to display it on the main form. Figure 29-17 shows the result of the code in Listing 29-27.
Figure 29-17: A dynamically created Windows.Forms.Button calling a dynamically assigned event handler
Listing 29-27: Dynamically creating a Windows.Forms.Button control
procedure TWinForm.DynamicEvent(sender: System.Object; e: System.EventArgs); begin System.Windows.Forms.MessageBox.Show('The form will now close.', 'Message', MessageBoxButtons.OK, MessageBoxIcon.Information); Self.Close; end; procedure TWinForm.TWinForm_Load(sender: System.Object; e: System.EventArgs); var btn: Button; begin btn := Button.Create; btn.Location := System.Drawing.Point.Create(10, 10); // top and left btn.Size := System.Drawing.Size.Create(100, 25); // width and height btn.Text := 'Close'; Include(btn.Click, DynamicEvent); // equivalent to "Button.Parent := Self" in VCL Self.Controls.Add(btn); end;
Figure 29-18 shows a very simple Windows.Forms MDI application that we are going to build in this section.
Figure 29-18: A Windows.Forms MDI application
To have a Windows.Forms form act as an MDI parent, set its IsMDIContainer property to True. Then add a MainMenu and an OpenFileDialog component from the components category to it. To allow users to select multiple files with the OpenFileDialog component, set its MultiSelect property to True.
Now add another Windows Form to the project and drop a TextBox component on it. The Windows.Forms.TextBox control can act as the TEdit and TMemo component. To display an entire text file in the TextBox control, set its Multiline property to True and its Dock property to Fill, to have it cover the entire form.
To load an entire text file into the TextBox control, you have to use the System.IO.StreamReader class. Listing 29-28 shows how to load a text file into a TextBox control.
Listing 29-28: Loading an entire text file into a Windows.Forms.TextBox control
unit Child; interface uses System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data, System.IO; type TChildForm = class(System.Windows.Forms.Form) public constructor Create; procedure LoadFile(FileName: string); end; implementation procedure TChildForm.LoadFile(FileName: string); var sr: System.IO.StreamReader; begin sr := System.IO.StreamReader.Create(FileName); try TextBox1.Text := sr.ReadToEnd; finally sr.Close; end; end;
The last tasks you have to do are create a File menu and the File ® Open item and write code that displays the OpenFileDialog and creates child forms. This code is displayed in Listing 29-29.
Listing 29-29: Creating child forms
procedure TWinForm.FileOpenItem_Click(sender: System.Object; e: System.EventArgs); var fileName: string; childForm: TChildForm; begin // display the OpenFileDialog if OpenFileDialog1.ShowDialog = System.Windows.Forms.DialogResult.OK then begin // browse through the selected files for fileName in OpenFileDialog1.FileNames do begin childForm := TChildForm.Create; // this changes the form to an mdi child form childForm.MdiParent := Self; childForm.Text := fileName; childForm.LoadFile(fileName); childForm.Show; end; // for end; // if ShowDialog end;