Introduction
This article is a very simple introduction on writing a Windows Form application for
the Microsoft.NET framework using C#. The sample application demonstrates
how to create and layout controls on a simple form and the handling of mouse
click events. The application displays a form showing attributes of a file.
This form is similar to the properties dialog box of a file (Right click on a file and
Click on Properties menu item). Since attributes of a file will be shown,
the sample will show how to use File IO operations in .NET framework.
What is the starting point?
Like every Win32 application source, we will start with the inclusion of some header
files. C# does not make use of header files; it uses namespaces to accomplish
this purpose. Most of the C# core functionality is implemented in the System
namespace.
For forms application, the functionality is included in the System.WinForms
namespace.
Therefore, right at the top of our source file we need to define these namespaces.
using System;
using System.WinForms;
We will need some more namespace definitions, but I will explain them as we go
along with this sample application. Like every C# application, Windows Forms
application will be defined as a class. Since we will be making use of the Form
class,
it needs to be derive from it.
public class WinFileInfo : Form
The next thing that needs to be identified is the entry point for the application. Unlike
Win32 applications, the method Main
(not WinMain
) will be the entry point for this application.
public static void Main ()
{
Application.Run (new WinFileInfo ());
}
The Application
class provides static methods to manage an application like running, closing,
and managing windows messages. In the method Main
of the application we will
start running the application using the method Run
of the Application
class. And later on we will call the method Exit
on the Application
class to stop the application or in other words close the form.
How do I layout controls on the form?
Since we are writing a GUI application, we need some controls on the form. And because this
application has been written using the .Net SDK, no wizard or tools have been used.
So how can we put controls on the form and define their location sizes?
This is very much like writing a resource file for Win32 application. The System.WinForms
namespace contains definitions for all the common controls we will use on
forms or Windows Form applications e.g. Button
, Checkbox
, RadioButton
, etc.
For more information check the online documentation for .NET SDK. The Control
class
defines the base class for controls. This class contains the base methods.
All the controls override the virtual methods specific to their functionality.
First lets see what the output of this Form application looks like.
The Application does this layout of controls in the method InitForm
.
public WinFileInfo ()
{
InitForm ();
}
Setting the Text
property of Form
object will set the title of the form.
this.Text = "File Information Application";
The client size of the form is controlled by the ClientSize
property of the form.
this.ClientSize = new Size(400, 280);
The controls in the Form application follow a hierarchical pattern i.e. each control
acts as a container for other controls. And for complex GUI design, controls
can be layered on top of each other and then pushed back or brought forward as
needed. In this particular application, Form acts as a container for a Panel
, GroupBox
and
a bunch of other controls like buttons, static text, etc. The GroupBox
acts
as a container for three checkboxes. The panel acts as a container for an edit
control and a static text (in Forms terms, label control). The following code
adds the check boxes to the GroupBox
control container using the method Add
of the Controls collection of the Form.
wndAttribBox.Controls.Add (wndArchiveCheck);
wndAttribBox.Controls.Add (wndReadOnlyCheck);
wndAttribBox.Controls.Add (wndHiddenCheck);
The other method of adding controls to the controls collection is by directly creating
an array of control objects and setting the All
property of control collection of
Form to the array. The following code sets the child controls in the Form container.
this.Controls.All = new Control [] {
wndPanel,
wndAttribBox,
wndFileExistCheck,
wndLocationLabel,
wndLocation,
wndCreateTimeLabel,
wndLastAccessLabel,
wndLastWriteLabel,
wndCreateTime,
wndLastAccessTime,
wndLastWriteTime,
wndFindButton,
wndCloseButton
};
How do I create controls and set their properties?
Every Control in the .NET Framework is an Object that implements methods, properties and
events. An object is created using the new operator in C#. The same concept applies to
controls too. I will describe the creation of a Button control, setting its
properties like size and location and then event handlers for the click on the button.
First we need to define the control variable for the WinFileInfo
class. The following code
shows how the various controls have been defined and created.
ButtonwndFindButton= new Button ();
ButtonwndCloseButton= new Button ();
CheckBoxwndFileExistCheck= new CheckBox ();
CheckBoxwndArchiveCheck= new CheckBox ();
CheckBoxwndReadOnlyCheck= new CheckBox ();
CheckBoxwndHiddenCheck= new CheckBox ();
The best thing about the .NET Framework is that we do not have to worry about releasing
the variables allocated on the heap using the new operator. Garbage collection will do
its magic (trust MS on this) when the variable is not in use anymore.
The following code shows how the various properties for the Find button have been set. The name of
the button's properties are very self-explanatory but I will try to describe them one by one.
wndFindButton.Text = "Find";
wndFindButton.TabIndex = 0;
wndFindButton.Anchor = AnchorStyles.BottomRight;
wndFindButton.Size = new Size (72, 24);
wndFindButton.Location = new Point (110, 250);
wndFindButton.Click += new EventHandler (this.buttonFind_click);
The Text
property is used to set the text that will be displayed on the button.
This is like calling the SetWindowText
Win32 API call on button control.
The TabIndex
sets the index of Tab i.e. where does this control stand in the sequence when a user
navigates through the controls using the Tab key. This is like setting TabOrder using
Visual Studio control wizard. A TabIndex
of 0 indicates that this control will be
the first to gain focus when the Tab key is pressed.
The Anchor
is a very important property if you want to fix the location of a control with
respect to some fixed point of the parent control or container. For example in this
case I want the Button to always stay anchored to the Bottom Right corner of
the form no matter what. So if user tries to resize the form, the button will
reposition itself to stay anchored to the Bottom Right corner of Form.
The Size
property fixes the size of a control.
The Location
property sets the location of the control with respect to its parent control.
The most important setting is how to handle the event when the user clicks on this control. This is
done through adding an event handler to the Click
event of the Control
object.
We create a new event handler using the EventHandler
object and pass the function
that will handle the event, as an argument to the EventHandler
constructor.
The EventHandler
created is added to the Click event of the Button
.
This way we can create controls dynamically and set their properties and event handling methods.
After creating all the controls do not forget to add them to the parent
container like Form
, Panel
, GroupBox
, and etc.
How do I tweak some properties of the Form?
Like every Win32 control, we can customize the appearance and actions of all the
controls. For example, what button should handle the message if the user clicks
ENTER? Setting the AcceptButton
property of the form can do this.
this.AcceptButton= wndFindButton;
The following code from the InitForm
method shows some of the other properties that can be customized.
this.MaximizeBox = false;
this.AcceptButton= wndFindButton;
this.CancelButton = wndCloseButton;
this.StartPosition = FormStartPosition.CenterScreen;
this.Activated += new EventHandler (this.WinFileInfo_activate);
Can I use Win32 API functions in C#?
Yes, you can do it. For this you need to import Win32 DLLs that implement the
functions you need. E.g. for LoadImage
, DestroyObject
API calls, we need to import User32.dll and Gdi32.dll DLLs. This can be
accomplished by using code as shown below.
[DllImport("user32.dll")]
public static extern int LoadIcon(int hinst, String name);
[DllImport("user32.dll")]
public static extern int DestroyIcon(int hIcon);
[DllImport("user32.dll")]
public static extern int LoadImage(int hinst, String lpszName,uint uType,
int cxDesired,int cyDesired, uint fuLoad);
[DllImport("gdi32.dll")]
public static extern int DeleteObject(int hObject);
How do I perform File IO operations?
This application shows the attribute for files. This means we need to do some File
IO operations. The .NET Framework provides a File Object for IO operations. We can
create a File object by providing a file name as the parameter to constructor.
File myFile = new File(wndFileName.Text);
Then we can make use of the Attributes
property of the File Object to get the attributes of file.
The attributes of the File are defined as a FileSystemAttributes
enumeration object.
For all the available enum
values look in the documentation.
FileSystemAttributes attribs = myFile.Attributes;
After that, we can do logical operations on the Attributes value to check if a particular
file attribute is set or not. For example, to see if a file is of Archive
type
or not, the following code can be used.
if ((attribs & FileSystemAttributes.Archive) == FileSystemAttributes.Archive)
{
wndArchiveCheck.Checked = true;
}
One thing that needs to be noticed is that the result of a logical ‘&
’
operation on an Enumeration
is an Enumeration
.
It is not a Boolean value. Therefore the resultant value is being compared
against an enum value. There is one more gotcha in this operation. If you
do not put the logical operation, (attribs & FileSystemAttributes.Archive
),
in bracket, the compiler will throw an error. Right now I do not know if
it is a bug in the compiler or if that is the way it is intended, but for now put
the logical operations in brackets.
To get file's creation date, use the CreateDate
property of the File
object.
This will return a result in a DateObject
. And then you can use the Format method of the DateTime
object to get the string representation of the file's creation date. Check the online documentation for
the DateTime
object to see what are the available format values.
DateTime timeFile = myFile.CreationTime;
wndCreateTime.Text = timeFile.Format ("F", DateTimeFormatInfo.InvariantInfo);
Can I include my own resources in the application?
Yes, you can. Use Visual Studio to generate a resource script file (.RC
file).
Add the resources you want to it. In this sample application, I have added an
icon and a bitmap to the resource file. Then use the command line RC compiler
.
This will generate a .RES
file.
For example in this application, WinFileInfo.rc
file generates WinFileInfo.res
file. Then, using the /win32res
compiler option the resources can be embedded in the application.
One file that has to be copied into the folder is afxres.h
. Otherwise the RC file will not compile.
Can I associate a specific icon to the application?
Yes, it too can be done. There are two ways to accomplish this. One is to use the /win32icon
compiler option and specifying the icon's file name. The disadvantage of this approach
is that you cannot embed resources in the application using the /win32res
compiler option. The other approach is adding the icon in the resource file and
then making sure that it has the lowest ID. You can then embed the resource file using the
/win32res
compiler option. This way you will see your icon associated to the application
in an explorer window.
How do I compile the application?
I have used a makefile
that is provided with the .NET SDK. I had to make changes
in master.mak
and makefile
so that it will compile only my application,
not the whole bunch of sample applications. But I did some extra stuff in the makefile
to add some extra information to the output file. Here is how the makefile
looks.
!include master.mak
_IMPORTS=$(_IMPORTS) /r:System.WinForms.DLL/r:System.DLL
/r:Microsoft.Win32.Interop.DLL
/r:System.Drawing.DLL /r:System.Net.DLL
_WIN32RES=$(_WIN32RES) /win32res:WinFileInfo.res
#_WIN32ICON=$(_WIN32ICON) /win32icon:FormIcon.ico
_XMLDOC=$(_XMLDOC) /doc:WinFileInfo.xml
all: WinFileInfo.exe
WinFileInfo.exe: WinFileInfo.cs
You must be wondering what that _XMLDOC
is. This is a very interesting
feature of the C# compiler. This helps you create documentation for the
methods of your class. But you have to follow very specific rules for tags and
where to place them. I have done this for a couple of methods in this application.
You can look for more details in the online documentation. The following code shows
how XML documentation is being generated for the method ButtonClose_Click
.
Last Thought
This is just a very simple Windows Form application. This should give you a starting point to
build upon. I am not an expert on C# or Forms application. I am learning too so
I thought I will share the experience with you guys. Please do send your
comments and suggestions to me. I will try to improve this application and add
some more advanced features.
Updating to the final release of .NET
The latest version is now RTM compliant. Here is a brief discussion of the changes.
Some changes in referencing system assemblies during compilation
The application needs to refer to some system assemblies like System.DLL
,
System.Windows.Forms.DLL
, etc. Earlier we had to specify all these
assembly references through /reference
switch. But now C#
compiler looks for CSC.rsp
response file in the project directory.
If the response file is not found in the project directory, then it looks for
the file in the directory from where compiler was invoked. Since we are
assuming that you don't have IDE installed, then this compiler will be invoked
from "Windows Folder"\Microsoft.NET\"CLR Version"
directory. You
will find a CSC.rsp
file there. Take a look at this file. You will
see that it already has added reference to most commonly referenced assemblies.
Therefore you don't need to specify them through /reference
switch
anymore.
If you want compiler to ignore the inclusion of default CSC.rsp
file,
use /noconfig
compilation switch. If you include your own CSC.rsp
file in the project folder, then its settings override the settings specified
in global CSC.rsp
file. For more details, please look in the
documentation for this compiler switch. Unfortunately
you cannot specify noconfig
switch in Visual Studio .Net IDE.
Other changes
There are namespace changes, attribute name and value changes and some method
name changes in the new code from the previous version that was written for
Beta1. Other than that I did not have to make a whole lot of changes.
What Next?
Next we will try to write an article that will show how an ASP.Net application
can be written without the help of IDE. and it will show how ASP.Net references
the assemblies at compile time and run time.