Back to C# For Students site

Chapter 6

Using Objects

This chapter explains:

Introduction

In this chapter, we will deepen our understanding of objects. In particular, we will look at the use of different types of objects from the C# library of classes. Note that, though there are many hundreds of these, the principles of using them are similar.

Here is an analogy: reading a book - whatever the book - involves opening it at the front, reading a page, then moving to the next page. We know what to do with a book. It is the same with objects. When you have used a few of them, you know what to look for when presented with a new one.

In general, the objects we will make use of are termed controls or components. The terms are really interchangeable, but C# uses 'control' for items which can be manipulated on a form (such as a button).

Instance variables

In order to tackle more advanced problems, we need to introduce a new place to declare variables. So far, we have used int, string etc. to declare local variables within methods. But local variables alone are insufficient to tackle most problems.

Here we introduce a simple program (Car Park1, figure 6.1) to assist in the running of a car park (or parking lot). It has two buttons: 'entering' and 'leaving'. The attendant clicks the appropriate button as a car enters or leaves. The program keeps a count of the number of cars in the park, and displays it in a label.

fig 1 -Screenshot of Car Park1

Note that the count is changed by two methods, so it cannot be declared locally within only one of them. It is tempting to think that the variable can be declared within each method, but this would result in two separate variables. Here is the code:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace carpark1
{
    /// 
    /// Summary description for Form1.
    /// 
    public class Form1 : System.Windows.Forms.Form
    {
        private int carCount = 0;

        // lots of code we have omitted here


        private void enterButton_Click(object sender,
            System.EventArgs e)
        {
            carCount = carCount + 1;
            countLabel.Text = Convert.ToString(carCount);
 
        }

        private void leaveButton_Click(object sender, 
            System.EventArgs e)
        {
            carCount = carCount - 1;
            countLabel.Text = Convert.ToString(carCount);
        }
    }
}
C# has automatically created a class named Form1 for us. We have added two methods: enterButton_Click and leaveButton_Click - to the class. (We renamed the buttons to make the code more understandable.) We set the Text property of label1 to 0 at design-time.

The point at issue here is the declaring of the carCount variable, but before considering this, we need to overview the code as a whole, and identify the different sections. As you will see when looking at the screen, a large amount of code has been generated by the IDE, and we must leave most of it unaltered. However, there are times when we need to insert our own instructions into the middle of this created code. Here is its structure, which is basically similar for all programs:

Now that we have seen the overall pattern of the code that the C# IDE creates, let us focus on the carCount variable: Note that the programmer has free choice of names for instance variables. But what if a name coincides with a local variable name, as in:

public class Form1 : System.Windows.Forms.Form
{
    private int n = 8;


    private void MyMethod()
    {
        int n;
        n = 3;         //which n?
    }
}

Although both variables are accessible (in scope) within MyMethod, the rule is that the local variable is chosen. The instance variable (class-level) n remains set to 8.


SELF-TEST QUESTION
In the above Form1 class, what are the consequences of deleting the local declaration of n?
ANSWER: The program will still compile and run - but will probably produce wrong results. It now modifies the value of a variable that is shared between methods. Before, it modified a local variable.

Instance variables are essential, but you should not ignore locals. For example, if a variable is used inside one method only, and need not keep its value between method calls, make it local.

The form constructor

Let us re-visit the car park program. We used a variable carCount to count with, and we used a label to display the carCount value. We set the value of carCount to 0 within the program, and we set the text property of label1 to 0 at design time. In fact, these are not separate. Consider the possibility that five cars are left in the car park for an extended period. We have to alter the initial value of carCount as well as the initial value of the text property of label1. In reality, there is only one item holding the number of cars. This is carCount. Rather than separately setting the initial text value of Label1 at design time, it would be better to arrange that the value of carCount - whatever it is - is placed in the label as the program starts running.

It is common for the initial values of controls to depend on variables and on other controls. We could attempt to set up this situation at design-time, but for several controls this is error-prone, and does not express the dependencies. It is better if we set up related initial values in code. Fortunately C# provides a special area of the program for such once-only initialisation. Look at the second version: Car Park2.

public class Form1 : System.Windows.Forms.Form
{
    private int carCount = 0;
    private System.Windows.Forms.Button enterButton;
    private System.Windows.Forms.Button leaveButton;
    private System.Windows.Forms.Label countLabel;
    private System.Windows.Forms.Label label1;
    /// 
    /// Required designer variable.
    /// 
    private System.ComponentModel.Container components = null;

    public Form1()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        countLabel.Text = Convert.ToString(carCount);

        //
        // TODO: Add any constructor code after 
        //       InitializeComponent call
        //
    }

    private void enterButton_Click(object sender,
        System.EventArgs e)
    {
        carCount = carCount + 1;
        countLabel.Text = Convert.ToString(carCount);

    }

    private void leaveButton_Click(object sender, 
        System.EventArgs e)
    {
        carCount = carCount - 1;
        countLabel.Text = Convert.ToString(carCount);

    }
}

Recall that we normally omit the using lines at the top of the program, and the namespace with its { and }.

Locate the section headed:


public Form1()
{
    

This starts a section of code that is a kind of method, but it has no void or return type. Its name (Form1) matches the class name, and the method is known as the constructor for Form1.

When the C# system runs your program, the first thing it does is to call the constructor for the form. The role of the constructor is to build the form, and initialise it. (Note that because the constructor is called from outside the class, it is declared as public rather than private).

If you look at the code created for the constructor, you will see that it is mainly comments, aside from a method call:


InitializeComponent();
whose role is to place components on the form. Following this line, we can insert instructions of our own to do some more initialisation. For this program, we put:
private int carCount = 0;

Note that the created code also identifies this area by a comment:


// TODO: Add any constructor code after InitializeComponent call

In this improved version of the program, we have no need to set the Text of the label at design time - instead, we have inserted code to do this task in the constructor, so the Text is guaranteed to be the same as carCount.


SELF-TEST QUESTION
What is wrong with:
public Form1()
{
    ...etc

    label1.Text = "38";
    InitializeComponent();
}


ANSWER: The program accesses a label before that label has been created (in InitializeComponent). This produces a run-time error.

In the above example, we modified the constructor, but did not call it ourselves. Later in this chapter, we will create new objects by explicitly calling the constructor of their class.

The TrackBar class

Here is another example of component initialization. The track bar is a GUI control from the toolbox. It is similar in nature to the scroll bar at the side of a word-processor window, but the track bar can be placed anywhere on a form. The user can drag the 'thumb' to the required position, and the minimum and maximum values can be set via properties - at design-time and run-time.

The track bar is not used for precise settings such as entering your age, where the range might be large. i.e. 10 to 100. It is used for more informal settings, such as setting the volume of a loudspeaker.

Here we look at a program (Oval Shape) which allows the user to modify the width and the height of an ellipse. The current dimensions are displayed on the form in labels. Figure 6.2 shows a screenshot, and here is the code:

public class Form1 : System.Windows.Forms.Form
{
    private Graphics paper;

    public Form1()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        paper = pictureBox1.CreateGraphics();
        vertTrackBar.Minimum = 0;
        vertTrackBar.Maximum = pictureBox1.Height;
        vertLabel.Text = Convert.ToString(vertTrackBar.Value);

        horizTrackBar.Minimum = 0;
        horizTrackBar.Maximum = pictureBox1.Width;
        horizLabel.Text = Convert.ToString(horizTrackBar.Value);
        //
        // TODO: Add any constructor code after 
        //       InitializeComponent call
        //

    } 



    private void vertTrackBar_Scroll(object sender,
        System.EventArgs e)
    {
        SolidBrush myBrush = new SolidBrush(Color.Black);
        vertLabel.Text = Convert.ToString(vertTrackBar.Value);
        paper.Clear(Color.White);
        paper.FillEllipse(myBrush, 0, 0, horizTrackBar.Value,
            vertTrackBar.Value);

    }

    private void horizTrackBar_Scroll(object sender,
        System.EventArgs e)
    {
        SolidBrush myBrush = new SolidBrush(Color.Black);
        horizLabel.Text = Convert.ToString(horizTrackBar.Value);
        paper.Clear(Color.White);
        paper.FillEllipse(myBrush, 0, 0, horizTrackBar.Value,
            vertTrackBar.Value);

    }
}

fig 2 - Screenshot of Oval Shape

Here are some points on design-time initialization: At run-time, we used the constructor to initialize some components: Note that: This program illustrates the benefits of initialising components in the form's constructor.
SELF-TEST QUESTION
In the track bar example, what are the consequences of altering the size of the track bar at design-time?
ANSWER
There are no serious consequences. The track bars alter their maximum value to the size of the picture box as the program runs.

using and namespaces

C# comes with a huge library (or collection) of classes which we can use. A very important aspect of C# programming is to make use of these, rather than write our own code. This is termed 'software reuse'.

Because there are thousands of classes, they are sub-divided into groups known as namespaces. To use a class, we must ensure that it has been imported into our program with the using keyword. However, there are two possibilities, because some of the most frequently-used namespaces are automatically imported into any Windows application. These namespaces are:

System
System.Drawing
System.Collections
System.ComponentModel
System.Windows.Forms
System.Data
There is a decision: Here is an example. When we use files in chapter 18, we will see the use of StreamReader, as in:
// declare a StreamReader object, named myStream
StreamReader myStream; 
The StreamReader class is in the System.IO namespace, so we must place the line:
using System.IO;
at the very top of our code.

There are two points to note:

To summarise, the vast library of C# classes is organised into namespaces, which can be imported into any program. When you have imported your class, you need to know how to create a new instance, and how to use its properties and methods. We shall look at these areas in a range of examples.

Members, methods, and properties

The members of a class are its properties and its methods. Properties contain values which represent the current state of an instance of a class (such as the text contained in a label), whereas methods cause an instance to do a task - such as drawing a circle.

Properties can be used in a similar manner to variables: we can place a new value in them, and access their current value. As an example, here is how the Width and Height properties of a label might be manipulated:

//set a new value in a property:
label1.Height = 30;
label1.Height = Convert.ToString(textBox1.Text);

//get current value of property:
int a;
a = label1.Height;
a = label1.Height * label1.Width;
In C# terminology, we can set a property to a new value and get the current value of a property. Each one also has a type. For example, the Width property of the label holds an integer, whereas the Text property holds a string. The names and types of properties are available from the Help system.


SELF-TEST QUESTION
Imagine a CD player - list some methods and properties that it has. Which of these are members?
ANSWER: typical methods are: move to next track, stop, start. Properties are not as universal, but many players display the current track number. They are all members.


SELF-TEST QUESTION
In geometrical terms, what do the following statements accomplish?
int a;
a = label1.Width * label1.Height;
label1.Height = label1.Width;

ANSWER: a becomes the area of the label, in pixels.
The height of the label becomes the same as the width: the label becomes square.

The Random class

Here we will look at a class (Random) whose instances need explicit declaration and initialisation. Random numbers are very useful in simulations and in games; for example we can give the game-player a different initial situation every time. Instances of the Random class provide us with a 'stream' of random numbers which we can obtain one-at-a-time via the Next method. Here is a program (Guesser) which attempts to guess your age (in a rather inefficient way) by displaying a sequence of random numbers. When you click on 'correct', the program displays the number of guesses it took. The screenshot is in figure 6.3, and here is the code:

public class Form1 : System.Windows.Forms.Form
{
    private Random ageGuesser = new Random();
    private int tries = 0;

    public Form1()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        guessLabel.Text = 
            Convert.ToString(ageGuesser.Next(5, 110));

        //
        // TODO: Add any constructor code after
        //       InitializeComponent call
        //
    }


    private void correctButton_Click(object sender,
        System.EventArgs e)
    {
        tries = tries + 1;
        MessageBox.Show("Number of tries was: " + 
            Convert.ToString(tries));
        tries = 0;
        guessLabel.Text = 
            Convert.ToString(ageGuesser.Next(5, 110));
    }

    private void button1_Click(object sender, 
        System.EventArgs e)
    {
        guessLabel.Text = 
            Convert.ToString(ageGuesser.Next(5, 110));
        tries = tries + 1;
    }
}

fig 3 - Screenshot of Guesser

To use a new class, we use the Help system to find its namespace, and we put the appropriate using at the top of our program. The Random class turns out to be in the System namespace, which is imported automatically. No additional using instructions are required.

We must then declare and initialise an instance of our class. This can be done in two ways. Firstly we can use one statement, as in

private Random ageGuesser = new Random();
Note that: The second way to declare and initialise instances is with declaration and initialisation in different areas of the program, as in
public  class Form1 : System.Windows.Forms.Form
{
    private Random ageGuesser;

...

    ageGuesser = new Random();

Whichever approach we choose, there are a number of points: Why would we need to separate declaration and initialisation? The situation often exists where we need an instance variable (as opposed to a local variable). It must be declared outside of the methods. But sometimes we cannot initialize the object until the program starts running - maybe the user enters a data value to be passed as a parameter to the constructor.

In this case, we would put the initialisation code inside a method (or perhaps the constructor of the form). We cannot put the declaration within the method, as this would declare the item as local to the method.

Let us return to the Random program. So far, we have created an instance of the Random class, named ageGuesser. We have yet to create any actual random numbers.

Once an object has been created with new, we can use its properties and methods. The documentation tells us that there are several methods which provide us with a random number, and we chose to use the method which lets us specify the range of the numbers. The method is named Next (in the sense of fetching the next random number from a sequence of numbers). In our program, we put:

guessLabel.Text = 
    Convert.ToString(ageGuesser.Next(5, 110));
We could have coded it less concisely as:
int guess;
guess = ageGuesser.Next(5, 110);
guessLabel.Text = Convert.ToString(guess);
The range of random numbers was chosen to be from 5 to 110 inclusive, as the outer limits of ages. To summarize, we declare an instance of the appropriate class (Random here), and use new to create and initialise it. These two stages can be combined, or separated; it depends on the particular program you are working on. Then we use properties and methods of the instance. The documentation provides us with details of their names and the types of data/parameters they require.


SELF-TEST QUESTION
I went to my car sales showroom and , after browsing the brochure, I ordered a specially-built 5-litre Netster in blue. When it arrived, I drove it away. Does the Car class have a constructor? Does the constructor have any parameters? Which is the instance - the photo of the car, or the real car?
ANSWER: there is a constructor, to which we pass a color. The instance is the real car which you drive away. (The photo in the catalog is really nothing more than documentation, showing you what you car will look like.)

The Timer class

So far, the classes we have used have fallen into two groups: The timer is slightly different: it is in the toolbox, but when it is dropped on a form, the IDE opens up a new Component Tray window, and puts a timer icon (a clock) on it. We can set properties at design-time, and double -clicking on the icon takes us to the timer's event-handling code. When we run the program, the timer does not appear on the form.

Here are the main timer facilities:

Here is a program (Raindrops) which simulates a sheet of paper left out in the rain. It shows randomly-sized drops falling, at random intervals. The random intervals can be changed via a track bar. Figure 6.4 shows a screenshot, and here is the code:
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
public class Form1 : System.Windows.Forms.Form
{
    private Random randomNumber = new Random();
    private Graphics paper;

    public Form1()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        paper = pictureBox1.CreateGraphics();
        gapLabel.Text = Convert.ToString(trackBar1.Value);

        //
        // TODO: Add any constructor code after
        //       InitializeComponent call
        //
    }


    private void startButton_Click(object sender, 
        System.EventArgs e)
    {
        timer1.Start();
    }

    private void stopButton_Click(object sender,
        System.EventArgs e)
    {
        timer1.Stop();
    }

    private void clearButton_Click(object sender, 
        System.EventArgs e)
    {
        paper.Clear(Color.White);
    }

    private void trackBar1_Scroll(object sender, 
        System.EventArgs e)
    {
        int timeGap = trackBar1.Value;
        gapLabel.Text = Convert.ToString(timeGap);
    }

    private void timer1_Tick(object sender,
        System.EventArgs e)
    {
        int x, y, size;
        Brush myBrush = new SolidBrush(Color.Black);

        x = randomNumber.Next(0, pictureBox1.Width);
        y = randomNumber.Next(0, pictureBox1.Height);
        size = randomNumber.Next(1, 20);
        paper.FillEllipse(myBrush, x, y, size, size);

        // set new interval for timer
        timer1.Stop();
        timer1.Interval =
            randomNumber.Next(1, trackBar1.Value);
        timer1.Start();
    }
}


fig 4 - Screenshot of Raindrops

The program works by drawing a randomly-sized filled circle at a random position at every tick. We also reset the timer interval to a random value controlled by the track bar. (This necessitates stopping and starting the timer.) Each time the track bar is moved, we display its current value in a label. The Minimum and Maximum track bar values were chosen by experimentation, and are 200 and 2000, set at design-time. We also made use of the Clear method of the picture box, which sets all the box to a specified color.


SELF-TEST QUESTION
We have a timer with an interval set to 1000, i.e. one second. Explain how we can get a display of minutes on the form.
ANSWER:
We introduce a variable, which might be named secondCount. It is incremented in the Tick method for the timer. This variable cannot be local, as it would lose its value when the method ends. Instead, it must be declared as an instance variable, at the top of the program. We use integer division by 60 to calculate the number of minutes.
public class Form1 : ...
{
    private int secondCount = 0;

    private void timer1_Tick(...)
    {
       secondCount = secondCount + 1;
       label1.Text = Convert.ToString(secondCount / 60);
    }
}

Programming principles

For many years it has been the dream of programmers to be able to build programs in the same way that hi-fi systems are built - i.e. from 'off the shelf' components, such as speakers, amplifiers, volume controls etc. The rise in object-oriented programming coupled with extensive class libraries such as that of the .Net framework bring this dream closer.

As well as making use of existing components, C# can be used to write GUI components for use by others. Such components are simple to incorporate: they are added to a project by a menu action. From then on, they appear on the toolbox like any other control, and provide event-handling method headers and properties that can be set at design time. In practical terms, it is well worth searching for an existing control which meets your requirements, rather than re-inventing the wheel by coding from scratch.

Programming pitfalls

If an instance is declared but its initialisation with new is omitted, a run-time error is produced, of type System.NullReferenceException. Run- time errors (i.e. bugs) are more problematic than compile-time errors; they are harder to find, and they are more serious, because the program's execution is halted. We guarantee that you will meet this error!

Grammar spot

New language elements

New IDE facilities

The component tray, for controls that do not have a visual representation on a form.

Summary

The C# system has a vast number of classes which you can (and ought to) use. As well as the control classes which are in the toolbox, there are classes which can be incorporated into your programs with using, and the appropriate constructor.


EXERCISES

1. Place a track bar on a form, together with two text boxes and a button. When the button is clicked, the track bar's Minimum and Maximum properties should be set from numbers entered in the text boxes. When the track bar is scrolled, display its minimum and maximum properties in message boxes.

2. Write a program which initially displays the number 1 in a label. Clicking a button should increment the value. Make use of a private variable initialised to 1, and set up the label in the constructor.

3. Write a program which produces a random number between 200 and 400 each time a button is clicked. The program should display this number, and the sum and average of all the numbers so far. As you click again and again, the average should converge on 300. If it doesn't, we would suspect the random number generator - just as we would be suspicious of a coin that came out heads 100 times in a row!

4. (a) Write a program which converts degrees Celsius to degrees Fahrenheit. The Celsius value should be entered in a text box - use integer values. Clicking a button should cause the Fahrenheit value to be displayed in a label. The conversion formula is:

f = (c * 9) / 5 + 32;
(b). modify the program so that the Celsius value is entered via a track bar, with its minimum set to 0, and its maximum set to 100.

(c) represent both the temperatures as long thin rectangles in a picture box.

5. Write a program which calculates the volume of a swimming pool, and which also displays its cross-section in a picture box. The width of the pool is fixed at 5 metres and the length is fixed at 20 metres. The program should have two track bars - one to adjust the depth of the deep end, and one to adjust the depth of the shallow end. The minimum depth of each end is 1 metre. Choose suitable values for the maximum and minimum track bar values at design time. The volume formula is:

v = averageDepth * width * length;
Figure 6.5 shows the cross-section.

fig 5 - pool cross-section

6. Write a program which displays changing minutes and seconds, representing them by two long rectangles: make the maximum width of the rectangles equal to 600 pixels to simplify the arithmetic (10 pixels for each minute and each second). Re-draw the two rectangles every second. Figure 6.6 shows a representation of 30 minutes and 15 seconds

fig 6 - time display - for 30 mins 15 secs

The program should count up in seconds with a timer, and display the total seconds, and the time in minutes and seconds. Recall that, given a total number of seconds, we can use the % operator to break it down into whole hours, and seconds remaining. In order to speed up testing the program, you should reduce the timer interval from 1000 milliseconds to say 200.

7. This question guides you through the writing of a geometry game:
(a) Write a program with two track bars which control the horizontal and vertical position of a circle of 200 pixels diameter.

(b) add a third track bar to control the diameter of the circle.

(c) What follows is a game based on the mathematical fact that a circle can be drawn through any three points. The program should display 3 points (each is a small filled circle) when a 'Next Game' button is clicked. Good initial positions are (100,100), (200,200), (200,100) but you can add a small random number to them for variety. The player has to manipulate the circle until they judge that the circle goes through each point, they then click a 'Done' button.

(d) add a timer to display how long the task takes.