Introduction
Two scenarios that were easier to code in Visual Basic Version 6 are
- Having multiple forms which do not have an MDI parent-child relationship.
- Having a dynamic array of controls which are indexed and have a single method to
handle a particular event for any member of the array.
This work illustrates ways to handle such situations in VB .NET using the context of a game.
Details
The game Memory, also known as Concentration, has pairs of images arranged randomly within
a rectangle. A turn consists of a player's clicking on two PictureBoxes. If they match
(i.e. have the same image), the player scores a point and continues. If the images do not
match, the images are covered again and the next player takes a turn. Covering the mismatched
images is done in code using a timer's Tick
event -- a delay is required so that the player
can see the images' locations and that they do not match. This version of the game
also allows for a single player who plays against the clock.
This version of the game consists of two Windows Forms.
- The first Form is the setup
Form
(see the first screen capture above). It has two Panels.
The upper Panel
allows the player to select the size of the board by selecting the number of
rows and columns using NumericUpDown
controls. The lower Panel
allows for various players'
names to be added to a ListBox
. The names can be removed as well as promoted or demoted
within the list to alter the order of players. Below the Panels is the Play Button which moves
one to the next Form.
- The second Form is the board Form (see the second screen capture above). It has two Panels.
The upper
Panel
will hold a dynamic array of UserControls that will display the players' names
and scores. The lower panel will hold a dynamic array of PictureBoxes that serve as the basic
elements of the Memory game. Below the Panels are two Buttons. The first of these allows one
to return to the setup form, and the second allows one to start a new game.
The code
Multiple Form Code
In order to move back and forth between two Forms, the specific names of the both Form
objects (as opposed to just the names of the Form
classes) must be known to the programmer.
This can be achieved by explicitly instantiating both Form
objects (with
Public
access) in
the Sub Main
of a code module and choosing the code module as the
project's Startup object. Sub Main
then calls the setup Form's
ShowDialog
method. The code module is shown below:
Module modStart
Public FrmBoard As New FrmPresidentialMemory
Public FrmSetup As New FrmGameSetup
Public Sub Main()
FrmSetup.ShowDialog()
End Sub
End Module
It is important here to use the
ShowDialog
method and not the
Show
method since the
ShowDialog
method is modal and hence does not allow the
Sub Main
to complete execution until the Form in question is closed. With
the modeless
Show
method, the Form appears briefly on the screen but is
closed when
Sub Main
completes execution.
To move from the first Form (setup) to the second Form (board), one uses the combination
Me.Hide()
FrmBoard.ShowDialog()
again using the
ShowDialog
method. However, to move from the second Form
(board) to the first Form (setup), one uses
Me.Hide()
FrmSetup.Show()
As the setup Form was previously hidden and not closed, use of the
ShowDialog
method here would cause an error. The Forms are not displayed in independent threads, closing either
Form returns one to
Sub Main
and terminates the project.
Another question that arose was which Form Event should be used to load the dynamic
control arrays. (Recall that the dynamic control arrays may be repeatedly loaded as the user can
change the number of players or size of the game board.) The Help for the Load
event states that it "occurs before a form is displayed for the first time"
(author's emphasis). However, placing a MessageBox
in the Load
event code
showed that it executed more than once as the user moved between the two forms despite the fact that
the forms were hidden and not closed. (Changes made by the user to the setup Form persist
implying that the Form remains in memory.) Because the description of Load
in
Help and its behavior in this program appeared to be inconsistent, using it was avoided. The
code that only needed to be executed once (the reading of the file with the image file names)
was moved to the constructor (Sub New
) in the Windows Form
Designer generated code after InitializeComponent()
. On the other hand,
the code that was to be executed each time the user moved from the setup form to the board form was
placed in the VisibleChanged
method as shown below.
Private Sub FrmPresidentialMemory_VisibleChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.VisibleChanged
If Me.Visible Then
SetUpBoard()
End If
End Sub
Since the
VisibleChanged
event is raised when the form is hidden and when
it is shown, the
If
statement above limits the setup code execution to when
the form is shown. Events such as
Activated
and
GotFocus
can be raised more often than desired for present purposes -- for instance, if the user is interacting
with other applications on the desktop.
Dynamic Control Array Code
Setting up the board requires generating a dynamic array of picture boxes which consists of
- Clearing the panel of any previous controls
- Instantiating an array of PictureBoxes
- Instantiating each element of the PictureBox array
- Adding the new control to the panel
- Determining the control's size
- Determining the control's location
- Determining any other (non-default) property of the control
- Assigning the control a handler (or handlers)
This is shown is the code below.
panBoard.Controls.Clear()
GameElement = New PictureBox(nRowNumber * nColumnNumber - 1) {}
For i = 0 To GameElement.GetUpperBound(0)
GameElement(i) = New PictureBox
panBoard.Controls.Add(GameElement(i))
GameElement(i).Size = New System.Drawing.Size( _
ELEMENT_WIDTH, ELEMENT_HEIGHT)
GameElement(i).Location = New System.Drawing.Point( _
GameElement(0).Width * (i Mod nColumnNumber), _
GameElement(0).Height * (i \ nColumnNumber))
GameElement(i).SizeMode = PictureBoxSizeMode.StretchImage
AddHandler GameElement(i).Click, AddressOf GameElementHandler
Next
In VB Version 6 the method handling the event of a dynamic array included an Index as
part of its signature. In VB .NET the handler has the same signature as the method for a
single control. The index is then determined by performing a search as shown in the code below.
nFirstElementIndex = Array.IndexOf(GameElement, sender)
Strictly speaking one does not need an array of PictureBoxes, one could simply add individual
controls to the panel as done in the article on Control Arrays by ManoRajan (http://www.codeproject.com/vb/net/Control_Arrays.asp) and use the panel's collection of controls.
But there are at least two conveniences provided by having a genuine array.
- In the game of Memory, a turn consists of a player turning over two PictureBoxes that must be
compared. Thus information about the first must be stored, and it is convenient simply to store
its index.
- If one uses the control collection to loop over the PictureBoxes, one must cast the
generic control as a PictureBox. Having an explicit array of a specific type eliminates the
need for casting.
The use of control collection can be even more involved if there is more than one type
of control on the Panel. In the upper Panel of the board form, there is a Label and a Textbox
associated with each player. To make managing this area easier, a
UserControl
was introduced.
The main purpose of the
UserControl
was the association of the Label and the
TextBox
, which
requires no code beyond the Designer generated code. However, the
PlayerName
property was introduced so that one did not have to access the Text property of the Label within
the UserControl, but instead needed only the UserControl's
PlayerName
property (e.g.
MyScore(i).PlayerName
replaces
MyScore(i).lblName.Text
). Similarly a Score Property and
Increment subroutine were added. Furthermore, the
LabelTextbox
UserControl
was designed
specifically for this project so that the default size could be used when loading them
onto the panel.
Other Minor Matters
- The game Memory requires an even number of game elements. This condition was accomplished by
insisting that the number of columns was even. The Value property of
nudColumn
(a NumericUpDown
control) was set to 4 and the Increment property was set to 2. These settings impose
the condition without the need for any additional code.
- Most of the resizing/repositioning was handled using the anchor properties of the controls,
but some code was still required as the number of players and number of game elements are unknown,
and thus the proportions of the board are not known until runtime.
- One must guarantee that a player clicks two and only two game elements in a turn. This was
accomplished by setting the selected PictureBoxes' Enable property to False and also setting
the panel's Enable property to False for mismatches. The Enable properties must be returned to
True after the mismatched images are turned over.
- There had to be logic for stopping the Timer when the game was finished or when the user
disrupted a game by returning to the setup Form or by clicking the Play Again Button.
- If no players are in the setup Form's
ListBox
when the Play Button is clicked, a single
player named "Player 1" is added.
- If there is only one player, then a
TextBox
is made visible since in this case the user is
playing against the clock. If there are multiple players, then this TextBox
is made invisible.
- If there are multiple players, then the current player's name is underlined in the score
display area. When a mismatch occurs, the player index changes as does the font of two labels.
This action is accomplished by the following code
MyScore(nPlayerIndex).lblName.Font = New Font( _
New FontFamily(MyScore(0).lblName.Font.Name), _
MyScore(0).lblName.Font.Size, FontStyle.Bold)
nPlayerIndex = (nPlayerIndex + 1) Mod MyScore.GetLength(0)
MyScore(nPlayerIndex).lblName.Font = New Font( _
New FontFamily(MyScore(0).lblName.Font.Name), _
MyScore(0).lblName.Font.Size, FontStyle.Underline _
Or FontStyle.Bold)
Acknowledgements
I would like acknowledge useful discussions with S. Longo and S. Wiley.
History
- Original submission May 20, 2004.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.