DateTimePicker Control Data Type Mismatch Problem

A reader recently made me aware of a problem with the Get User Favorites example in Chapter 2 that could cause a lot of problems depending on which language you use when working with Visual Studio 2012. This issue does affect some of the examples in Microsoft ADO.NET Entity Framework Step by Step so it’s really important you know about it.

Look at the model on page 30 of the book (the “Creating the model” section of Chapter 2). The Birthday field is defined as type DateTime. When you finish creating the model, you right click the resulting entity and choose Generate Database from Model to create a database from it. The “Working with the mapping details” section of Chapter 1 (page 19) tells you how to use the Generate Database Wizard to create the database. The Birthday field in the database will appear as a datetime type when you complete the wizard, which is precisely what you should get.

At this point, you begin creating the example form to test the database (the “Creating the test application” section on page 36). The example uses a DateTimePicker control for the Birthday field by default. You don’t add it, the IDE adds it for you because it sees that Birthday is a datetime data type. The example will compile just fine and you’ll be able to start it up as normal.

However, there is a problem that occurs when working with certain languages when you start the “Running the basic query” section that starts on page 39. The DateTimePicker control appears to send a datetime2 data type back to SQL Server when you change the birthday information. You’ll receive an exception that says the data types don’t match, which they don’t. There are several fixes for this problem. For example, you could coerce the output of the DateTimePicker control to output a datetime data type instead of a datetime2 data type. However, the easiest fix is to simply change the data type of the Birthday field in the database from datetime to datetime2. After you make this change, the example will work as it should. You only need to make this change when you see the data type mismatch exception. I haven’t been able to verify as of yet which languages are affected and would love to hear from my international readers about the issue.

As far as the book is concerned, this problem is quite fixable using the manual edit (although, manually editing the database shouldn’t be something you should have to do). However, it does bring up the question of how teams working across international boundaries will interact with each other if they can’t depend on the controls acting in a particular way every time they’re used. This is a problem that you need to be aware of when working with larger, international, teams. Let me know if you have any questions or concerns about the book example at John@JohnMuellerBooks.com. You’ll have to contact Microsoft about potential fixes to the DateTimePicker control since I have no control over it.

 

Exploring the TypingBuddy Application (Part 10)

In the previous installment of this series, Exploring the TypingBuddy Application (Part 9), you discovered how the Notification Area menu works in Typing Buddy. The main purpose of this menu is to control the timer used to determine when it’s time to stop typing. Given the purpose of TypingBuddy, keeping you from working so long as to cause repetitive stress injuries, the most important code is that used by the timer to display the stop message.

The TypeTimer control relies on the Timer_Tick() event handler to respond to Tick events once each second. The Timer_Tick() method must accomplish the following tasks:

 

  • Update the counter.
  • Check for an end of typing time condition.
  • Choose a message.
  • Ensure the message falls within the required parameters for display.
  • Display the message on screen.


With these criteria in mind, it’s time to look at the method code. The following code shows how the Timer_Tick() method performs its assigned tasks.

private void Timer_Tick(object sender, System.EventArgs e)
{
   frmMessage MyMessage;   // Message dialog box.
   DialogResult RetVal;    // Return value from dialog.
   Int32 Count = 0;        // Number of messages.
   Random Rand;            // A random number generator.
   Int32 Selection = -1;   // The message selection.
 
   // Set the timer value.
   TimeLeft--;
 
   // See if the timer has expired.
   if (TimeLeft == 0)
   {
      // Determine whether there are any messages to use.
      if ((Settings.MessageList != null) &&
         (Settings.MessageList.Length > 0))
      {
         // Obtain the current number of messages.
         Count = Settings.MessageList.Length;
 
         // Randomize a specific message.
         Rand = new Random(DateTime.Now.Millisecond);
         Selection = Rand.Next(Count);
 
         // Ensure that the message isn't one that has a date set.
         if (Settings.MessageList[Selection].UseDates)
 
            // When the message does have the date set,
            // ensure the date is in the right timeframe.
            Selection = VerifySelection(
               Settings.MessageList, Selection);
      }
 
      // Create the new dialog box.
      if (Selection == -1)
      {
         if (chkShowIcon.Checked)
            MyMessage = new frmMessage(
               (Int32)RestingValue.Value * 60,
               chkShowIcon.Checked);
         else
            MyMessage = new frmMessage(
               (Int32)RestingValue.Value * 60);
      }
      else
      {
         if (Settings.MessageList[Selection].ShowTitle)
         {
            if (chkShowIcon.Checked)
               MyMessage = new frmMessage(
                  (Int32)RestingValue.Value * 60,
                  Settings.MessageList[Selection].Title,
                  Settings.MessageList[Selection].MessageText,
                  chkShowIcon.Checked);
            else
               MyMessage = new frmMessage(
                  (Int32)RestingValue.Value * 60,
                  Settings.MessageList[Selection].Title,
                  Settings.MessageList[Selection].MessageText);
         }
         else
         {
            if (chkShowIcon.Checked)
               MyMessage = new frmMessage(
                  (Int32)RestingValue.Value * 60,
                  Settings.MessageList[Selection].MessageText,
                  chkShowIcon.Checked);
            else
               MyMessage = new frmMessage(
                  (Int32)RestingValue.Value * 60,
                  Settings.MessageList[Selection].MessageText);
         }
      }
       
      // Reset the controls.
      TimeLeft = (Int32)TypingInterval.Value * 60;
      TypeTimer.Stop();
 
      // Play the required sound when necessary.
      if (chkPlaySound.Checked)
         if (Selection != -1)
         {
            switch (Settings.MessageList[Selection].Sound)
            {
               case TBMessages.SystemSoundList.None:
                  break;
               case TBMessages.SystemSoundList.Asterisk:
                  System.Media.SystemSounds.Asterisk.Play();
                  break;
               case TBMessages.SystemSoundList.Beep:
                  System.Media.SystemSounds.Beep.Play();
                  break;
               case TBMessages.SystemSoundList.Exclamation:
                  System.Media.SystemSounds.Exclamation.Play();
                  break;
               case TBMessages.SystemSoundList.Hand:
                  System.Media.SystemSounds.Hand.Play();
                  break;
               case TBMessages.SystemSoundList.Question:
                  System.Media.SystemSounds.Question.Play();
                  break;
            }
         }
         else
            System.Media.SystemSounds.Beep.Play();
 
      // Display the stop typing message.
      RetVal = MyMessage.ShowDialog(this);
 
      if (RetVal == DialogResult.OK)
         // Restart the timer.
         TypeTimer.Start();
      else
         // Exit the application.
         Close();
   }
   else
   {
      // Update the time left indicator.
      txtTypingTime.Text = TimeLeft.ToString();
      ThisNotifyIcon.Text = "Time Left in Seconds: " +
                         TimeLeft.ToString();
   }
}

Let’s start with the simple decision. Most of the time, the code will decrement TimeLeft and find that it still isn’t 0. When this condition occurs, the code updates the Time Left (in Seconds) field of frmMain to display the new typing time left value. In addition, you might remember that the the user can also see the amount of time left by hovering the mouse over the icon in the Notification Area. In order to accomplish this task, the code updates the ThisNotifyIcon.Text property value.

Of course, the alternative to updating the timer is to display a message. The Selection variable contains the number of the message to display. It begins with a value of -1 to indicate there is no message to display. When the user has configured custom messages, the code chooses a random message number from that collection and places it in Selection. However, the selection process must consider whether the selected message is only usable on a specific date. When this occurs, the code calls VerifySelection() with the list of available messages and the current selection from that list. Don’t worry about the inner workings of this method for now; you’ll see it in operation later.

When the user hasn’t configured any custom messages, Selection remains at -1 and the application displays the standard message that’s provided as part of the frmMessage configuration. The only decision left to make at this point is which constructor to call. When the user wants to display an icon, the code passes the value of chkShowIcon.Checked to the constructor. Otherwise, all it passes is the amount of resting time that the message should use.

Creating the message box when working with custom messages becomes a little more complex because the code must now decide whether the user wants the message title displayed or not (along with the decision about displaying an icon). When the user wants to display both the title and a custom message, the code uses constructors that pass along Settings.MessageList[Selection].Title and Settings.MessageList[Selection].MessageText; otherwise, the code simply passes the message text to the constructor.

Now that the application has a message box ready to display, it’s time to reset the count and stop the timer. The user may want to hear a sound when the message displays. To accommodate this need, the code looks at chkPlaySound.Checked. When this property is true, it then checks for a standard or custom message and plays the appropriate sound. Note that when the custom message has TBMessages.SystemSoundList.None selected, the application doesn’t play a sound even when chkPlaySound.Checked is true.

Everything is ready to display the message to the end user. The user either waits for the rest time counter to count down or clicks Quit to exit TypingBuddy. When the user waits for the rest period to end and clicks Continue, the code restarts the timer, which starts a new typing cycle. Otherwise, the application ends.

These are the basics of how the timer handling works. However, there is another issue to consider, verifying that the message is within the correct date span. The VerifySelection() method handles this requirement as shown here.

private Int32 VerifySelection(TBMessage[] List, Int32 Selection)
{
   // Check the date range.
   if ((DateCompare(Settings.MessageList[Selection].StartDate) <= 0) &&
      (DateCompare(Settings.MessageList[Selection].EndDate) >= 0))
 
      // The Selection value is in the correct date range.
      return Selection;
 
   // Obtain the message list count.
   Int32 Count = List.Length;
 
   // When the selected message isn't in the right timeframe, look for
   // a message that does fit within the required timeframe starting
   // from the current selection.
   for (Int32 NewSel = Selection + 1; NewSel < Count; NewSel++)
 
      // Check to see if the message has a date usage requirement.
      if (List[NewSel].UseDates)
      {
 
         // Check the date range.
         if ((DateCompare(List[NewSel].StartDate) <= 0) &&
            (DateCompare(List[NewSel].EndDate) >= 0))
 
            // Return a new selection.
            return NewSel;
      }
      else
 
         // This entry doesn't use a date, so return it.
         return NewSel;
 
   // We've gotten to the end of the list without finding a message.
   // Let's start over from the beginning of the list.
   for (Int32 NewSel = 0; NewSel < Selection; NewSel++)
 
      // Check to see if the message has a date usage requirement.
      if (List[NewSel].UseDates)
      {
 
         // Check the date range.
         if ((DateCompare(List[NewSel].StartDate) <= 0) &&
            (DateCompare(List[NewSel].EndDate) >= 0))
 
            // Return a new selection.
            return NewSel;
      }
      else
 
         // This entry doesn't use a date, so return it.
         return NewSel;
 
   // There aren't any messages in the database that will work in
   // the current date range. In short, even if there are messages
   // in the list, it's as if the list is empty because none of the
   // message are useful.
   return -1;
}

The code begins by verifying that the date is within correct time span. However, you don’t want to compare years, just the month and day, so the code relies on a special method, DateCompare(), to perform this task. You’ll see how DateCompare() works in a few moments, so don’t worry about it for now. All you really need to know is that DateCompare() returns -1 when the date supplied as part of the custom message is less than the current date, 0 when the month and day are precisely the same as the current date, and 1 when the custom message date is greater than the current date. When the selected message dates fall within the current date, then the code returns the current selection.

There are going to be times when the custom message dates won’t include the current date. When that happens, the application begins with the current message and looks at each message in the list until it gets to the end of the list looking for a message that either doesn’t have the dates set (which means that the reader would like to see the message at any time) or the current date falls within the message date. The application uses a simple for loop to perform the task.

It could happen that none of the custom messages starting with the current message to the end of the list are useable. In this case, the code starts looking for a useable message from the beginning of the list to the original selected message location. The criteria are the same—the code looks for a message that has no dates set or one that has a range that includes the current date.

The application has reviewed the entire list of custom messages at this point. It could happen that the application gets to the end of the list and still doesn’t find a useable message. When that situation occurs, VerifySelection() returns -1. If you review the Timer_Tick() code, you’ll see that this condition automatically selects the default message, even if the user has created custom messages.

The .NET Framework doesn’t provide an easy method for comparing just the month and the day of two dates. You usually compare the entire date or a specific date element. In order to perform the correct level of comparison, the example uses the DateCompare() method shown here.

private Int32 DateCompare(DateTime Date)
{
   // Create variables to hold the checked date information.
   Int32 Month = Date.Month;
   Int32 Day = Date.Day;
 
   // Create variables to hold the current date information.
   Int32 CMonth = DateTime.Now.Month;
   Int32 CDay = DateTime.Now.Day;
 
   // Compare the month and the day.
   if (Month < CMonth)
 
      // When the month specified by the user is less
      // than the current month, return -1.
      return -1;
   else if (Month > CMonth)
 
      // When the month specified by the user is greater
      // than the current month, return 1.
      return 1;
 
   // The months are equal, so look at the dates.
   else
   {
      if (Day < CDay)
 
         // When the day specified by the user is less than
         // the current day, then reutrn -1.
         return -1;
      else if (Day > CDay)
 
         // When the day specified by the user is greater
         // than the current day return 1.
         return 1;
      else
 
         // Both the month and the day are equal, so
         // return 0.
         return 0;
   }
}

As you can see, this method first compares the month values. When the two months are precisely equal, it then compares the day values. When the two days are also equal, the method returns a value of 0. Otherwise, the method returns either -1 or 1 depending on whether the custom message date is greater or less than the current date.

Now you have the code for the entire application except for the event handlers in frmMain itself. Next week you’ll receive this last bit of code. In the meantime, let me know if you have any questions at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the TypingBuddy Application (Part 11).