Exploring the TypingBuddy Application (Part 6)

The previous post, Exploring the TypingBuddy Application (Part 5), describes the technique for displaying messages of various types. A user can choose to use the default message or provide any of a number of randomly selected custom messages. This post assumes that the user has chosen the custom message route. Of course, that means having a database of messages to choose from. The act of adding and editing messages requires a special form, one that contains all of the fields required to perform these two tasks. The TypingBuddy application relies on frmAddEdit to perform this task. You saw this form described in the Exploring the TypingBuddy Application (Part 3) post. As described by its name, this is a dual use form that allows for either adding or editing message records to the XML file that contains the user’s data for this application.

A developer has a number of techniques available to create forms used for multiple purposes. In this case, the caller exercises control over public members of frmAddEdit to modify its behavior. This approach is actually the easiest way to do things. In order to make configuration easier, frmAddEdit defines three special properties:

// Create a resources property that contains the message database.
public TBMessage[] MessageList { get; set; }
 
// Make it possible to edit a particular record.
public Int32 RecordNumber { get; set; }
 
// Check whether this is an add or an edit.
public Boolean IsEdit { get; set; }

These properties contain the message database, the selected record within that database, and a simple Boolean value that determines whether the form is in adding or editing mode. Because this form is created with flexibility in mind, you could probably add other properties to better control precisely how the form appears. However, you don’t require the level of flexibility described for frmMessage, so the use of multiple constructors (as in that case) is unnecessary.

The task of configuring the form falls to the frmAddEdit_Load() event handler. There are actually two levels of configuration required—the appearance of the user interface and the data shown within the form. The appearance is handled by the caller at the time the form is created, before it gets displayed. The data is configured as shown here:

private void frmAddEdit_Load(object sender, EventArgs e)
{
   // Fill the related sound list with the correct values.
   cbSound.Items.AddRange(
      Enum.GetNames(typeof(TBMessages.SystemSoundList)));
   cbSound.SelectedIndex = 0;
 
   // When this is an edit, update the form with the correct
   // data.
   if (IsEdit)
   {
      txtTitle.Text = MessageList[RecordNumber].Title;
      chkShowTitle.Checked = MessageList[RecordNumber].ShowTitle;
      txtMessageText.Text = MessageList[RecordNumber].MessageText;
      cbSound.SelectedItem = MessageList[RecordNumber].Sound.ToString();
      chkDateSpecific.Checked = MessageList[RecordNumber].UseDates;
 
      // Don't provide date values unless the message uses dates.
      if (MessageList[RecordNumber].UseDates)
      {
         dtStartDate.Value = MessageList[RecordNumber].StartDate;
         dtEndDate.Value = MessageList[RecordNumber].EndDate;
      }
      else
      {
         dtStartDate.Enabled = false;
         dtEndDate.Enabled = false;
      }
   }
}

When the form is in adding mode, the frmAddEdit_Load() event handler only has to add the list of available sounds to cbSound, and then select one of the items on the list. Because the form is already configured for adding mode, you don’t need to do anything to the user interface.

In editing mode, the frmAddEdit_Load() event handler must perform some additional tasks. It begins by configuring all of the common message data on the form. Then, if the UseDates property is checked, the event handler must also configure the starting and ending date values.

Handling the use of dates is an important consideration. You don’t want to set any dates unless the user says to use dates. Consequently, the application also requires the chkDateSpecific_CheckedChanged() event handler shown here.

private void chkDateSpecific_CheckedChanged(object sender, EventArgs e)
{
   // Determine the checked state.
   if (chkDateSpecific.Checked)
   {
      // Enable the dates when using date information.
      dtStartDate.Enabled = true;
      dtEndDate.Enabled = true;
   }
   else
   {
      // Otherwise, disable the dates.
      dtStartDate.Enabled = false;
      dtEndDate.Enabled = false;
   }
}

This event handler enables or disables the date fields as needed. Notice that the date fields are always enabled and disabled as a pair. The user must set both a starting and ending date in order for the date system to work properly.

The most complex piece of code for this part of the example appears in the btnAdd_Click() event handler. This code must incorporate a number of safety check to ensure the user doesn’t enter an invalid record into the database. In addition, the code must ensure that dates are only recorded when the user wants to employ dates as a filtering mechanism as shown here.

private void btnAdd_Click(object sender, EventArgs e)
{
   // Verify that the fields are correct.
   if ((txtTitle.TextLength == 0) || (txtMessageText.TextLength == 0))
   {
      // Display a message and exit when the user
      // hasn't provided the right information.
      MessageBox.Show(
         "You must provide values for both the Message Title " +
         "and Message Text fields!",
         "Entry Error",
         MessageBoxButtons.OK,
         MessageBoxIcon.Error);
      return;
   }
 
   // Create a temporary array.
   TBMessage[] Temp;
 
   // Determine the sort of change taking place.
   if (IsEdit)
   {
      // Initialize the temporary array.
      Temp = MessageList;
 
      // Add information to the new element.
      Temp[RecordNumber].Title = txtTitle.Text;
      Temp[RecordNumber].ShowTitle = chkShowTitle.Checked;
      Temp[RecordNumber].MessageText = txtMessageText.Text;
      Temp[RecordNumber].Sound =
         (TBMessages.SystemSoundList)Enum.Parse(
            typeof(TBMessages.SystemSoundList),
            cbSound.SelectedItem.ToString());
      Temp[RecordNumber].UseDates = chkDateSpecific.Checked;
 
      // Add dates only when required.
      if (chkDateSpecific.Checked)
      {
         Temp[RecordNumber].StartDate = dtStartDate.Value;
         Temp[RecordNumber].EndDate = dtEndDate.Value;
      }
 
      // Update the permanent message list.
      MessageList = Temp;
   }
   else
   {
      // Initialize the temporary array.
      if (MessageList != null)
      {
         // Create a temporary array that's one longer than the
         // existing array.
         Temp = new TBMessage[MessageList.Length + 1];
 
         // Copy the messages from the existing array to the
         // temporary array.
         for (Int32 Record = 0; Record < MessageList.Length; Record++)
            Temp[Record] = MessageList[Record];
 
         // Add a new record to the end of the temporary array.
         Temp[Temp.Length - 1] = new TBMessage();
      }
      else
      {
         Temp = new TBMessage[1];
         Temp[Temp.Length - 1] = new TBMessage();
      }
 
      // Add information to the new element.
      Temp[Temp.Length - 1].Title = txtTitle.Text;
      Temp[Temp.Length - 1].ShowTitle = chkShowTitle.Checked;
      Temp[Temp.Length - 1].MessageText = txtMessageText.Text;
      Temp[Temp.Length - 1].Sound =
         (TBMessages.SystemSoundList)Enum.Parse(
            typeof(TBMessages.SystemSoundList),
            cbSound.SelectedItem.ToString());
      Temp[Temp.Length - 1].UseDates = chkDateSpecific.Checked;
 
      // Add dates only when required.
      if (chkDateSpecific.Checked)
      {
         Temp[Temp.Length - 1].StartDate = dtStartDate.Value;
         Temp[Temp.Length - 1].EndDate = dtEndDate.Value;
      }
 
      // Update the permanent message list.
      MessageList = Temp;
   }
}

As a minimum, a message must include a title and some message text. If the user doesn’t include any other information, the application will either rely on defaults or not provide that information as part of the output. Consequently, txtTitle.Text and txtMessageText.Text are the only two required entries. If the user doesn’t provide these two entries, the application displays an error message and exits.

When the required input is correct, the code moves on to creating a temporary array to hold the updated data. This is a safety feature to ensure that the entire transaction takes place before being made permanent. If the application should suddenly fail between steps, the initial array will still hold valid information.

In editing mode, the btnAdd_Click() event handler begins by copying all of the records from MessageList (the permanent array) to Temp (the temporary array). It then changes each of the required values in Temp. Notice how the code handles dates. Again, it only saves dates when the user wants to use dates as a filtering mechanism. The final step is to make the change permanent by copying Temp to MessageList.

In adding mode, the application can’t assume there are any existing records. As a result, the first check is to determine whether this is a new list of messages or an addition to an existing list. When it’s an addition, the code simply adds a new entry to Temp and copies the required records from MessageList to Temp. However, when this is a new list, the code simply creates a single entry array in Temp. At this point, the process is much like editing an entry. The new values are added to the empty array element and then Temp is copied to MessageList.

Next week we’ll move one level up in the form hierarchy and examine frmMessages. In the meantime, let me know if you have any questions about this part of the example at John@JohnMuellerBooks.com. You can see the next part of this series at Exploring the TypingBuddy Application (Part 7).