Hidden Image Game
- Posted: Oct 31, 2006 at 3:22 AM
- 408 Views
- 1 Comment
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
In this article, we'll create a Hidden Image game. We'll demostrate few features available in Windows Forms in Microsoft .NET 2.0 for creating a consistent and user-friendly interface.
Recently I was challenged to quickly write an application for playing the Hidden Image game, popularized here in the Czech Republic by a certain TV show. Luckily I had my notebook with Visual Basic Express at hand. This example shows how easy it is to write software in Visual Basic Express, and how to almost effortlessly create a well-behaved Windows application. It also demonstrates how to use certain new features available in Windows Forms in Microsoft .NET 2.0 for creating a consistent and user-friendly interface using the following techniques:
Hidden Image is not a computer game in the pure sense — it's more like a puzzle. Here's where a computer comes in handy, because it's simpler to show an image on the wall using a video projector than with cut paper cards (especially for us computer geeks, of course).)
To play the Hidden Image game, you need two opposite sides (players or teams) and one game master. The game master chooses an image. When displayed, the image is covered by numbered squares. Each player takes a turn removing one square, which reveals a portion of the image, and then attempts to guess what the image is. If the guess is correct, the player wins the game. If it's an incorrect guess, the other side takes a turn.
For this application I wanted to allow the game master to select the picture, set up certain parameters (like the number and color of the squares), and then display the covered picture. When clicking on any square, the square would disappear and reveal a part of the picture underneath. Pressing the ENTER key would reveal the entire picture, and pressing the ESC key would return you to the setup screen.
Certain applications are very visually appealing. They have fantastic colors, fantastic forms shapes, and rich graphics. They are beautiful. Unfortunately, in most cases they're not very useful. Unless your application is a role-playing adventure game, it is not a good idea to let your user wonder if the gimmick on the screen is actually a button or only a figment of the designer's imagination.
One key reason for the success of the Windows operating system is that the elements are generally the same in all the applications. You'll quickly realize that this is a menu, this is a button, and that this little square is actually a check box. Your applications should likewise follow common rules rather than trying to reinvent the wheel.
An application should also behave well on differing system configurations and not just on the machine that created it. Certain users — for example, me — have the taskbar on top of the screen instead of at the bottom. Some users like very high resolutions (for example, 1600 x 1200), while others prefer lower (and easier to read) resolutions (for example, 1024 x 768). Therefore it is a good idea to plan on making your Windows Forms resizable for the user's viewing comfort. And when the user resizes or moves Window Forms, the application should remember these changes and place the form in the same position the next time the user runs the application. It is also good to keep in mind that the computer may have different user accounts, and that user changes and preferences need to be stored per user.
This is the art of designing a decent user interface that creates the best user experience. Users, even inexperienced ones, should be able to use your application without unnecessary confusion.
Sound tricky? Not really. Microsoft .NET 2.0 has lots of features that allow you to painlessly address all these issues. You just need to know about them.
Let's start Visual Basic Express and create a new Windows Application project (or simply download and open the finished project attached to this article). After creating the application, you'll see an empty form ready for you to place controls on it.
Below is what the finished application setup form should look like:

Begin with placing the group boxes, buttons, and text boxes. You'll notice that if you place a control near to another control or form border, little blue lines appear from the control sides. These lines help you to place the controls the same distance from one another and from the form boundaries. This new feature in Visual Studio 2005 makes aligning controls very easy.
When designing forms, you can also display the Layout toolbar. Right-click any toolbar and, in the resulting pop-up menu, click "Layout." A new toolbar will appear, with buttons to align the controls in relation to one another, make similar controls the same size, center them, and so on. Just play with the buttons and discover what they do.
At this time, there are a few regular controls on the form:
For entering the number of rows and columns, you could use text boxes as well #151;. but it's not the best possible solution. You need to be sure that the entered value is a whole number within a certain range, for example between 2 and 20. So it's a good idea to use the NumericUpDown control instead of a text box. In the control's Properties window you can specify minimum and maximum values.
For setting the background and the foreground colors for the displayed number on the overlay squares, you can use two standard Label controls with the same border and size properties as those for the text box above them. What matters is their BackgroundColor property. Also, near each of them is a button that allows a user to choose the color.
You can use a similar approach to make it possible for the user to select the font used for the numbers on the overlay squares. Font specification is complicated. For example, you'll need to store font name, size, and style (bold, italic). We'll sort this out later as well, but for now, just place the controls and set their visual values.
Let's go ahead and run the application. It should display a nice window. You can click the buttons, write text in text boxes, and so on. Nothing will actually happen, but you can play.
Now try to resize window. The window resizes successfully, but the controls do not: If you make the form larger, the controls will remain in the upper-left corner; if you make the form smaller, they will be hidden. You want the controls to resize nicely with the form.
Close the application and display the properties for any control on the form. Notice the Anchor property. When you click it, you can select any combination of Top, Bottom, Left, and Right values.

These values specify how the control should behave when its container control — in this case the Form — resizes. By default, Anchor is set to Top and Left. This means that the control will retain its distance from the top and left borders of the Window Form. For example, it would be better if the "Start" and "Close" buttons remained at the left and bottom borders of the form. Select more than two anchors (for example, Top, Left and Right), and the magic continues: When you resize the form, the control will stick to each selected Anchor and will resize as well. Play with the Anchor property for all the controls. Some controls (such as buttons) move to adjust to the change in size of the container control, while others (such as group boxes) will actually resize proportionate to the change in size of the container.
However, despite these built-in resizing features in Windows Forms and controls, it is usually a good idea to place some limits on the user's freedom to resize the form. If the form size becomes too small, some controls will disappear. To prevent this from happening, a Windows Form has MinimumSize and MaximumSize properties. In the form designer window, try resizing the form to the smallest possible size where all the controls are still fully displayed, readable, and usable. Then in the Properties window, copy the value in the form's Size property into the MinimumSize field. Now, when you run the application and resize the window, the controls resize nicely without allowing the window to become too small to be useful.
Now it's time to write some code and make the application actually do something. First, let's make some of the buttons go live. The "Browse" and "Change" buttons should use Windows common dialog boxes for file browsing and the selection of colors and fonts. There are two reasons for this. First, it's what users expect. Virtually all professionally written Windows programs use this approach, and anything else would seem strange. Second, it's actually the simplest way. Every other solution is harder to implement. It's one of those times when being lazy actually helps.
Let's start with file selection. Grab the OpenFileDialog control (in the Dialogs section of Toolbox) and place it somewhere on the form. It will immediately disappear from wherever you placed it and display in the grey area at the bottom part of the edit pane. This happens because the dialog control does not have any visual implementation on the form itself. It's not like a button or a text box, which are directly visible to users.
In the Properties window for this control, there are settings for a lot of things, like the title of the dialog, conditions the file must meet (for example, CheckFileExists), and so on. For our purposes, there is an interesting property called Filter. Values in this property specify what displays in the Files of type box.

For the Hidden Image application, the values would be Images|*.jpg;*.bmp;*.gif;*.png;*.tif;*.tiff|All files|*.*. By default, we would allow users to choose from certain types of image files. As a last resort, a user can view all the files, regardless of extension. The file type extensions are separated by a pipe character.
Now, let's write a few lines of code that will display the Open File common dialog (pointing to the currently selected file), so that if the user clicks "Open," the name of the currently selected file appears in the text box. Double-click the "Browse" button to open the code editing pane and write the following code as handler of the Click event:
C#
private void ButtonBrowse_Click(System.Object sender, System.EventArgs e)
{
// Display file browser dialog
this.OpenFileDialogImage.FileName = this.TextBoxImageFilePath.Text;
if (this.OpenFileDialogImage.ShowDialog(this) !=
System.Windows.Forms.DialogResult.OK)
{
return;
}
this.TextBoxImageFilePath.Text = this.OpenFileDialogImage.FileName;
}
Private Sub ButtonBrowse_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)_
Handles ButtonBrowse.Click
' Display file browser dialog
Me.OpenFileDialogImage.FileName = Me.Text boxImageFilePath.Text
If Me.OpenFileDialogImage.ShowDialog(Me) <> Windows.Forms.DialogResult.OK Then Return
Me.Text boxImageFilePath.Text = Me.OpenFileDialogImage.FileName
End Sub
The approach for the Color common dialog box and color selection is very similar. The code for the Font common dialog box will be a little different so we'll come back to it later.
One of the nice features in Windows is auto-completion of text box entries. That's when a user starts to type an entry (for example, a path to a file) and the text box offers possible entries based on previously entered values. To enable this feature in the Hidden Image application, you need to enable a couple of properties for the file name text box on the setup form. Set the AutoCompleteMode property to SuggestAppend and the AutoCompleteSource property to FileSystem.
Don't forget that people can control your application using the keyboard as well as the mouse. If an application is well designed, advanced users usually find using the keyboard quicker and easier.
Three things help to make using the keyboard easier and more productive:
Let's look at default buttons first. In the case of most dialogs, there are buttons like "OK" and "Cancel" that function like the ENTER and ESC keys. You can enable this function using the dialog form's AcceptButton and CancelButton properties. In this case, set them to the "Start" and "Close" buttons. When you press ENTER or ESC, the application will behave as if the corresponding button has been clicked. The functionality of the "Start" button can be added later, but the "Close" button is easy to enable right away — just close the form:
C#
private void ButtonClose_Click(System.Object sender, System.EventArgs e)
{
// Close window and quit application
this.Close();
}
Private Sub ButtonClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)_
Handles ButtonClose.Click
' Close window and quit application
Me.Close()
End Sub
Next is the tab order. In a standard application, pressing the TAB key would move the focus to the next control in sequence. The order in which the controls are selected is specified by their TabIndex property. For the best user experience, this should follow the order in which the fields are usually filled in, which is usually the same as the order in which the controls line up on the form.
By default, the tab order is assigned as the controls are placed on the form. The first control is 0, the next control is 1, and so on. In most cases, that's not what we want. Of course you can set the TabIndex values by hand, but Visual Basic offers a clever alternative. On the menu, click "View" and then "Tab Order." You'll notice there are now small squares with numbers in the corners of all the controls:
(Click image to zoom)
Click the controls in the same order you want the tab order to follow. After you're finished, click "View" and then "Tab Order" again to hide the numbers.
You'll need to assign TabIndex values even to controls that are not directly selectable, such as Labels. Why? Because of the keyboard accelerators.
You may notice that some letters on the running form are underlined (you may need to press and hold the ALT key to see this, depending on your system configuration). If you press ALT and the underlined letter, you'll shift the focus to the appropriate control. Or, if the control itself (for example, a label) cannot be selected, the focus moves to the next control in the tab order. So, if you press ALT+I, the text box for entering the file name is selected.
You can choose which letter is underlined by placing an ampersand before it. If you name the label "&Image File," the letter "I" will be underlined and used as a keyboard accelerator. This applies to the first letter of most labels. If the letter has already been assigned to another label, the next letter is underlined. So we have "F&ont" here, because "F" is already used in "&Foreground color."
Since the dawn of personal computing, there has been a need to store configuration settings. You can store the settings in many ways: as text files, in the registry, and so on. Microsoft .NET uses XML files for this. And, best of all, you do not need to worry about reading and converting values; it will be done for you automatically.
Some settings should be stored for all users of the application. This is called "having Application scope." These settings are usually configured during installation or initial setup by an administrator and should not be modified by regular users. They're stored in an application configuration file that is present in the same directory as the application's EXE file; it has the same name with a ".config" extension added. In this case, there are no such settings.
All our settings are "per user." This means that you need to store them someplace the user can read and write (remember, your user must not have administrator privileges). The ideal location for this is the user profile, usually in C:\Documents and Settings\<User Name>\Local Settings\Application Data\<Vendor Name>\<Product Name>. Actually, .NET is kind enough to take care of creating and managing the file. You just need to give it some information about your application.
Go to the Solution Explorer window and double-click "My Project." A new window will appear. On the Application tab, click the "Assembly Information" button and fill in the information about yourself and the application:

The values you enter here will be used for specifying the location of the configuration files (among other things).
While you're in "My Project", go to the "Settings" tab. Here you can define the configuration properties of your application. Choose a settings name (for example ImageFilePath), type (String), scope (User), and default value (C:\WINDOWS\Web\Wallpaper\Bliss.bmp). Note that you can use pretty complex types for your settings, such as color (System.Drawing.Color) or font specification (System.Drawing.Font).
![]()
(Click image to zoom)
Visual Basic will automatically create a proxy class that takes care of reading and writing those values and you'll be able to use them directly with the correct type, for example as My.Settings.OverlayFont.
As you see, this is another example of laziness being a good thing (I just love being a programmer!). Not only are you able to read and write application settings without having to bother about configuration files, but the files are also placed correctly. Your application won't crash if a user does not have write privileges for the file (the user can always save to his profile). Tools like Windows XP File and Settings Transfer Wizard are an added bonus.
Now it's time to use — read and write — the settings. Basically there are two possible ways to do this. One is to work with them programmatically, using the auto generated My.Settings class mentioned before. We can then write the code that will make the button that selects font family, size, etc. go "live."
As before, place the FontDialog control on the form and write the following code to the Click event handler of the appropriate button:
C#
private void ButtonFont_Click(System.Object sender, System.EventArgs e)
{
// Display font dialog
this.FontDialogChange.Font =
HiddenImage.Properties.Settings.Default.OverlayFont;
if (this.FontDialogChange.ShowDialog(this) !=
System.Windows.Forms.DialogResult.OK)
{
return;
}
HiddenImage.Properties.Settings.Default.OverlayFont =
this.FontDialogChange.Font;
this.LabelFont.Text =
string.Format(
"{0}, {1} pt",
HiddenImage.Properties.Settings.Default.OverlayFont.Name,
HiddenImage.Properties.Settings.Default.OverlayFont.SizeInPoints);
}
Private Sub ButtonFont_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)_
Handles ButtonFont.Click
' Display font dialog
Me.FontDialogChange.Font = My.Settings.OverlayFont
If Me.FontDialogChange.ShowDialog(Me) <> Windows.Forms.DialogResult.OK Then Return
My.Settings.OverlayFont = Me.FontDialogChange.Font
Me.LabelFont.Text = String.Format("{0}, {1} pt", My.Settings.OverlayFont.Name, _
My.Settings.OverlayFont.SizeInPoints)
End Sub
The second way to use application settings is declaratively, with property bindings. We'll show an example of this with the image file name text box. Click the text box and display its properties. Find the (ApplicationSettings) item in the property box and click the small "+" before it. Then click the "…" button in the (PropertyBinding) row. The following dialog box will appear:

You can see that you can bind certain properties to application settings. Actually you can easily create new application settings with correct data types from here (using the "New…" link).
So, bind the Text property to the ImageFilePath application setting. In the same way bind all the other controls — for the number of rows and columns, and using BackgroundColor for the color specification labels.
Not everything can be bound in this way. For example, you would not use binding for font, because you wouldn't want to have the text of the label written in the selected font.
As mentioned before, a well-behaved application should remember its window placement and size. We can use the application settings for this as well.
First, define FormOptionsSize configuration value (of type System.Drawing.Size) and FormOptionsLocation (of type System.Drawing.Point).
The following code in the form's FormClosed event handler will save the window position and size when you close the window:
C#
private void FormOptions_FormClosed(object sender,
System.Windows.Forms.FormClosedEventArgs e)
{
// Save form size and position, if not minimized/maximized
if (!(this.WindowState == FormWindowState.Normal))
{
return;
}
HiddenImage.Properties.Settings.Default.FormOptionsLocation = this.Location;
HiddenImage.Properties.Settings.Default.FormOptionsSize = this.Size;
}
Private Sub FormOptions_FormClosed(ByVal sender As Object,_
ByVal e As System.Windows.Forms.FormClosedEventArgs)_
Handles Me.FormClosed
' Save form size and position, if not minimized/maximized
If Not Me.WindowState = FormWindowState.Normal Then Return
My.Settings.FormOptionsLocation = Me.Location
My.Settings.FormOptionsSize = Me.Size
End Sub
Note that we've added a condition that ensures that nothing is saved when a window is in a minimized or maximized state. That's because the Location and Size properties have awkward values in this case. The code can store the WindowState as well, but it's not usually a good idea to start an application minimized. (The user might think it hasn't been started at all.)
Now you need to make sure that the saved values are used when the form is open, and you can do this in the Load event handler. The selected font is displayed in the label as well. There is no need to load values of the selected files and so on, because they would automatically be loaded by data binding.
C#
private void FormOptions_Load(System.Object sender, System.EventArgs e)
{
// Setup form size and position
this.Location = HiddenImage.Properties.Settings.Default.FormOptionsLocation;
this.ClientSize = HiddenImage.Properties.Settings.Default.FormOptionsSize;
// Show font name and size in text form
this.LabelFont.Text = string.Format(
"{0}, {1} pt",
HiddenImage.Properties.Settings.Default.OverlayFont.Name,
HiddenImage.Properties.Settings.Default.OverlayFont.SizeInPoints);
}Private Sub FormOptions_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
' Setup form size and position
Me.Location = My.Settings.FormOptionsLocation
Me.ClientSize = My.Settings.FormOptionsSize
' Show font name and size in text form
Me.LabelFont.Text = String.Format("{0}, {1} pt", My.Settings.OverlayFont.Name, _
My.Settings.OverlayFont.SizeInPoints)
End Sub
We have successfully created our application, which nicely resizes and remembers its configuration. That's good, but it's not good enough. We need the application to actually do what it was created for. So let's add a new form and name it FormGame. There's no need to do anything else with it for now.
Double-click the "Start" button in the main window and write few lines of code to display the newly created FormGame:
C#
private void ButtonStart_Click(System.Object sender, System.EventArgs e){
// Show game form
FormGame GameForm = new FormGame();
GameForm.ShowDialog(this);
}Private Sub ButtonStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles ButtonStart.Click
' Show game form
Dim GameForm As New FormGame
GameForm.ShowDialog(Me)
End Sub
The actual game itself is created in the new form. Because we need to display the form in full-screen and without any borders, only the image is shown across entire screen. To achieve this, set the following properties of the FormGame form:
FormBorderStyle = None
ShowInTaskbar = False
WindowState = Maximized
To display the selected picture, place a PictureBox on the form and in the Properties window find the Dock property and set the value to Fill. This property has a similar effect to Anchor: It causes the selected control to stick to one side of the form or, in case of the Fill setting, to fill entire form, which is exactly what's needed. Another solution would be to make the picture box the same size as the form and then set the Anchor property to all four sides. Using the Dock property is easier.
We need to show the picture resized to full screen, so set the SizeMode property to Zoom, which will ensure this behavior.
Then place the following code to the Load event handler of the form:
C#
private void FormGame_Load(System.Object sender, System.EventArgs e)
{
// Load image
try
{
this.PictureBoxImage.Image =
Image.FromFile(HiddenImage.Properties.Settings.Default.ImageFilePath);
}
catch (Exception ex)
{
Interaction.MsgBox("Error loading image: " + ex.Message,
MsgBoxStyle.Exclamation, null);
this.Close();
}
}
Private Sub FormGame_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)_
Handles MyBase.Load
' Load image
Try
Me.PictureBoxImage.Image = Image.FromFile(My.Settings.ImageFilePath)
Catch ex As Exception
MsgBox("Error loading image: " & ex.Message, MsgBoxStyle.Exclamation)
Me.Close()
End Try
End Sub
As you see, you can directly use the value from My.Settings.ImageFilePath so that it automatically sets the file name selected by the user with the binding created earlier.
When doing anything with user-entered data, it's important to expect the worst possible error. The file specified may be corrupted, it may not actually be an image, or it may not even exist at all. The above code catches the possible exception thrown when a file cannot be read as image.
If you run the application now, it will display the selected an image in full-screen. You can close the message box window using ALT+F4.
To cover the image, use Label controls. These must generate programmatically at runtime because you won't know during the design stage how many of them there will be.
A control is, from the code point of view, a class just like any other. You can create its instance and set its properties. You can extend the Load event handler using the following code:
C#
private void FormGame_Load(System.Object sender, System.EventArgs e)
{
// Generate basic overlay label width/height
Int32 H = this.PictureBoxImage.Height /
System.Convert.ToInt32(HiddenImage.Properties.Settings.Default.Rows);
Int32 W = this.PictureBoxImage.Width /
System.Convert.ToInt32(HiddenImage.Properties.Settings.Default.Columns);
// Create overlay labels
for (int Y = 1; Y <= System.Convert.ToInt32(
HiddenImage.Properties.Settings.Default.Rows); Y++)
{
for (int X = 1; X <= System.Convert.ToInt32(
HiddenImage.Properties.Settings.Default.Columns); X++)
{
// Create and place overlay label
System.Windows.Forms.Label L = new System.Windows.Forms.Label();
this.PictureBoxImage.Controls.Add(L);
L.Left = (X - 1) * W;
L.Top = (Y - 1) * H;
L.Width = System.Convert.ToInt32(Interaction.IIf(
X == HiddenImage.Properties.Settings.Default.Columns,
this.PictureBoxImage.Width - L.Left, W));
L.Height = System.Convert.ToInt32(Interaction.IIf(
Y == HiddenImage.Properties.Settings.Default.Rows,
this.PictureBoxImage.Height - L.Top, H));
// Setup overlay label display
L.Text = System.Convert.ToString((Y - 1) *
HiddenImage.Properties.Settings.Default.Columns + X);
L.BackColor =
HiddenImage.Properties.Settings.Default.OverlayBackgroundColor;
L.ForeColor =
HiddenImage.Properties.Settings.Default.OverlayForegroundColor;
L.Font = HiddenImage.Properties.Settings.Default.OverlayFont;
L.TextAlign = ContentAlignment.MiddleCenter;
L.BorderStyle = BorderStyle.FixedSingle;
// Attach event handlers
L.Click += new System.EventHandler(ClickHandler);
L.MouseEnter += new System.EventHandler(MouseEnterHandler);
L.MouseLeave += new System.EventHandler(MouseLeaveHandler);
}
}
// Load image
try
{
this.PictureBoxImage.Image = Image.FromFile(
HiddenImage.Properties.Settings.Default.ImageFilePath);
}
catch (Exception ex)
{
Interaction.MsgBox("Error loading image: " + ex.Message,
MsgBoxStyle.Exclamation, null);
this.Close();
}
}
Private Sub FormGame_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)_
Handles MyBase.Load
' Generate basic overlay label width/height
Dim H As Int32 = Me.PictureBoxImage.Height \ CInt(My.Settings.Rows)
Dim W As Int32 = Me.PictureBoxImage.Width \ CInt(My.Settings.Columns)
' Create overlay labels
For Y As Int32 = 1 To CInt(My.Settings.Rows)
For X As Int32 = 1 To CInt(My.Settings.Columns)
' Create and place overlay label
Dim L As New System.Windows.Forms.Label
Me.PictureBoxImage.Controls.Add(L)
L.Left = (X - 1) * W
L.Top = (Y - 1) * H
L.Width = CInt(IIf(X = My.Settings.Columns, Me.PictureBoxImage.Width - L.Left, W))
L.Height = CInt(IIf(Y = My.Settings.Rows, Me.PictureBoxImage.Height - L.Top, H))
' Setup overlay label display
L.Text = CStr((Y - 1) * My.Settings.Columns + X)
L.BackColor = My.Settings.OverlayBackgroundColor
L.ForeColor = My.Settings.OverlayForegroundColor
L.Font = My.Settings.OverlayFont
L.TextAlign = ContentAlignment.MiddleCenter
L.BorderStyle = BorderStyle.FixedSingle
Next
Next
' Load image
Try
Me.PictureBoxImage.Image = Image.FromFile(My.Settings.ImageFilePath)
Catch ex As Exception
MsgBox("Error loading image: " & ex.Message, MsgBoxStyle.Exclamation)
Me.Close()
End Try
End Sub
The only things left to solve are the width and height of the last labels. A control's size property must be an integer, and the screen resolution may not be evenly divisible by the number of squares. Actually the size of the squares at the bottom and to the right of the screen are not the same as the others that fill the remaining space. If this weren't the case, we would be able to see a few pixels of the picture underneath on the right and bottom sides. If you run the aplication now, you'll see no image, only the numbered grid.
A particular square should hide itself when clicked. To achieve this use the Click event handler with the following code:
C#
private void ClickHandler(object sender, EventArgs e)
{
// Make label transparent (leave only border)
System.Windows.Forms.Label L = ((System.Windows.Forms.Label)(sender));
L.BackColor = Color.Transparent;
L.Text = "";
}
Private Sub ClickHandler(ByVal sender As Object, ByVal e As EventArgs)
' Make label transparent (leave only border)
Dim L As System.Windows.Forms.Label = DirectCast(sender, System.Windows.Forms.Label)
L.BackColor = Color.Transparent
L.Text = ""
End Sub
The squares should also indicate visually when the mouse pointer moves over them. A good visual cue is to swap the squares' foreground and background colors. There are two methods for doing this:
C#
private void MouseEnterHandler(object sender, EventArgs e)
{
// Invert color on hover, if label is not revealed
System.Windows.Forms.Label L = ((System.Windows.Forms.Label)(sender));
if (L.BackColor == Color.Transparent)
{
return;
}
L.BackColor = HiddenImage.Properties.Settings.Default.OverlayForegroundColor;
L.ForeColor = HiddenImage.Properties.Settings.Default.OverlayBackgroundColor;
}
private void MouseLeaveHandler(object sender, EventArgs e)
{
// Return color on hover end, if label is not revealed
System.Windows.Forms.Label L = ((System.Windows.Forms.Label)(sender));
if (L.BackColor == Color.Transparent)
{
return;
}
L.BackColor = HiddenImage.Properties.Settings.Default.OverlayBackgroundColor;
L.ForeColor = HiddenImage.Properties.Settings.Default.OverlayForegroundColor;
}
Private Sub MouseEnterHandler(ByVal sender As Object, ByVal e As EventArgs)
' Invert color on hover, if label is not revealed
Dim L As System.Windows.Forms.Label = DirectCast(sender, System.Windows.Forms.Label)
If L.BackColor = Color.Transparent Then Return
L.BackColor = My.Settings.OverlayForegroundColor
L.ForeColor = My.Settings.OverlayBackgroundColor
End Sub
Private Sub MouseLeaveHandler(ByVal sender As Object, ByVal e As EventArgs)
' Return color on hover end, if label is not revealed
Dim L As System.Windows.Forms.Label = DirectCast(sender, System.Windows.Forms.Label)
If L.BackColor = Color.Transparent Then Return
L.BackColor = My.Settings.OverlayBackgroundColor
L.ForeColor = My.Settings.OverlayForegroundColor
End Sub
Next let's "wire" the methods specified above to run at the right moment. You can use the AddHandler command for this while generating the controls. Change the FormGame_Load method by entering the following code to the inner loop (right after line L.BorderStyle = BorderStyle.FixedSingle):
C#
// Attach event handlers L.Click += new System.EventHandler(ClickHandler); L.MouseEnter += new System.EventHandler(MouseEnterHandler); L.MouseLeave += new System.EventHandler(MouseLeaveHandler);
' Attach event handlers AddHandler L.Click, AddressOf ClickHandler AddHandler L.MouseEnter, AddressOf MouseEnterHandler AddHandler L.MouseLeave, AddressOf MouseLeaveHandler
These methods will handle the events of all the label controls. The source parameter indicates which particular control caused the event.
The application is almost complete. If we run it now, the squares will hide when they are clicked, and that's what we want.
As mentioned earlier, pressing the ENTER key should reveal the entire image, while pressing the ESC key should cancel the game. So, how to you achieve this? By using the form's KeyDown event. Attach the following code to that method:
C#
private void FormGame_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { switch (e.KeyData) { case Keys.Escape: case Keys.Back: // Close window this.Close(); break; case Keys.Space: case Keys.Enter: // Reveal image foreach (System.Windows.Forms.Control C in this.PictureBoxImage.Controls) { C.Hide(); } break; } }Visual BasicPrivate Sub FormGame_KeyDown(ByVal sender As Object,_ ByVal e As System.Windows.Forms.KeyEventArgs)_ Handles Me.KeyDown Select Case e.KeyData Case Keys.Escape ' Close window Me.Close() Case Keys.Enter ' Reveal image For Each C As Control In Me.PictureBoxImage.Controls C.Hide() Next End Select End Sub
We did not hide the label controls completely in the per-square hiding code, but made their contents transparent to leave a border. In the new code revealing the entire image, we will hide them, to show the picture in all its beauty rather than with black grid bars over it.
I hope that by building this application along with me you have learned some useful things to help you in developing more complicated applications.
First, it is important to have a decent user interface — and it is easy and painless to achieve it with Microsoft .NET 2.0. Second, we reviewed how the application settings framework works and how you can easily manage configuration settings. Also you now know how to bind them to control properties and use them procedurally from the code. Third, we dynamically generated controls at run time, setting their properties and handling their events. And last but not at least, you have a fun game to play with friends. So, let's start!
Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation,
please create a new thread in our Forums,
or
Contact Us and let us know.
Follow the Discussion
Oops, something didn't work.
What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in. You need to be signed in to Channel 9 to use this feature.What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in and view them all on your notifications page.sign up for email notifications?
Nice Article so far, BUT: IMHO you don't save your settings. So the line
HiddenImage.Properties.Settings.Default.Save()
is missing in FormOptions_FormClosed.
Remove this comment
Remove this thread
close