2016-04-19

WPF Behaviors:How to save your TextBox on Enter and Cancel it on Escape

Using WPF, if you are not yet familiar with the so-called Behavior you will discover how to you can create some and reuse them in all your designs.

Introduction

First of all, there are 2 types of Behavior: Attached Behavior and Blend Behavior. The main difference between them is that the Attached Behaviors are static and therefore limited in terms of usage, the Blend Behaviors are not static but require the blend SDK to be used. Blend Behaviors are available via the user interface like any other control using drag and drop, a good and recent description can be found here: 

Now let's have a look at how the Attached Behaviors can be used.

Let's say you have several TextBoxes on your wpf windows or usercontrol, where the user can type in text to be saved through Binding to some properties of your ViewModel.

In your XAML it looks like that:  <TextBox Text="{Binding Path=Dielectric}"/> 

And in your ViewModel there is your property:  public string Dielectric { get; set;} 

You have already certainly noticed the several Binding Mode options and know the difference:
 Mode=Default, OneWay, OneTime, OneWayToSource, TwoWay 

And you also know what means the Binding UpdateSourceTrigger property:
 UpdateSourceTrigger=Default, Explicit, LostFocus, PropertyChanged 



The default settings for a TextBox is  Mode=TwoWay  and  UpdateSourceTrigger=LostFocus , meaning that the binding occurs in both direction, and that this update occurs when the TextBox looses its focus (by leaving the TextBox). 

However you may notice that it is not completely true when you press a button ("Save" by example) right after typing in the TextBox, the string property has not yet been updated. 

More problematic is, if your text generates a validation error ( GetBindingExpression(TextBox.TextProperty).HasError == true , when you must type an integer but you type some text which can not be converted into a number by example), there is no automatic workaround.

What we need

For these reasons, it would be nice if your TextBox have some additional features to make the editing more confortable and reliable. And it is important that theses features are implemented in code-behind only (not the violate the MVVM pattern), without overloading it, and be reusable by any TextBoxes in your design without typing a lot of additional lines of code.

Let see what we need:
  • When we press ENTER or leave the TextBox, the string property is updated.
  • If there is an error, the TextBox is rewritten with the previous value of the string property.
  • When we press ESCAPE, the editing is cancelled and the TextBox is rewritten with the previous value of the string property.
If you are familiar with the WINFORM, you would certainly decide to do it as usual: create events handlers on "KeyUp" and "LostKeyboardFocus" to implement these features. You would have to do it for each TextBoxes, and change the "UpdateSourceTrigger" to "Explicit" to each of them. At the end you would end up with a big messy code-behind file, not reusable anywhere else.

You must solve these disadvantages because as a programmer you will not write a single application in your life, so reusing the code is very important, and as a good programmer you are happy to learn new languages so it's time to go on with XAML and WPF not in the WINFORM way!

How to do it

To good way to implement this feature is with the "Behaviors".

By writing a new class you will be able to transform all your TextBoxes in your project with a simple line, and you can then reuse it everywhere by simply referencing this class, even in different project simply by copying the file.

You should create a new folder in your project called "Behavior". Inside this folder create a new class:  public static class TextBoxBehavior{ } 

"Attached Bevahiors" need what is called DependencyProperty, it is just a custom property that you can add to any controls of your choice. So we will create one in order to inform the TextBox that it is now a "TextBox used for editing that implements our custom feature".

Let's call this DependencyProperty: OnEnterAndEscUpdate

In order to declare it as a DependencyProperty, the following syntax must be used:

It simply declares a boolean informing that the TextBox now implements the feature: OnEnterAndEscUpdate.

if ((bool)args.NewValue == truemeans that OnEnterAndEscUpdate is true so we will catch the events "KeyUp" of this textBox and process it how we need.

This portion changes the Binding "UpdateSourceTrigger" to "Explicit" as said before. We could ignore it by simply adding it manually to each TextBox in the xaml file: UpdateSourceTrigger=Explicit

In order to duplicate the Binding, there is the easy way where you only copy the property you need, or this way where you copy all properties.
For that I have used an existing function found online, which have been tested and seems to work perfectly. Here is my file in the utils folder of my project: clonebinding.cs
The reason is that I want my Behavior to be as much reusable as possible.

Here is the KeyUp event handler:

It does exactly what we said before:

  • if "ESC" is pressed and the TextBox Binding can be updated from the source, we do it.
  • If "ENTER" is pressed:
    • If the validation has error, we cancel and use the previous value.
    • If the validation is correct, we update the source.
  • If none of these was pressed, we validate the content in order to inform the user that the typing is correct.

if (tb.GetBindingExpression(TextBox.TextProperty).IsDirty == true)  informs if the TextBox has changed or not, so we don't update when it's not necessary.



The complete class

In the final version, you will see that I have added the possibility to call an ICOMMAND when the user presses "ENTER" and the validation is correct. The command can also take a parameter defined in xaml like the other properties.
It is again to make the Behavior as much reusable as possible, because in some circumstances (the current page in a navigation toolbar in my case), it is necessary to perform a command without the need of clicking an additional button (to refresh the records at the new page in my navigation toolbar).

Following the same example, you can implement the same functionnality when the TextBox looses its focus, meaning that the binding source is updated if the value is correct or rewritten to the TextBox if not.

In my case I have used a different DependencyProperty for that, to be more flexible. So at the end it is possible to decide very precisely how the TextBoxes behaves.

So you will see others DependencyProperty :

  • OnLostFocusUpdateTarget: What to do when the TextBox looses its focus.
  • OnEnterAndEscUpdateCommand: The command to be called on ENTER.
  • OnEnterAndEscUpdateCommandParameter: The parameter for this command.
In the last chapter you will see how to use them.

Here is the full class: TextBoxBehavior.cs

How To Use It

The first things to do is to register the namespace in your xaml file, so it can access your behavior file: xmlns:behaviors="clr-namespace:wpf_stockelec.Behaviors"

You can now use them in your TextBox like any other property with code completion: behaviors:TextBoxBehavior.OnEnterAndEscUpdate="True"

I would advice you to use the property "Style" with your controls, it will clarify your xaml file and make it easier to modify all your controls at once.

You should create a folder called Themes with a sub-folder called "current" and place a new xaml file called "Styles.xaml".

In this folder create a new style section for the controls of type TextBox where you place all the common properties used by all the TextBoxes of the same kind.

As you can see it becomes now useless to redefine all the properties for each TextBox.

Now it's necessary to declare your Style.xaml file in your main (windows or usercontrol) xaml:

In my case it a usercontrol.

And then you apply this style to your TextBox like any other property: Style="{StaticResource TextBoxStyleEdit}"


Conclusion

Now you have created 2 more files: TextBoxBehavior.cs and Styles.xaml.

You have added several great features to your TextBoxes and you can reuse them in any project just by copying these files.

You can activate or deactivate these new feature simply by setting the style property of your TextBoxes.

You did not violate the MVVM pattern, your code-behind is still empty and your xaml is not overloaded.



I hope this post was useful to someone and worth to read it.
Do not hesitate to help me to improve it, any new idea or suggestion is welcome.

No comments:

Post a Comment