| Now let's make a serious program that could be
genuinely useful in a school context - it could even help you get better grades
in different subjects! We will make a multiple choice testing program that
automatically grades the user's responses. VB can handle multiple choice
responses very
easily, but would be very bad at grading essays. In this project you will learn:
 | more about control arrays |
 | how to declare constants |
 | more about arrays, include multi-dimensional arrays and dynamic arrays |
This project is in two parts - follow the link above to move to the second
part.
Make a new project and set up the form with 2 labels, 2 command buttons and 4
option buttons like this:

Set the font property of all the controls to size 10, except that the top three
controls can be set to size 14. Note that the form's BorderStyle property has
been set to 'fixed single' so that the user cannot resize the form. Set names as
follows:
 | small label: lblQno |
 | command buttons: cmdPrev and cmdNext |
 | large label: lblQuestion |
 | option buttons: optAns |
Giving all the option buttons the same name turns them into a 'control array'
which has the advantage that they can share the same code. VB can still
distinguish between the members of the control array because each member is
given a unique index value (0 to 3 in this case). Thus the top option button is
optAns(0) and so on. An easy way to create the control array is to make the
first option button (giving it the name optAns), copy it then paste it several times, answering 'yes' when
VB asks you whether you wish to create a control array. It will be easier for
us to write the code if the index numbers of the option buttons run from 1 to 4
rather than 0 to 3 so change the index property values, starting by changing the
index property of the bottom option button from 3 to 4 (why can't you start at
the top?) We could put each
question on a separate form (it's OK to have several forms in the same project)
but this would probably be a bad idea for we would have to deal with several
forms, many controls etc. and if we decide to change the layout we will have to
modify each form individually. So we will use a single form and use the
command buttons to switch between different questions, displaying different
question/answer sets as appropriate. We'll keep the questions and answers in
arrays in the program's code (remember that an array is just a variable
that can contain several values instead of just one). Now for the code - let's
start with the command buttons that move us from question to question. Let's
make a variable qno to keep track of which question we are looking at.
Since the total number of questions will not change while the program is running,
we will create a 'constant' called numq to record the total number of questions in the test.
Notice how the constant is set up in the code below, and notice that it is not
necessary to declare a constant with a Dim statement. Let's also create an array to store the questions, called question(). The empty
parentheses in this declaration create a 'dynamic array' - one that can
contain as many elements as needed. We will need to use the Redim
statement later on to tell the computer how many elements will actually be
present (this number will equal numq). We'll also need a dynamic array Answer()
to store all the answers for the various questions, an array called correct()
to store the number of the answer which we know to be correct for each question and an array
called response()
to store the choices made by the person being tested. So far our code looks like
this (it belongs in the General Declarations area, above all other code and
outside every procedure): Dim qno, question(), ans(), correct(), response()
Const numq = 4 ' total number of questions
Let's think about the code for the cmdNext button. When we click it,
we want to see the question and answers corresponding to the next question, so
the number of the current question needs to be increased by one and then the
corresponding questions and answers need to be displayed, so we could use this
code: Private Sub cmdNext_Click()
qno = qno + 1
SetQuestion
End Sub
If you were to run the program now, you would get an error message 'Sub or
Function not defined' for VB does not have a SetQuestion statement
built in, and therefore this line generates a SetQuestion event that has
no procedure yet to handle it. Soon we will make the SetQuestion procedure. Before
we do the SetQuestion procedure, let's do the cmdPrev code - it's just
the same as the cmdNext code except that we will subtract one from 'qno'
rather than adding to it: Private Sub cmdPrev_Click()
qno = qno - 1
SetQuestion
End Sub
Let's try to build up this program one small step at a time - let's write a
SetQuestion procedure that doesn't actually set the question yet but simply sets
the caption of the lblQno label to show the current question number: Private Sub SetQuestion()
lblQno.Caption = qno
End Sub
Run the program and notice two things:
 | the question number has no limits - it can go very high, or become zero or
negative. This is to be expected at this stage. |
 | if you click the cmdNext button first, the label's caption remains at 1
for the first click |
The second problem occurs because when the qno variable was created with Dim
qno, it was given the value of zero initially, so clicking the
cmdNext button adds 1 to the zero and displays the answer, 1, in the label's
caption, where a 1 was already on display. Let's fix the second problem by
telling the program to set qno to equal 1 when the program starts. When any
program starts, the first thing that happens is that the form is loaded,
generating a Form_Load event. Therefore code put in the Form_Load procedure will
be executed before all other code:
Private Sub Form_Load()
qno = 1
End Sub
With the new code in place, the second problem noted above has been solved.
Now let's add to the Form_Load procedure some code to 'dimension' (set the
size of) the arrays as the program starts:
Private Sub Form_Load()
ReDim question(numq), ans(numq, 4), correct(numq), response(numq)
FillArrays
qno = 1
SetQuestion
End Sub The ReDim
statement sets the size of the dynamic arrays created earlier. Actually the
arrays will have space for 5 values, not 4, for the arrays begin with element
zero, not one. Thus ReDim question(numq), for example, creates an array with values
question(0) to question(4) - that's 5 values in all. We won't be
using the first element (element zero) of each array. The following line calls
the FillArrays procedure which we have not yet written but which will fill the
arrays with the questions and answers stored elsewhere in the code. The
SetQuestion line is included so that the first question (qno=1) is displayed as
soon as the program starts.
There's something special going on in the above procedure, in the ReDim line
- it's the way the ans array has been dimensioned (sized). The ans
array has to contain four different answers for each question, so the
array has been created as a two-dimensional array with space for several
answer values for each question. We have actually created space for 5 answers
for each question, with index values from 0 to 4, but we will only use the index
numbers 1 to 4 - we won't be using the value with an index number of zero.
Let's address the other problem mentioned above: the command buttons allow us
to increase or decrease the question number without limits, so we can have
negative question numbers, for example. The neatest way to limit the range of
numbers that can be reached with the command buttons is to disable each one when
it would otherwise allow us to access an invalid question number. In other
words, the cmdPrev button should be disabled whenever question one is displayed,
and the cmdNext button should be disabled whenever the last question (with qno
equal to numq) is displayed. We could code this with an IF structure, but
there may be a better way:
Private Sub SetQuestion()
cmdPrev.Enabled = (qno <> 1)
cmdNext.Enabled = (qno <> numq)
lblqno.Caption = qno
End Sub
Look at the first of the highlighted lines above. The '<>' means 'is
not equal to, so the expression in parentheses will be true if the
question number is not equal to 1, and false if the question number is 1.
Thus the complete line enables the cmdPrev button when the question number is
not 1 (by setting the enabled property to true), and disables it
when the question number is 1 (by setting the enabled property to false).
If you try the program, you will notice that the code works fine, but there is
still one small problem - the cmdPrev button is enabled when the program starts,
so it is possible to move to question number zero. Fix this problem by setting
the cmdPrev enabled property to false initially, using the properties window.
Now let's work on the FillArrays procedure that will be called from
the Form_Load procedure above. We have already created several arrays with a Dim
statement and dimensioned (sized) them with a ReDim statement in the Form_Load
procedure, but all the arrays we have created are empty. We now need to fill the
question() array with questions, the answer() array with answers,
and so on. I hope you will create some original questions of your own, to
replace these:
Private Sub FillArrays()
question(1) = "What is the most popular programming language in the world?"
ans(1, 1) = "C++"
ans(1, 2) = "Pascal"
ans(1, 3) = "Visual Basic"
ans(1, 4) = "Java"
correct(1) = 3 ' the correct answer, Visual Basic,
' has the index value 3 above
question(2) = "Which VB control has no caption property?"
ans(2, 1) = "Textbox"
ans(2, 2) = "Commandbutton"
ans(2, 3) = "Label"
ans(2, 4) = "PictureBox"
correct(2) = 1 ' the correct answer to the second
' question is answer 1, "textbox"
question(3) = "What would this code display? MsgBox(2 + 2 / 2 + 2)"
ans(3, 1) = "1"
ans(3, 2) = "4.5"
ans(3, 3) = "5"
ans(3, 4) = "An error message"
correct(3) = 3 ' VB will do the division first, then 2+1+2
question(4) = "When a VB program is run, which event happens first?"
ans(4, 1) = "Dim" ' not an event
ans(4, 2) = "Form_Load"
ans(4, 3) = "cmd1_Click"
ans(4, 4) = "Form_Run"
correct(4) = 2
End Sub
Now that we have set up the questions and answers, we can extend the
SetQuestion code so that the appropriate question and answers are displayed each
time we move to a new question:
Private Sub SetQuestion()
cmdPrev.Enabled = (qno <> 1)
cmdNext.Enabled = (qno <> numq)
lblqno.Caption = qno
lblQuestion.Caption = question(qno)
For n = 1 To 4
optAns(n).Caption = ans(qno, n)
Next
End Sub We didn't really have to use a For...Next loop here but it
makes the code a little neater.. and it's good practice!
Now the program allows us to view each of the questions and its corresponding
answers. However, if we choose any answer we notice that the computer 'forgets'
our choice in the time it takes to move to a different question and then return.
We have no code to record the user's choices, nor to set the option buttons when
we return to a question that has already been answered.
The neat thing about a control array is that all the members of the array
share the same code. Let's write the code for our option buttons which form a
control array since they all have the same name (optAns):
Private Sub optAns_Click(Index As Integer)
response(qno) = Index
End Sub That was short! Note that when we click any option button VB copies its
unique index property into a variable called index which we can
then use in the code. For example, if we click the top option button, OptAns(1),
then the index variable is set equal to 1. The middle line of code copies the
value of the index variable into the response array, so that the user's
selection (1, 2, 3 or 4) is stored even when we move to a different question.
An annoying aspect of our program is that each time we move to an unanswered
question, the first answer is already selected - this could be confusing so
let's fix it with the following modification:
Private Sub SetQuestion()
cmdPrev.Enabled = (qno <> 1)
cmdNext.Enabled = (qno <> numq)
lblqno.Caption = qno
lblQuestion.Caption = question(qno)
For n = 1 To 4
optAns(n).Caption = ans(qno, n)
optAns(n).Value = False
Next
If response(qno) <> "" Then
optAns(response(qno)).Value = True
End If
End Sub When the response array is created it contains empty values
rather than zeros. The extra code above first sets the value property of each
option button to false then, after checking that the corresponding response
value is not empty, sets the value property of
one of the option buttons to true, according to the number stored in the response
array, making that option button appear selected. Ooof!
As a final step, we should include an additional command button that allows
the user to indicate that he or she has finished working on the test and is
ready to be GRADED. Add a command button with the caption "End Test"
and the name "cmdEndTest" and then, for ,
work out code for this button that will use a messagebox to display the user's
score or, even better, the user's percentage. Hint: the code should
probably use a For...Next loop that moves through all the questions comparing
each of the user's choices with the correct answer. Each time a match is found
the value of a new variable called score could be incremented (increased)
by 1. HAPPY CODING! If, after several hours of blood, sweat and tears you are
unable to get this code working then you can click HERE
for help.
The user of your program might want to review the questions after grading, to
see which questions were answered wrong. For extra extra credit, add the
code to make this possible.
For extra extra extra credit, how about adding sound effects. Do the short
project called called Sound to see how sound effects can be added. There could be a
sound such as THIS ONE each time we move from one question to another, or
applause if the
percentage of correct answers is greater than 80, for example... If
left-clicking these links does not give you a file-save option then try
right-clicking them.
For extra extra extra extra credit, you could..
 | add a bunch more questions |
 | add menus to replace some of the command buttons. You could have,
for example, a File menu containing a Grade Test item as well
as an Exit item, and a Help menu with an About item.
See the Menus project to learn how to do menus. |
 | improve the program in ways that only YOU have
thought of |
 | modify the program so that some questions could show an appropriate picture
or play sounds that are appropriate only for those specific questions.
For help with this please see the second part of this project... |
Now click HERE to move to learn how to add
pictures, sounds and even videos to your project. |