WinForms from Scratch


A typical Windows Forms application has at least one form. Without the form it's just an "application," which is pretty boring. A form is simply a window, the unit of the Microsoft user interface we've seen since Windows 1.0.

One form in a WinForms application is typically the main form , which means that it is either the parent or the owner [1] of all other forms that may be shown during the lifetime of the application. It's where the main menu is shown, along with the toolbar, status bar, and so on. When the main form goes away, the application exits.

[1] The distinction between a form's "parent" and "owner" is covered in detail in Chapter 2: Forms.

The main form of an application can be a simple message box, a dialog box, a Single Document Interface (SDI) window, a Multiple Document Interface (MDI) window, or something more complicated, such as the forms you're used to seeing in applications like Visual Studio .NET. These latter forms may include multiple child windows, tool windows, and floating toolbars .

If your application is enormously simple, you can implement it using the staple of any windowing system, the lowly message box :

 class MyFirstApp {   static void Main() {  System.Windows.Forms.MessageBox.Show("Hello, Windows Forms");  } } 

If you're new to C#, Main is the entry point for any C# application. The Main function must be a member of a class, and hence the need for MyFirstApp. However, the .NET runtime doesn't create an instance of the MyFirstApp class when our code is loaded and executed, so our Main function must be marked static . In this way, you mark a method as available without creating an instance of the type.

The single line of real code in our first WinForms application calls the static Show method of the System.Windows.Forms.MessageBox class, which is really a long-winded way of saying we're calling a method on the MessageBox class contained within the System.Windows.Forms namespace. Namespaces are used extensively in the .NET Framework Class Libraries (FCL) to separate types, such as classes, structures, enumerations, and so on, into logical groupings. This separation is necessary when you've got thousands of Microsoft employees working on the FCL as well as hundreds of third parties extending it and millions of programmers trying to learn it. Without namespaces, you would need all kinds of wacky conventions to keep things uniquely named (as demonstrated by the existing Win32 API).

However, as necessary as namespaces are, they're a little too much typing for me, so I recommend the C# using statement, as shown here:

  using System;   using System.Windows.Forms;  class MyFirstApp {   static void Main() {  MessageBox.Show("Hello, Windows Forms");  } } 

When the compiler sees that the MessageBox class is being used, it first looks in the global namespace , which is where all types end up that aren't contained by a namespace (for example, the MyFirstApp class is in the global namespace). If the compiler can't find the type in the global namespace, it looks at all the namespaces currently being used ”in this case, System and System.Windows.Forms. If the compiler finds a type name being used that exists in two or more namespaces, it produces an error and we're forced to go back to the long notation. But in practice this is rare enough to make the short form the form of choice when you're typing code by hand.

However, even though the MessageBox class is enormously handy for showing your users simple string information or asking them yes/no questions, it's hard to build a real application with MessageBox. For most things, you'll need an instance of the Form class (or a Form-derived class):

 class MyFirstApp {   static void Main() {     Form form = new Form();  form.Show(); // Not what you want to do  } } 

Although this code will show the form, you'll have to be quick to see it because Show shows the form modelessly. If you're not steeped in user interface lore, a modeless form is one that displays but allows other activities (called modes ) to take place. So, immediately after Show puts our new form on the screen, it returns control to the Main function, which promptly returns, exiting the process and taking our nascent form with it. To show a form modally ” that is, to not return control to the Main function until the form has closed ”the documentation suggests using the ShowDialog function:

 class MyFirstApp {   static void Main() {     Form form = new Form();  form.ShowDialog(); // Still not what you want to do  } } 

This code would show a blank form and wait for the user to close it before returning control to the Main function, but it's not the code you will generally be writing. Instead, to make it accessible in other parts of your application, you'll be designating one form as the main form. To do this, pass the main form as an argument to the Run method of the Application object, which also resides in the System.Windows.Forms namespace:

 class MyFirstApp {   static void Main() {     Form form = new Form();  Application.Run(form); // This is what you want to do  } } 

The Application class's static Run method will show the main form, and when it's closed, Run will return, letting our Main function exit and closing the process. To see this in action, you can compile your first WinForms application using the following command line: [2]

[2] To get a command prompt with the proper PATH environment variable set to access the .NET command line tools, click on StartProgramsMicrosoft Visual Studio .NETVisual Studio .NET ToolsVisual Studio .NET Command Prompt. If you don't have VS.NET installed, you can set up the PATH using the corvars.bat batch file in your FrameworkSDK\Bin directory.

 C:\> csc.exe /t:winexe /r:System.Windows.Forms.dll MyFirstApp.cs 

The csc.exe command invokes the compiler on our source file, asking it to produce a Windows application via the /t flag (where the "t" stands for "target"), pulling in the System.Windows.Forms.dll library using the /r flag (where the "r" stands for "reference").

The job of the compiler is to pull together the various source code files into a .NET assembly. An assembly is a collection of .NET types, code, or resources (or all three). An assembly can be either an application, in which case it has an .exe extension, or a library, in which case it has a .dll extension. The only real difference between the types of assemblies is whether the assembly has an entry point that can be called by Windows when the assembly is launched (.exe files do, and .dll files do not).

Now that that the compiler has produced MyFirstApp.exe, you can execute it and see an application so boring, it's not even worth a screen shot. When you close the form, MyFirstApp.exe will exit, ending your first WinForms experience.

To spice things up a bit, we can set a property on our new form before showing it:

 class MyFirstApp {   static void Main() {     Form form = new Form();  form.Text = "Hello, WinForms!";  Application.Run(form);   } } 

Like most objects in the FCL, Form objects have several properties to access, methods to call, and events to handle. In this case, we've set the Text property, which, for a Form, sets the caption. We could do the same thing to set other properties on the form, showing it when we were finished, but that's not the way we generally do things in WinForms. Instead, each custom form is a class that derives from Form and initializes its own properties:

  class MyFirstForm : Form {   public MyFirstForm() {   this.Text = "Hello, WinForms!";   }   }  class MyFirstApp {   static void Main() {  Form form = new MyFirstForm();  Application.Run(form);   } } 

Notice that the MyFirstForm class derives from Form and then initializes its own properties in the constructor. This gives us a simpler usage model, as shown in the new Main function, which creates an instance of the MyFirstForm class. You also gain the potential for reuse should MyFirstForm be needed in other parts of your application.

Still, our form is pretty boring. It doesn't even include a way to interact with it except for the system-provided adornments. We can add some interactivity by adding a button:

 class MyFirstForm : Form {   public MyFirstForm() {     this.Text = "Hello, WinForms!";  Button button = new Button();   button.Text = "Click Me!";   this.Controls.Add(button);  } } 

Adding a button to the form is a matter of creating a new Button object, setting the properties that we like, and adding the Button object to the list of controls that the form manages . This code will produce a button on the form that does that nifty 3-D depress thing that buttons do when you press them, but nothing else interesting will happen. That's because we're still not handling the button's click event , where an event is a way for a control to notify its container that something has happened . For example, the following code handles the button's Click event:

 class MyFirstForm : Form {   public MyFirstForm() {     this.Text = "Hello, WinForms!";     Button button = new Button();     button.Text = "Click Me!";  button.Click += new EventHandler(button_Click);  this.Controls.Add(button);   }  void button_Click(object sender, EventArgs e) {   MessageBox.Show("That's a strong, confident click you've got...");   }  } 

Handling the button's Click event involves two things. The first is creating a handler function with the appropriate signature; we've named this function button_Click. The signature of the vast majority of .NET events is a function that returns nothing and takes two parameters: an object that represents the sender of the event (our button, in this case), and an instance of a System.EventArgs object (or an object that derives from the EventArgs class).

The second thing that's needed to subscribe to an event in C# is shown by the use of the "+=" operator in the MyFirstForm constructor. This notation means that we'd like to add a function to the list of all the other functions that care about a particular event on a particular object, and that requires an instance of an EventHandler delegate object. A delegate is a class that translates invocations on an event into calls on the functions that have subscribed to the event. For this particular event, we have the following logical delegate and event definitions elsewhere in the .NET FCL:

 namespace System {   public delegate void EventHandler(object sender, EventArgs e); } namespace System.Windows.Forms {   public class Button {     public event EventHandler Click;   } } 

Notice that the Click event on the Button class is a reference to an EventHandler delegate, and so to add our own method to the list of subscribers, we need to also create an instance of the delegate. [3] Of course, it can quickly become tedious to figure out the delegate signatures of all the events you're interested in or to add controls to a form via code by hand.

[3] For a more detailed, although less reverent, look at delegates and events, read Appendix B: Delegates and Events. For a much more detailed and reverent look, refer to Essential .NET (Addison-Wesley, 2003), by Don Box, with Chris Sells.

Luckily, it's also unnecessary because of the WinForms Wizard and the WinForms Designer provided by Visual Studio .NET.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net