Thursday, October 15, 2020

 Application Settings As some applications get more sophisticated, users expect more from all their applications. For example, some applications let users set fonts and colors and all kinds of crazy things (guaranteeing that you'll never be able to successfully test every combination). In addition to the other fancy user interface (UI) features that it provides, WinForms supports the idea of reading a control's properties from an application's configuration file, also called a .config file . .NET allows every application to have a .config file associated with it. For example, when .NET loads MySecondApp.exe, it will look for a corresponding MySecondApp.exe.config file in the same folder as the application. A .config file can have standard settings for things such as security, remoting, loading, and versioning. In addition to standard .NET settings, a configuration file can have custom settings strictly for the application's use. For example, the following is a .config f


ile containing settings specifying a custom key-value pair

 

 Notice that the .config file is in XML format. This means that savvy users can open the .config file in Notepad to change the setting. For this setting to be used to set the main form's opacity property, the setting needs to be read:


 Instead of opening the .config file directly, this code uses the AppSettingsReader function from the System.Configuration class. This class provides access to the key-value pairs in the section of the .config file associated with the application. We use the reader to get the value of the MainFormOpacity key and use that to set the property on the form. If this is the kind of thing you'd like to provide in your application's forms for a wide variety of properties, it will be a chore to manually pull each value from the AppSettingsReader. For this reason, each control on each form, as well as the form itself, has a set of dynamic properties available in the Property Browser. The dynamic properties are those automatically read from the application's .config file. For example, to tell the Designer to generate code to pull in the opacity setting for the main form, you click the Advanced property's "…" button under the Dynamic Properties for the form, bringing up the list of potential properties to be made dynamic, as shown in 

 Dynamic Properties for MainForm


 

 Any property checked in this dialog will be read from the .config file. Notice the key mapping provided by the dialog after we've chosen to make Opacity dynamic. This mapping is the key that will be used in the .config file. After you've chosen a dynamic property, three things happen. First, a file called app.config is added to your project. This file will be automatically copied into the output directory of your project as it is built and will be renamed to match the name of your application, saving you from having to manually keep .config files up-to-date in Release and Debug directories. The second thing that happens is that the contents of app.config will be populated with whatever value is set in the Property Browser for that property. For example, the following app.config file will be generated for MainForm.Opacity (assuming a default opacity value of 1)

 

 

 


The final thing that happens for each dynamic property is that the InitializeComponent function is augmented with code to pull in the properties at run time, saving you the need to write that code yourself

 



 

 

In the Designer, you can drop and arrange any controls on the user control that you like, setting their properties and handling events just as on a form.  shows a sample user control as seen in the Designer

 

A User Control Shown in the Designer


When you're happy with your control, you can add it to the Toolbox by dragging the user control's source code file, in this case UserControl1.cs, from the Solution Explorer to the Toolbox. When it's there, you can drag and drop it onto the forms of your choice, setting properties and handling events via the Property Browser just as with any of the built-in controls. shows the user control from  hosted on a form.  


User controls aren't the only kind of custom controls. If you're interested in drawing the contents of your controls yourself, scrolling your controls, or getting more details about user controls, you'll want to read

 Controls

 Often, after arranging a set of controls just right, you need that group of controls elsewhere. In that case, you can copy and paste the controls between forms, making sure that all the settings are maintained, or you can encapsulate the controls into a user control for a more robust form of reuse. User controls are containers for other controls and are best created in a Windows Control Library project. To add a Windows Control Library project to an existing solution, you use the Add New Project item from the menu you get when you right-click on your WinForms application's solution in Solution Explorer. You'll also want to make sure that you're creating the new project in the same location as your existing solution, because VS.NET 2002 defaults to placing new projects one folder too far up the hierarchy in most cases.  shows how to add a new project called MyFirstControlLibrary to an existing solution called MySecondApp.

  Adding a New Project to an Existing Solution


 

 After you've created a control library project, you'll be presented with a user control design surface very like that of a form. The only real difference is that there's no border or caption, which will be provided by the form host of your new control. The code generated by the Wizard looks very much like the code generated for a new form except that the base class is UserControl instead of Form

 

 

  Setting the Dock Property

 


 
As an example, the form in shows the Dock properties for a status bar, a tree view, and a list view, the latter two being split with a splitter control. You can arrange all this without writing a line of code.

Docking and Splitting

 Anchoring, docking, and splitting are not the only ways to arrange controls on a form. WinForms also lets you group controls and handle custom layout for special situations. In addition, WinForms supports arranging windows within a parent, which we call MDI. These techniques are all covered in detail in


 

 To change the text boxes so that they anchor to the right edge as well as the top and left edges is a matter of clicking on the anchor rectangle on the right and changing the Anchor property to Top, Left, Right. This will cause the text boxes to resize as the form resizes, as shown in

 

Anchoring Text Boxes Top, Left, Right and Buttons Bottom, Right

 

 


The default anchoring is top-left, but those edges need not be a part of the anchoring settings at all. For example, notice that anchors the OK and Cancel buttons to the bottomright, as is customary with Windows dialogs. If instead of building a dialog-style form, you'd like to build a window-style form, anchoring is not your best bet. For example, suppose you're building an Explorer-style application, with a menu bar and toolbar on the top, a status bar on the bottom, and a tree view and a list view taking up the rest of the space as determined by a splitter between them. In that kind of application, anchoring won't do. Instead, you'll want docking. Docking allows you to "stick" any control on the edge of its container, the way a status bar is stuck to the bottom of a form. By default, most controls have the Dock property set to None (the default for the StatusBar control is Bottom). You can change the Dock property in the Property Browser by picking a single edge to dock to or to take up whatever space is left, as shown in

 The user isn't resizing the form to get more gray space but to make the controls bigger so that they will hold more data. For that to happen, the controls need to resize to take up the newly available space. You can do this manually by handling the form's Resize event and writing the code. Or you can do it with anchoring. Anchoring is one of the ways that WinForms provides for automatic layout control of your forms and the controls contained therein. By default, all controls are anchored to the upperleft, so that as the form is resized and moved, all controls are kept at their position relative to the upper-left corner of the form. However, in this case, we'd clearly like to have the text box controls widen or narrow as the form is resized. We implement this by setting each text box's Anchor property. In the Property Browser for the control, we choose the Anchor propert


y, which displays an editor like the one in

 Arranging Controls

The beauty of the Designer is that it lets you lay out your controls lovingly within your form, making sure everything lines up nicely, as shown 





 After you've added a handler to a form, that handler will show up in a drop-down list for other events having the same signature. This technique is handy if you'd like the same event for multiple objects to be handled by the same method, such as multiple buttons with the same handler. You can use the sender argument to determine which object fired the event:

If, as is often the case, you'd like each event that you handle for each object to be unique or you just don't care what the name of the handler is, you can simply double-click on the name of the event in the Property Browser; an event handler name will be generated for you, based on the name of the control and the name of the event. For example, if you double-clicked on the Load event for the Form1 form, the event handler name would be Form1_Load. Furthermore, if you're handling the default event of an object, you can handle it simply by double-clicking on the object itself. This will generate an event handler name just as if you'd double-clicked on that event name in the Property Browser event list. The default event of an object is meant to be intuitively the most handled event for a particular type. For example, I'm sure you won't be surprised to learn that the default event for a button is Click and that the default event for a Form is Load. Unfortunately, neither the Designer nor the Property Browser gives any indication what the default event will be for a particular type, but experimentation should reveal few surprises

 You have a few ways to handle an event from the Property Browser window. One way is to find the event you'd like to handle on the object selected (say, Click) and type the name of the function you'd like to call when this event is fired (say, button_Click). Then press Enter, and VS.NET will take you to the body of an event handler with that name and the correct signature, all ready for you to implement



 It may look like your favorite programming language, but InitializeComponent is actually the serialized form of the object model that the Designer is using to manage the design surface. Although you can make minor changes to this code, such as changing the Text property on the new button, major changes are likely to be ignored—or worse, thrown away. Feel free to experiment with just how far you can go by modifying this serialization format by hand, but don't be surprised when your work is lost. I recommend putting custom form initialization into the form's constructor, after the call to InitializeComponent, giving you confidence that your code will be safe from the Designer. Of course, we put up with the transgression of the Designer because of the benefits it provides. For example, instead of writing lines of code to set properties on the form or the controls contained therein, all you have to do is to right-click on the object of interest and choose Properties (or press F4) to bring up the Property Browser for the selected object, as shown in



Notice again that this code is very similar to what we built ourselves, but this time created for us by the Designer. Unfortunately, for this process to work reliably, the Designer must have complete control over the InitializeComponent method. In fact, notice that the Wizardgenerated InitializeComponent code is wrapped in a region, which will hide the code by default, and is marked with a telling comment:


 Before we start the drag-and-drop extravaganza that the Designer enables, let's take a look at a slightly abbreviated version of the code generated by the WinForms application Wizard (available by right-clicking on the design surface and choosing View Code or by pressing F7):

Most of this code should be familiar, including the using statements at the top, the form class that derives from the Form base class, the static Main function inside a class providing the entry point to the application, and the call to Application.Run, passing an instance of the main form class. The only thing that's different from what we did ourselves is the call to InitializeComponent in the form's constructor to set the form's properties instead of doing it in the constructor itself. This is done so that the WinForms Designer, which we can use to design our form visually, has a place to put the code to initialize the form and the form's control. For example, dragging a button from the Toolbox onto the form's design surface will change the

 Windows Forms in Visual Studio .NET

Most WinForms projects start in the New Project dialog box, available via File | New | Project (Ctrl+Shift+N) and shown To build an application, you'll want the Windows Application project template. To build a library of custom controls or forms for reuse, you'll want the Windows Control Library project template. When you run the Windows Application Wizard, choosing whatever you like for the project name and location, you'll get a blank form in the Designer, as shown

To build an application, you'll want the Windows Application project template. To build a library of custom controls or forms for reuse, you'll want the Windows Control Library project template. When you run the Windows Application Wizard, choosing whatever you like for the project name and location, you'll get a blank form in the Designer

 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

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

 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:


 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:


 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:


 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:


 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:


 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 Formderived class):

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:

 


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:

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


 

 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.

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



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:

 Application Settings As some applications get more sophisticated, users expect more from all their applications. For example, some applicat...