Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

September 21, 2009

Write a Memorization Helper Application

Filed under: C#,VB.NET,WinForms @ 7:24 pm

Though this is not necessarily a common requirement, this post demonstrates the following techniques.

  • Converting a text string to an array.
  • Passing data from one form to another on a constructor.
  • Picking a unique set of random numbers.
  • Using the set of random numbers to pick a set of items from an array.
  • Building controls at runtime.
  • Leveraging the FlowLayoutPanel to simplify control layout at runtime.
  • Using the ErrorProvider to show end-user data entry errors.

One of the common ways to memorize a set of text is to read it several times, then blank out some of the words and attempt to fill them in. Then blank out more words and fill them in again. Repeat this process until you can fill in the entire set of text with no words.

Going to a parochial school, my daughters had to memorize lots and lots and lots of Bible verses. Some of their teachers helped them learn the verses using this technique.

Here is the basic use case/user story:

  1. User enters the set of text to memorize and the number of words to remove on one form.
  2. User identifies when this process is finished.
  3. The form closes and a second form appears with the defined number of words removed.
  4. The user enters the text for the missing words.
  5. The user selects to check their work. Errors are indicated.
  6. The user can select to try again.
  7. Each time, more words are removed.

In this example, the first form looks like this:

image

The first TextBox is set as follows:

  • (Name): MemorizeTextBox
  • Anchor: Top, Left, Right
  • Multiline: True

The second TextBox is set as follows:

  • (Name): BlanksTextBox

The button is set as follows:

  • (Name): PracticeButton

The second form looks like this:

image

The first control is a FlowLayoutPanel set as follows:

  • (Name): TextPanel
  • Anchor: Top, Botton, Left, Right
  • AutoSize: True

The Try Again button is set as follows:

  • (Name): TryAgainButton
  • Anchor: Bottom, Right
  • Text: Try Again

The Check button is set as follows:

  • (Name): CheckButton
  • Anchor: Bottom, Right
  • Text: Check

Also add an ErrorProvider (it shows up in the form’s tray) as follows:

  • (Name): ep
  • BlinkStyle: NeverBlink

Let’s start by coding the second form first.

PracticeWin Form

In the PracticeWin form, insert the following code. This code is described in detail below.

In C#:

public partial class PracticeWin : Form
{
    // Memorized text in an array
    string[] memorizeText;

    // Number of blanks
    int  blankCount;
    // Dictionary of blank textBoxes
    Dictionary<int,TextBox> blanks;

    // Random instance
    Random rnd = new Random();

    public PracticeWin(string[] textToMemorize, int intialBlankCount)
    {
        InitializeComponent();

        this.memorizeText = textToMemorize;
        this.blankCount = intialBlankCount;
    }

    private void PracticeWin_Load(object sender, EventArgs e)
    {
        // Set panel properties
        TextPanel.WrapContents = true;

        // set up the form
        SetupForm();
    }

    private void SetupForm()
    {
        // Clear the current controls
        TextPanel.Controls.Clear();

        // Get a set or random numbers
        blanks = new Dictionary<int, TextBox>();
        int numberOfBlanks = 0;
        int randomNumber;
        do
        {
            randomNumber = rnd.Next(0, memorizeText.Length);

            // Ensure this random number was not already selected
            if (!blanks.ContainsKey(randomNumber))
            {
                blanks.Add(randomNumber, null);
                numberOfBlanks += 1;
            }
        } while (numberOfBlanks < blankCount);

        // Create a cell for each array entry
        TextBox tb;
        for (int i = 0; i < memorizeText.Length; i++)
        {
            tb = new TextBox();
            tb.Margin = new Padding(tb.Margin.Left,
                                    tb.Margin.Top,
                                    15,
                                    tb.Margin.Bottom);
            if (blanks.ContainsKey(i))
            {
                tb.Text = string.Empty;
                blanks[i] = tb;
            }
            else
            {
                tb.Text = memorizeText[i];
                tb.ReadOnly = true;
            }
            TextPanel.Controls.Add(tb);
        }
    }

    private void CheckButton_Click(object sender, EventArgs e)
    {
        // Check the answers
        foreach (var blank in blanks)
        {
            if (blank.Value.Text.ToLower() ==
                             memorizeText[blank.Key].ToLower())
            {
                // Correct, so do whatever here
                ep.SetError(blank.Value,string.Empty);
            }
                else
            {
                // Wrong, so do whatever here
                ep.SetError(blank.Value, memorizeText[blank.Key]);
            }
        }

    }

    private void TryAgainButton_Click(object sender, EventArgs e)
    {
        // Increase the blank count
        blankCount += 3;

        if (blankCount > memorizeText.Length)
            blankCount = memorizeText.Length;

        // Repeat
        SetupForm();
    }
}

In VB:

Public Class PracticeWin

    ‘ Memorized text in an array
    Dim memorizeText() As String

    ‘ Number of blanks
    Dim blankCount As Integer
    ‘ Dictionary of blank textBoxes
    Dim blanks As Dictionary(Of Integer, TextBox)

    ‘ Random instance
    Dim rnd As New Random()

    Public Sub New(ByVal textToMemorize() As String, _
                   ByVal intialBlankCount As Integer)

        ‘ This call is required by the Windows Form Designer.
        InitializeComponent()

        ‘ Add any initialization after the InitializeComponent() call.
        Me.memorizeText = textToMemorize
        Me.blankCount = intialBlankCount
    End Sub

    Private Sub PracticeWin_Load(ByVal sender As System.Object, _
                    ByVal e As System.EventArgs) Handles MyBase.Load
        ‘ Set panel properties
        TextPanel.WrapContents = True

        ‘ set up the form
        SetupForm()

    End Sub

    Private Sub SetupForm()
        ‘ Clear the current controls
        TextPanel.Controls.Clear()

        ‘ Get a set or random numbers
        blanks = New Dictionary(Of Integer, TextBox)
        Dim numberOfBlanks As Integer = 0
        Dim randomNumber As Integer
        Do
            randomNumber = rnd.Next(0, memorizeText.Length)

            ‘ Ensure this random number was not already selected
            If Not blanks.ContainsKey(randomNumber) Then
                blanks.Add(randomNumber, Nothing)
                numberOfBlanks += 1
            End If

        Loop While (numberOfBlanks < blankCount)

        ‘ Create a cell for each array entry
        Dim tb As TextBox
        For i As Integer = 0 To memorizeText.Length – 1
            tb = New TextBox()
            tb.Margin = New Padding(tb.Margin.Left, _
                                    tb.Margin.Top, _
                                    15, _
                                    tb.Margin.Bottom)
            If (blanks.ContainsKey(i)) Then
                tb.Text = String.Empty
                blanks(i) = tb
            Else
                tb.Text = memorizeText(i)
                tb.ReadOnly = True
            End If
            TextPanel.Controls.Add(tb)
        Next

    End Sub

    Private Sub CheckButton_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles CheckButton.Click
        ‘ Check the answers
        For Each blank In blanks
            If (blank.Value.Text.ToLower() = _
                      memorizeText(blank.Key).ToLower()) Then
                ‘ Correct, so do whatever here
                ep.SetError(blank.Value, String.Empty)
            Else
                ‘ Wrong, so do whatever here
                ep.SetError(blank.Value, memorizeText(blank.Key))
            End If
        Next

    End Sub

    Private Sub TryAgainButton_Click(ByVal sender As System.Object, _
              ByVal e As System.EventArgs) _
              Handles TryAgainButton.Click
        ‘ Increase the blank count
        blankCount += 3

        If (blankCount > memorizeText.Length) Then
            blankCount = memorizeText.Length
        End If

        ‘ Repeat
        SetupForm()

    End Sub

End Class

Wow! That is a lot of code. But I thought it would be easier for cutting and pasting to put all of the code in and then explain it instead of having multiple pieces of code for you to paste in with lots of words around it.

Declaring the Form-Level Variables

The memorizedText array contains the array of words in the order they appear in the text to memorize. For example, if the text to memorize is this:

"That that is, is. That that is not, is not. Is that it? It is."

The memorizedText array consists of 15 entries, each containing a word from the text:

  • That
  • that
  • is,
  • is.
  • it?
  • It
  • is.

Notice that the array elements include the capitalization and punctuation. For my kids, having the correct capitalization and punctuation in the memorized text was required. If that is not the case for you, you will need to add code to remove the punctuation and capitalization.

The blankCount integer variable defines how many blanks should be inserted into the text. The blankCount increases on each try.

The blanks generic Dictionary contains the list of random numbers and their associated TextBoxes on the form. This is needed for checking the user-entered values.

The rnd variable is an instance of the Random class. It is defined here and not within the code that sets up the form to ensure that the application does not pick the same set of words to remove each time the user retries.

Defining the Constructor

The next step is to define a constructor. This constructor receives the memorized text array and the blank count from the calling form.

If you are not familiar with this technique for passing data from one form to another using a constructor, see this blog post for more information.

Loading the Form

The code in the load event of the PracticeWin form ensures that the FlowLayoutPanel wraps its contents and then calls a method to set up the form.

Setup the Form

The SetupForm routine takes advantage of the features of the FlowLayoutPanel to layout TextBoxes for each word in the memorized text. Normal words are displayed in a read-only TextBox. Words that were removed are shown with empty TextBoxes.

The code starts by clearing the FlowLayoutPanel from any prior try. It then initializes the variables for this setup. The blanks generic Dictionary holds a selected set of random numbers along with their associated TextBox. The numberOfBlanks variable counts the number of blanks that were defines. This ensures that the correct number of words were blanked out.

The Next method of the Random class is used within a loop to generate a set of random numbers. These are added to the Dictionary. If you are not familiar with this technique of generating random numbers, you can see more information in this post.

Once the correct number of random numbers is selected, the code builds the appropriate Textboxes in the FlowLayoutPanel.

The code loops for each element in the array of words. Within the For loop, the code creates a new TextBox and sets it margin. This is required to leave room for display of the ErrorProvider control.

If the list of random numbers contains the index of the current array element, this word is randomly selected to be removed. So the Text property of the TextBox is blank and the TextBox is added to the Dictionary. This helps keep track of the TextBoxes associated with the removed entires.

If the index of the current array element is not on the list of selected random numbers, the Text property is set to the array element text and the Textbox is marked as ReadOnly. This ensures that the user only enters values for the blank spaces.

The resulting Textbox is then added to the FlowLayoutPanel. The result of this process appears as follows at runtime:

image

Check Processing

The code behind the Check button loops through the generic Dictionary of blanks. The Key of this dictionary is the set of selected random numbers. Each random number defines the index into the word array that contains a blank. The Value of the blanks Dictionary contains the TextBox associated with the blank.

So the code can retrieve the user-entered value by using blank.Value.Text. This value can be compared to the value in the original word array using the key as the index into the memorizedText array.

If the values are equal, any errors are cleared. If the value is not equal, the ErrorProvider SetError method is used to display an error icon.

The result at run-time will look something like this:

image

Notice the red ErrorProvider icons. If the user hovers over the icon, the correct answer is displayed.

Try Again Processing

The TryAgainButton click event increases the blankCount by 3. You can change this to any value. It then ensures it does not calculate a blankCount greater than the number of words and calls SetupForm to reset up the form for another try.

MemorizeWin Form

In the MemorizeWin form, insert the following code. This code is described in detail below.

In C#:

public partial class MemorizeWin : Form
{
    public MemorizeWin()
    {
        InitializeComponent();
    }

    private void PracticeButton_Click(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(MemorizeTextBox.Text) )
        {
            MessageBox.Show("Enter the text to memorize");
            return;
        }

        // Put the text into an array
        string[] wordArray = MemorizeTextBox.Text.Split(‘ ‘);

        int blanks = 0;
        if (int.TryParse(BlanksTextBox.Text, out blanks))
        {
            if (blanks > wordArray.Length)
            {
                MessageBox.Show(
            "The number of blanks cannot exceed the number of words");
            }
            else
            {
                PracticeWin frmPractice = new PracticeWin(wordArray,
                                                    blanks);
                this.Hide();
                frmPractice.ShowDialog();
                this.Show();
            }
        }
        else
        {
            MessageBox.Show("The number of blanks must be numeric");
        }
    }
}

In VB:

Public Class MemorizeWin

    Private Sub PracticeButton_Click(ByVal sender As System.Object, _
                 ByVal e As System.EventArgs) _
                 Handles PracticeButton.Click
        If String.IsNullOrEmpty(MemorizeTextBox.Text) Then
            MessageBox.Show("Enter the text to memorize")
            Return
        End If

        ‘ Put the text into an array
        Dim memorizedTextArr() As String = _
                                       MemorizeTextBox.Text.Split(" "c)

        Dim blanks As Integer = 0
        If Integer.TryParse(BlanksTextBox.Text, blanks) Then
            If (blanks > memorizedTextArr.Length) Then
                MessageBox.Show( _
             "The number of blanks cannot exceed the number of words")
            Else
                Dim frmPractice As New PracticeWin(memorizedTextArr, _
                                                   blanks)
                Me.Hide()
                frmPractice.ShowDialog()
                Me.Show()
            End If
        Else
            MessageBox.Show("The number of blanks must be numeric")
        End If

    End Sub
End Class

This code performs some basic checking to ensure that valid values were entered. It also converts the entered text into an array. If you are not familiar with this technique of using Split to convert a string to an array, see this post.

The code then calls the constructor for PracticeWin, passing in the array of words and the number of blanks. If you are not familiar with this technique of passing values from one form to another using a constructor, see this post.

When the user is finished practicing, the first form re-appears. The user can click Practice again to practice the same text again or can enter another set of text to memorize.

Though you may never need this exact application, the code for this application demonstrated quite a few useful techniques.

Enjoy!

1 Comment

  1.   lol — December 7, 2009 @ 4:41 pm    Reply

    wat i dont get it too long

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2019 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs