Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Lessons Learned in Debugging of WPF Input Commands and Events

Learn more about debugging of Input/Key related commands and events.


advertisement

This article describes a situation during debugging of Input/Key related commands and events. The ensuing problems and solutions are presented here to help readers avoid pitfalls that they may face while debugging similar scenarios.

Task

One day a product manager stopped by my desk and said that some product requirements had changed. One of the new requirements was to transform a TextBox Control previously used for displaying values into a Textbox Control that allowed entering values. New actions would also be associated with some of the Control's events.

New Requirements

The new product requirement became the following software requirements:

  • When the user brings focus to the control
    • Do some action
  • When the user presses the Enter key after typing
    • Do another action
    • Highlight the entered text
  • When the user moves focus to another control
    • Do a different action

Implementation

In the beginning, the implementation seemed straightforward. The application was developed using the WPF UI Framework with the MVVM pattern in mind and its Light Toolkit implementation. The XAML code for the above requirements is shown in Listing 1.



Listing 1. XAML code example for Commands

.  .  .
xmlns:int="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:conv="clr-namespace:Converters;assembly=Converters"
.  .  .
< Grid.Resources>
          < conv:InDataMultiBindingConverter  x:Key="InDataMultiBindingConverter" />
< /Grid.Resources>

< TextBox Name="InDataTextBox"
                             . . .
                             >
               < TextBox.InputBindings>
               < KeyBinding   Command="{Binding Path=InDataEnterPressedCommand}" Key="Enter">
                             < KeyBinding.CommandParameter>
                             < MultiBinding Converter="{StaticResource InDataMultiBindingConverter}">
                                           < MultiBinding.Bindings>
                                                          < Binding ElementName="InDataTextBox" />
                                                          < Binding  />
                                           < /MultiBinding.Bindings>
                             < /MultiBinding>
                             < /KeyBinding.CommandParameter>
               < /KeyBinding>
               < /TextBox.InputBindings>

< int:Interaction.Triggers>
               < int:EventTrigger EventName="LostFocus">
                             < int:InvokeCommandAction CommandParameter="{Binding}"
                                                                      Command="{Binding InDataLostFocusCommand}" />
               < /int:EventTrigger>

               < int:EventTrigger EventName="GotFocus">
                             < int:InvokeCommandAction CommandParameter="{Binding}"
                                                                        Command="{Binding InDataGotFocusCommand}" />
                < /int:EventTrigger>
< /int:Interaction.Triggers>

< /TextBox>

The Command classes are shown in Listings 2-4:

Listing 2. C# code example for EnterPressed Command Implementation

public class InDataEnterPressedCommand : ICommand
{
        public void Execute(object par)
        {
            List< object> o = par as List< object>;
            ViewModellnData  vmInData = o[1] as ViewModellnData;
            vmInData.InDataEnterPressed(o[0]);
        }
              . . .
}

Listing 3. C# code example for GotFocus Command Implementation

public class InDataGotFocusCommand : ICommand
{
        public void Execute(object par)
        {
            ViewModellnData  vmInData = par as ViewModellnData;
            vmInData.InDataGotFocus();
        }
        . . .
}

Listing 4. C# code example for LostFocus Command Implementation

public class InDataLostFocusCommand : ICommand
{
        public void Execute(object par)
        {
            ViewModellnData  vmInData = par as ViewModellnData;
            vmInData.InDataLostFocus();
        }
        . . . 
}

The Multibound converter is shown below:

Listing 5. C# code example for Multibound Converter

public class InDataMultiBindingConverter : IMultiValueConverter
{
        public object Convert(object[] values, Type targetType,
                                                         object parameter, CultureInfo culture)
        {
            List< object> o = new List< object>();
            o.Add(values[0]);
            o.Add(values[1]);
            return o;
        }
}

The ViewModel provides methods that represent actions that are initiated through user interface (UI). The ViewModel handle code for the EnterPressed Command is shown in Listing 6.

Listing 6. C# code example for ViewModel handle for the EnterPressed Command

 internal void InDataEnterPressed(object sender)
 {
            TextBox tb = sender as TextBox;
            tb.SelectAll();
                . . .
            //product specific code here
  }

The ViewModel handle code for GotFocus Command is shown below:

Listing 7. C# code example for ViewModel handle for theLost\GotFocus Commands

internal void InDataGotFocus()  //no parameters
{
              //product specific code here
              . . .
}

First Lesson

After the prototype was created, it became apparent that ViewModel accesses the View's TextBox object. My first implementation broke the MVVM separation of concerns principle.

Process

I needed to fix the code and to move the text highlighting (selecting) related code from the ViewModel to the place it belonged. UI logic should be encapsulated in the View. I set a breakpoint on the first line in the InDataEnterPressed() method shown in Listing 6 and looked at the Visual Studio Call Stack Window (Listing 8). As you can see, the EnterPressed Command is associated with the Key Down event.

Listing 8. Call Stack for InDataEnterPressed method

ViewModel.dll!ViewModelData.ViewModelData.InDataEnterPressed
ViewModel.dll!ViewModelData.InDataEnterPressedCommand.Execute
PresentationCore.dll!System.Windows.Input.CommandManager.TranslateInput
PresentationCore.dll!System.Windows.UIElement.OnKeyDownThunk
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl
PresentationCore.dll!System.Windows.UIElement.RaiseTrustedEvent
PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea
PresentationCore.dll!System.Windows.Input.InputManager.ProcessInput
PresentationCore.dll!System.Windows.Interop.HwndKeyboardInputProvider.ReportInput
PresentationCore.dll!System.Windows.Interop.HwndKeyboardInputProvider.ProcessKeyAction
PresentationCore.dll!System.Windows.Interop.HwndSource.CriticalTranslateAccelerator
PresentationCore.dll!System.Windows.Interop.HwndSource.OnPreprocessMessage
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen
WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl
WindowsBase.dll!System.Windows.Threading.Dispatcher.Invoke
PresentationCore.dll!System.Windows.Interop.HwndSource.OnPreprocessMessageThunk
WindowsBase.dll!System.Windows.Interop.ComponentDispatcherThread.RaiseThreadMessage
WindowsBase.dll!System.Windows.Threading.Dispatcher.TranslateAndDispatchMessage
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl
PresentationFramework.dll!System.Windows.Application.RunInternal
PresentationFramework.dll!System.Windows.Application.Run
MotorSentry.exe!MotorSentry.App.Main
[Native to Managed Transition]	
Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.
                                        HostingProcess.HostProc.RunUsersAssembly   
mscorlib.dll!System.Threading.ExecutionContext.RunInternal
mscorlib.dll!System.Threading.ExecutionContext.Run
mscorlib.dll!System.Threading.ExecutionContext.Run

I hooked up the KeyUp event to the InDataTextBox_KeyUp method implemented in the .XAML.CS code behind file. Inside this method I added the conditional statement to only apply highlighting code for a case in which the user pressed the Enter key. Also, the first two lines were removed from InDataEnterPressed method. The modifications to XAML code in Listing 1 are shown in Listing 9. The implementation method is shown in Listing 10. As we see from Listing 9, the InDataEnterPressedCommand command was still used to perform the above stated "do another action" requirement.

Listing 9. XAML code with use of KeyUp Event

< TextBox Name="InDataTextBox"
              . . .          KeyUp="InDataTextBox_KeyUp"    
              >
              < TextBox.InputBindings>
< KeyBinding   CommandParameter="{Binding}"
                                              Command="{Binding Path=InDataEnterPressedCommand}" Key="Enter" />
< /TextBox.InputBindings>
              . . .
< /TextBox>

Listing 10. C# code example for KeyUp Event handler

private void InDataTextBox_KeyUp(object sender, KeyEventArgs e)
{
              if (e.Key == Key.Enter)
              {
                   TextBox tb = sender as TextBox;
                   tb.SelectAll();
              }
}

Note: For those who like to use inline code and to pack as much as possible in .XAMLfiles, Listing 11 shows the trick.

Listing 11. Using Alternative Inline Code in XAML

< x:Code>
               < ![CDATA[
               void InDataTextBox_KeyUp(object sender, KeyEventArgs e)
                {
                    if (e.Key == Key.Enter)
                     {
                        TextBox tb = sender as TextBox;
                        tb.SelectAll();
                    }
                }
         ]]>
< /x:Code>

Second Lesson

I began debugging the code. I set two breakpoints — one in the beginning of the InDataEnterPressed method (Listing 6) and the second at the beginning of the InDataTextBox_KeyUp() method (Listing 10).

As expected, execution of the program paused when the first breakpoint was hit. Then I pressed the F5 key to continue. The program continued to run, but the second breakpoint was not hit.

I repeated the debug session several times. The result was the same — the second breakpoint was not hit. I was puzzled.

Third Lesson

During one more try, I pressed the Enter key and unintentionally held it down a little bit longer than during previous runs. When the first breakpoint was hit, blank lines were inserted in the code in the Code Editor window just above the line with the breakpoint. It gave me a hint. I understood that Visual Studio stalled the focus from the application that I had debugged. Then Visual Studio stalled the keyboard input. The KeyUp event for the TextBox Control never executed. This was the reason why the debugger never hit the second breakpoint. I removed the first breakpoint and ran the debugger again. At this time, the second breakpoint, which was the only breakpoint, was hit. The "mystery" was solved. I realized that the Debugger should be used with precision when troubleshooting problems related to analyzing the sequences of keyboard-related events.

Fourth Lesson

To prove that both methods were executed consecutively, I used my application trace facilities instead of breakpoints. That was how I confirmed that a particular piece of code was executed without interference from the Debugger.

In addition, it is possible to use the System.Diagnostics.Debug.WriteLine method to write information to the trace listeners in the Debug.Listeners collection property. The Visual Studio Debugger shows this information in the Output window. In Release builds, calls to this method are not compiled.

Wrapping Up

The lessons learned illustrate that in software debugging the Randori martial arts principles of responding with the right action, right timing, and knowledge of when to disengage are essential to success.

Acknowledgments

I would like to thank my colleague Ali Husain for helpful discussions and advices during analysis of the requirements. Also thanks to Debie Urycki for proofreading and suggestions on corrections.

About the Author

Boris Eligulashvili is a software developer at LineStream Technologies, Inc. a pioneer in embedded controls software. In his career he has implemented many innovative software solutions for various development projects. Contact Boris.



   
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date