Private
;
New
;
Random
class;
TrackBar
and Timer
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 VB uses 'control' for items which can be manipulated on a form.
Dim
to declare local variables
within methods. But local variables alone are insufficient to tackle most
problems.
Here we introduce a simple program (Car Park1) 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.
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.
The screenshot is shown figure 6.1, and here is the code:
Public Class Form1
Inherits System.Windows.Forms.Form
Private carCount As Integer = 0
Private Sub EnterButton_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles EnterButton.Click
carCount = carCount + 1
CountLabel.Text = CStr(carCount)
End Sub
Private Sub LeaveButton_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles LeaveButton.Click
carCount = carCount - 1
CountLabel.Text = CStr(carCount)
End Sub
End Class
fig 6.1 screenshot of Car Park1 program
There are a number of points to note:
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.
carCount
is declared outside the methods, and
inside the class Form1
. It can be used by any method in Form1
.
Private
, meaning that any other classes we might
have cannot use it. The variable is encapsulated or sealed up inside
Form1
i.e. it is for the use of the methods and properties of Form1
only.
Dim
has not been used. We only use it for local variables.
carCount
is an example of an instance variable. It belongs to an
instance of a class, rather than to one method.
CarCount
is said to have module scope. (A class is a type of module
in VB). The scope of an item is the area of the program in which it can be
used. The other type of scope we have seen is local scope used with local
variables.
Private
means that other classes (outside of our Form1
class)
cannot access the item. This is the preferred style for instance variables.
Public Class Form1 Inherits System.Windows.Forms.Form Private n As Integer = 8 ... Private Sub MyMethod() Dim n As Integer n = 3 'which n? End Sub End ClassAlthough both variables are accessible (in scope) within
MyMethod
, the rule
is that the local variable is chosen. The instance variable (module-level)
n
remains set to 8.
Form1
class, what are the consequences of deleting the Dim
statement?
Class-level 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.
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 VB provides a special area of the program for such once-only
initialisation. Look at the second version: Car Park2.
Public Class Form1
Inherits System.Windows.Forms.Form
Private carCount As Integer = 0
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
CountLabel.Text = CStr(carCount)
End Sub
' ... followed by code to initialise controls (not shown)
Private Sub LeaveButton_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles LeaveButton.Click
carCount = carCount - 1
CountLabel.Text = CStr(carCount)
End Sub
Private Sub EnterButton_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles EnterButton.Click
carCount = carCount + 1
CountLabel.Text = CStr(carCount)
End Sub
End Class
To create this code, we need to use the editor to open up an area of code
that has not been explored before. Use your Car Park1 example, and locate
the line:
Windows Form Designer generated code
Click on the small +
to its left to open up the code. You will see a large
amount of code, but the good news is that most of it must not be altered - it
has been produced by the VB system, and in fact, you should not play
around with the code!
There is a method near the top headed:
Public Sub New()
When the VB system runs your program, it creates a new instance of Form1
,
and it calls its New
method first. This method is known as a
constructor - it does some initial 'building' of the object. First,
it creates your form and its controls by calling the InitializeComponent
method. After the controls are created, you can write code to modify their
initial values.
In this improved version of the program, we have no need to set the Text
of
the label at design time - instead, we insert code to do it towards the end
of New
, and the value is guaranteed to be the same as carCount
. The
comment:
'Add any initialization...
in the code shows you where it is safe to put your initialization. Do not
put it anywhere else.
After your amendments, you can run the program. The area of code you
expanded can now be collapsed if you wish, by clicking the - at the left
of the line:
#REGION: "Windows Form Designer generated code "
Public Sub New() ...etc Label1.Text = "38" InitializeComponent() End Sub
InitializeComponent
). This produces a run-time error.
In the above example, we modified the New
constructor, but did not call it
ourselves. Later in this chapter, we will create new objects by explicitly
calling the constructor of their class.
TrackBar
class
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.
ure 6.2 shows a screenshot, and here is the code:
Public Class Form1
Inherits System.Windows.Forms.Form
Private paper As Graphics
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
paper = PictureBox1.CreateGraphics()
VertTrackBar.Minimum = 0
VertTrackBar.Maximum = PictureBox1.Height
VertLabel.Text = CStr(VertTrackBar.Value)
HorizTrackBar.Minimum = 0
HorizTrackBar.Maximum = PictureBox1.Width
HorizLabel.Text = CStr(HorizTrackBar.Value)
End Sub
Private Sub VertTrackBar_Scroll( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles VertTrackBar.Scroll
Dim myBrush As SolidBrush = New SolidBrush(Color.Black)
VertLabel.Text = CStr(VertTrackBar.Value)
paper.Clear(Color.White)
paper.FillEllipse(myBrush, 0, 0, HorizTrackBar.Value, _
VertTrackBar.Value)
End Sub
Private Sub HorizTrackBar_Scroll( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles HorizTrackBar.Scroll
Dim myBrush As SolidBrush = New SolidBrush(Color.Black)
HorizLabel.Text = CStr(HorizTrackBar.Value)
paper.Clear(Color.White)
paper.FillEllipse(myBrush, 0, 0, HorizTrackBar.Value, _
VertTrackBar.Value)
End Sub
End Class
( fig 6.2 screenshot of Oval Shape program)
Here are some points on design-time initialization:
HorizTrackBar
and VertTrackBar
.
Orientation
property to Vertical.
HorizLabel
and VertLabel
.
100
, 100
.
Minimum
property of the track bars to 0
, and the Maximum
properties to the height and width of the picture box.
Text
property of HorizLabel
- which displays the
current track bar value - is set to HorizTrackBar
.Value
. VertTrackbar
is initialized in a similar way.
Scroll
) is called when we move it to a new
position.
Value
property gives us the current setting. We use this
to control the size of an imaginary rectangle enclosing the oval.
Imports
and namespaces
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. 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.Data
System.Drawing
System.Windows.Forms
System.XML
There is a decision:
Imports
at the top of our program.
StreamReader
, as in:
Dim myStream As StreamReaderThe
StreamReader
class is in the System
.IO
namespace, so we must place
the line:
Imports System.IOat the very top of our code.
There are two points to note:
System
namespace does not automatically import every namespace which starts with
System
. Every namespace must be imported explicitly.
Imports
merely provides us with a shorthand. For example, we
could use the StreamReader
class without importing, but we would have to
put:
Dim myStream As System.IO.StreamReader
To summarise, the vast library of VB 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 classes.
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 = CStr(TextBox1.Text)
'get current value of property:
Dim a As Integer
a = Label1.Height
a = label1.Height * Label1.Width
In VB 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.
Dim a As Integer a = Label1.Width * Label1.Height Label1.Height = Label1.Width
a
becomes the area of the label, in pixels.
Random
) which needs 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 Inherits System.Windows.Forms.Form Private ageGuesser As Random = New Random() Private tries As Integer = 0 Private Sub CorrectButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles CorrectButton.Click tries = tries + 1 MessageBox.Show("Number of tries was: " & CStr(tries)) tries = 0 GuessLabel.Text = CStr(ageGuesser.Next(5, 110)) End Sub Private Sub WrongButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles WrongButton.Click GuessLabel.Text = CStr(ageGuesser.Next(5, 110)) tries = tries + 1 End Sub End Class
( Fig 6.3 screenshot of Guesser program)To use a new class, we use the Help system to find its namespace, and we put the appropriate
Imports
at the top of our program. The Random
class
turns out to be in the System
namespace, which is imported automatically.
No imports are needed in this program.
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 ageGuesser As Random = New Random()
Note that:
ageGuesser
for our instance.
Random
class, which always has
the same name as the class itself. The constructor is basically a method.
New
precedes the use of the constructor. New
creates a new
instance of a class in RAM.
Random
has two constructors, and the one with no parameters
is suitable here.
Private ageGuesser As Random...and
... = New Random()The first part declares
ageGuesser
as a variable of class Random
, but it
does not yet have a concrete instance (containing methods and property
values) associated with it. The second part calls the constructor of the
Random
class to complete the task of declaring and initialisation.
Public Class Form1 Inherits System.Windows.Forms.Form Private ageGuesser As Random .... ageGuesser = New Random()Whichever approach we choose, there are a number of points:
Random
.
AgeGuesser
has module
scope - it can be used by any method of the Form1
class, rather than being
local to a method.
AgeGuesser
is private. It cannot be used by other classes outside our
Form1
class. Normally we make all such variables private.
In this case, we would put the initialisation code inside a method (or perhaps the constructor). 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 = CStr(ageGuesser.Next(5, 110))
We could have coded it less concisely as:
Dim guess As Integer
guess = ageGuesser.Next(5, 110)
GuessLabel.Text = CStr(guess)
The range of random numbers was chosen to be from 5 to 110 inclusive, as the
outer limits of ages.
To summarise, 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.
Car
class have a constructor? Does the constructor have
any parameters? Which is the instance - the photo of the car, or the real
car?
Random
.
They do not appear at design time, and we have to explicitly code up a call
to its constructor. Properties can only be set at run-time.
Here are the main timer facilities:
Tick
method.
Interval
property can be set to an integer value, representing the time
between ticks in milliseconds.
Start
and Stop
methods.
Public Class Form1 Inherits System.Windows.Forms.Form Private randomNumber As Random = New Random() Private paper As Graphics Public Sub New() MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call paper = PictureBox1.CreateGraphics() GapLabel.Text = CStr(TrackBar1.Value) End Sub Private Sub StartButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles StartButton.Click Timer1.Start() End Sub Private Sub StopButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles StopButton.Click Timer1.Stop() End Sub Private Sub Timer1_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Timer1.Tick Dim x, y, size As Integer Dim myBrush As Brush = 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() End Sub Private Sub TrackBar1_Scroll( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles TrackBar1.Scroll Dim timeGap As Integer = TrackBar1.Value GapLabel.Text = CStr(timeGap) End Sub Private Sub ClearButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ClearButton.Click paper.Clear(Color.White) End Sub End Class
( fig 6.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.
1000
, i.e. one second. Explain how
we can get a display of minutes on the form.
minuteCount
. 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. To display the
minute value, we use \
to convert the seconds into minutes.
Public Class Form1 Inherits ... Private minuteCount As Integer = 0 Private Sub Timer1_Tick(etc...) minuteCount = minuteCount + 1 Label1.Text = CStr(minuteCount \ 60) End Sub End Class
Such controls 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.
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!
Private
, as in:
Private yourVariable As Integer Private myVariable As Random = New Random()
New
for initialisation
Imports
for namespaces
TrackBar
, Random
, and Timer
classes
Imports
and the
appropriate constructor.
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. 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.
(*** insert figure 6.5 near here
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
(*** insert fig 6.6 here
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 Mod
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.