RecentComments

Comment RSS

What is the sequence of events/method calls in a WPF Textbox for the Text Property?

by Ioannis 21. March 2009 11:14

What is the sequence of events/method calls in a WPF Textbox for the Text Property?

The moment you start implementing an enterprise application, you realize that you need to take full control of the Textbox control. So you inherit from it, usually creating MyTextbox or something like this. And now there is a whole bunch of events/methods that you can override in order to modify/control its Text property.

In this post, I will use a simple application (source at the end of the post) where I establish the sequence of occurrence of the following events/methods:

  • Converter Methods (Convert/ConvertBack) of a converter attached to the binding of the Text property.
  • PropertyChanged event of the control
  • DependencyPropertyChanged event of the Text property
  • CoerceValue event of the Text property
  • TextChanged event of the Control
  • GotFocus/LostFocus event of the control
  • TextInput event of the control

I will display the sequence of those events using the following figure as the basis:

This figure says that the Textbox and its Text property can be set either by Keyboard Input (when the user types something in the Textbox) or by Code Input (assigning the Text property of the control to a value). Additionally the Text property can either be visualized in the Textbox (Visual Output) or retrieved in code (Code Output).

We create a new class named TextBoxText1 which inherits from Textbox:

public class TextBoxText1 : TextBox

{

In the class constructor we override the PropertyChanged and CoerceValue of the Text property and define our own:

static TextBoxText1()

{

    FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();

    metadata.PropertyChangedCallback = OnTextPropertyChanged;

    metadata.CoerceValueCallback = OnCoerceTextProperty;

    TextProperty.OverrideMetadata(typeof(TextBoxText1), metadata);

}


 

And the two methods:

public static void OnTextPropertyChanged(DependencyObject sender,DependencyPropertyChangedEventArgs args)

{

    (…)

}

 

public static object OnCoerceTextProperty(DependencyObject sender,object value)

{

    return value.ToString()+"(coer)";

}


 

The first is called whenever the Text property is changed and the second is called just before a new value is assigned to the Text property.

We press the key “A” in the Textbox. In this case the following applies (follow the route from “Keyboard Input”):

When we set the value of the Text property from code the following applies (follow the rout from “Code Input”):

Now we override the following events (those are Textbox events):

protected override void OnTextChanged(TextChangedEventArgs e)

{

    (Code 1)

    base.OnTextChanged(e);

    (Code 2)

}

 

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)

{

    (Code 1)

    base.OnPropertyChanged(e);

    (Code 2)

}


 

And we also listen to the TextChanged event from the code that uses the control:

void TextBox1_TextChanged(object sender, TextChangedEventArgs e)

{

    Code

}


 

We press the key “A” in the Textbox. In this case the following applies (follow the route from “Keyboard Input”):

Some notes about those:

  • If we comment out the base.OnTextChaged method call then the Textbox1_TextChanged will not be called
  • If we comment out the base.OnPropertyChanged event the Textbox does not work well.

When we set the value of the Text property from code the following applies (follow the rout from “Code Input”):

We move even further and now also override:

protected override void OnTextInput(TextCompositionEventArgs e)

{

 

    (Code 1)

    base.OnTextInput(e);

    (Code 2)

}

 

protected override void OnLostFocus(RoutedEventArgs e)

{

    (Code 1)

    base.OnLostFocus(e);

    (Code 2)

}


 

We also listen to the Lost Focus event from our client code:

void TextBox1_LostFocus(object sender, RoutedEventArgs e)

{

    (…)

}


 

We press the key “A” in the Textbox. In this case the following applies (follow the route from “Keyboard Input”):

Some notes about those:
* If we comment out the base.OnTextInput method call then the whole sequence is suppressed and nothing is displayed on the Textbox.
* If we comment out the base.OnLostFocus event the TextBox1_LostFocus will never be called.

We take now this another step further by adding DataBinding to the Text property of the control. The databound value is the string “TestValue”.

We press the key “A” in the Textbox. In this case the same events are fired as in the previous case. The only thing to note is that TestValue will be set (the setter will be called) just after OnLostFocus(Code1) and before TextBox1_LostFocus. If we comment out base.OnLostFocus , the setter is never called (this applies only if we set that the UpdateSourceTrigger to LostFocus – the default).

An interesting thing to keep in mind is that although the Text property has the coerced value “A(coer)” the bound value TestValue always has the non-coerced value “A”.

Of course setting the Text property by code, will not affect the value of the TestValue. If we set the TestValue by code the sequence of events is the same as setting the Text Property.

The interesting thing happens when we add the following converter:

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

    return value.ToString()+"(conv)";

}

 

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

    (…)

    return value.ToString()+”(back)”;

}


 

Suppose now that we enter the character “A” in the Textbox. Converter magic happens when the Textbox looses the focus.

1. Converter’s method ConvertBack is called with value “A”.
2. TestValue Setter is called. The value returned from ConvertBack is set to TestValue “A(back)”.
3. Converter’s method Convert is called to retrieve the value “A(back)” and change it to “A(back)(conv)”.
4. Sequence of events like setting the Text property.

If we set the TestValue in code, the value is set in the Setter, the Converter’s Convert method is called and the the sequence is the same as setting the Text property.

And this completes the Textbox event GPS!

kick it on DotNetKicks.com  Shout it

TextBoxTests.zip (53,56 kb)

Tags:

WPF

User Control with a Business Object and Dependency Properties

by Ioannis 7. March 2009 20:29

It's been a while... Changes in my professional dependencies have kept me quite preoccupied. But I am back today with another kind of dependencies the ones that have to do with properties and live in the WPF world.

In an application, there is often the need of implementing a user control that uses a business object and is responsible for providing several manipulation mechanisms on its properties. The User Control – Business Object pair is an approach that isolates the presentation logic for the business object in one place and therefore is very flexible for modifications.

In this post I will demonstrate how this is done in WPF and some possible glitches that may occur for the ones that come from the WinForms approach.

Now suppose that you have the following business object (Person) in your application which holds the first name and last name of a person:

public class Person:System.ComponentModel.INotifyPropertyChanged

{

    private string _firstName = "";

    public string FirstName

    {

        get { return _firstName; }

        set

        {

            _firstName = value;

            if (PropertyChanged != null)

                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("FirstName"));

        }

    }

 

    private string _lastName = "";

    public string LastName

    {

        get { return _lastName; }

        set

        {

            _lastName = value;

            if (PropertyChanged != null)

                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("LastName"));

        }

    }

 

    #region INotifyPropertyChanged Members

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    #endregion

}


Note that the business object already implements the INotifyPropertyChanged interface in order to allow the Binding mechanism to use it.

We want to implement a User Control that uses a Person business object and provides two TextBoxes one for the first name and one for the last name. The User Control should look like this:

And the corresponding XAML code is:

<UserControl x:Class="WPFDPTests.UCPersonEdit"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <StackPanel x:Name="StackPanelPersonInfo" Orientation="Horizontal">

        <Label Content="First Name" VerticalAlignment="Center"/>

        <TextBox x:Name="TextBoxFirstName" Text="{Binding Path=FirstName}" VerticalAlignment="Center" Margin="4" MinWidth="120"/>

        <Label Content="Last Name"  VerticalAlignment="Center"/>

        <TextBox x:Name="TextBoxLastName" Text="{Binding Path=LastName}" VerticalAlignment="Center" Margin="4" MinWidth="120"/>

    </StackPanel>

</UserControl>


In XAML we have defined two data bound TextBoxes, one for the last name and one for the first name. The code-behind file is as follows:

public partial class UCPersonEdit : UserControl

{

    private Person _boundPerson = null;

    public Person BoundPerson

    {

        get { return _boundPerson; }

        set { _boundPerson = value; this.DataContext = value; }

    }

    public UCPersonEdit()

    {

        InitializeComponent();

    }

}


That is, we have implemented a new property for the User Control that holds the controlling Business Object and whenever the BO is changed we reset the DataContext property of the User Control to the new BO for the bindings to work.

To test this functionality, we implement a Window as follows:

<Window x:Class="WPFDPTests.WindowPersonsEdit"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:WPFDPTests"

    Title="User Control - Business Object Binding (c) 2009 - http://www.progware.org/Blog/" Height="153" Width="452">

    <StackPanel>

        <local:UCPersonEdit x:Name="UCPersonEdit1" Margin="4"/>

        <local:UCPersonEdit x:Name="UCPersonEdit2" Margin="4"/>

        <local:UCPersonEdit x:Name="UCPersonEdit3" Margin="4"/>

    </StackPanel>

</Window>


And the code-behind file:

public WindowPersonsEdit()

{

    InitializeComponent();

    Person TestPerson1 = new Person() { FirstName = "Ios", LastName = "Panlos" };

    this.UCPersonEdit1.BoundPerson = TestPerson1;

    this.UCPersonEdit2.BoundPerson = TestPerson1;

    this.UCPersonEdit3.BoundPerson = TestPerson1;

}


Pretty straightforward. We expect to run the application and have all the textboxes filled with the contents of the TestPerson1 BO. Moreover, we expect to be able to change, say, the first name in a textbox and see this change reflected to the other “first name” textboxes as well:

Download Solution 1(51,56 kb)


Remark #1.

Does the User Control really needs to carry the reference to the Business Object in the Property BoundPerson?

The answer is that in cases where the User Control is just used as a way of grouping visual elements that are bound to a single business object (like our case), this is not necessary. We could completely omit the BoundPerson property and use the following code in the window’s constructor:

public WindowPersonsEdit()

{

    InitializeComponent();

    Person TestPerson1 = new Person() { FirstName = "Ios", LastName = "Panlos" };

    this.UCPersonEdit1.DataContext = TestPerson1;

    this.UCPersonEdit2.DataContext = TestPerson1;

    this.UCPersonEdit3.DataContext = TestPerson1;

}


As you can see we play with the DataContext inheritance. That is, the user control has its data context property set to the business object. The StackPanel in the User Control does not have any DataContext property set and neither have the two TextBoxes. Therefore the first available DataContext property for the User Control’s bindings in the textboxes is the one supplied by the user control itself:

 


Of course in cases where the BO is used inside the User Control for some other calculations this approach cannot be applied.

Download Solution 2(50,87 kb)

Now, let us forget Remark #1 and return to the case where we actually need the BoundPerson BO reference there. And we want this BoundPerson property to be data bound to a Business Object when we use the control.

For testing purposes our goal is as follows:


Meaning that we want our BoundPerson property to be bound to a Person object in the main Window. Note that in normal circumstances the User Control handles the visualization of a BO which is part of a larger BO in the Window. The code-behind file for the Window looks now like that:

private Person _thisPerson = new Person() { FirstName = "Ios", LastName = "Panlos" };

public Person ThisPerson

{

    get { return _thisPerson; }

    set { _thisPerson = value; }

}

public WindowPersonsEdit()

{

    InitializeComponent();

    this.UCPersonEdit1.DataContext = this;

    this.UCPersonEdit2.DataContext = this;

    this.UCPersonEdit3.DataContext = this;

}


And we modify XAML as follows:

<StackPanel>

     <local:UCPersonEdit x:Name="UCPersonEdit1" BoundPerson="{Binding Path=ThisPerson}" Margin="4"/>

     <local:UCPersonEdit x:Name="UCPersonEdit2" BoundPerson="{Binding Path=ThisPerson}" Margin="4"/>

     <local:UCPersonEdit x:Name="UCPersonEdit3" BoundPerson="{Binding Path=ThisPerson}" Margin="4"/>

 </StackPanel>


But the dreadful blue curly line appears to haunt us just below the Binding and this is due to the fact that in WPF Data Binding works only with Dependency Properties. Therefore we need to modify BoundPerson and make it a dependency property.

No problem a little googling for DPs and there it is:

public static readonly DependencyProperty BoundPersonProperty =

    DependencyProperty.Register("BoundPerson", typeof(Person), typeof(UCPersonEdit),

    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnBoundPersonChanged)));

public Person BoundPerson

{

    get { return (Person)GetValue(BoundPersonProperty); }

    set { SetValue(BoundPersonProperty,value); }

}

public UCPersonEdit()

{

    InitializeComponent();

}


Ok. Lots of changes. First there is no need for the private field. Second the BoundPerson property is now a mere accessor for the actual dependency property. But wait a second, what is that OnBoundPersonChanged definition there? Well, if you remember before, whenever the _boundPerson field was set we were also setting the DataContext of the User Control to point to the new _boundPerson. We cannot do the same in the present setter since we have no idea who is going to set the dependency property. In other words, the property BoundPerson is only going to be used from out code. WPF will propably be using SetValue directly. Therefore we cannot place the DataContext there.

We need to place it in a method that gets called whenever the dependency property is changed and this is all about the OnBoundPersonChanged callback. Actually, the implementation is as follows:

public static void OnBoundPersonChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)

{

    (sender as UCPersonEdit).DataContext = args.NewValue;

}


Splendid! Now everything should be working. The project compiles. We run it and all TextBoxes are empty. There is something missing. The Binding points to the BO ThisPerson. The DataContext of the UserControl is set to the Window so the binding should be able to find ThisPerson. So, when the binding provides ThisPerson, the OnBoundPersonChanged is called to set the DataContext of the UserControl to ThisPerson, in order for the FirstName and LastName paths to find it. But wait a second? Did we just set the UserControl’s DataContext twice? That is exactly what we did by mistake and now nothing works.

In fact, the solution is quite simple. We will provide the DataContext of the StackPanel in the callback:

public static void OnBoundPersonChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)

{

    (sender as UCPersonEdit).StackPanelPersonInfo.DataContext = args.NewValue;

}


Test it now and it works.

So keep in mind that Data Binding works only with dependency properties, dependency properties do not support the INotifyPropertyInterface (if someone can even say that) and DataContext inheritance may be a blessing in terms of creating simple code but it turns into a curse when debugging data bindings.

Download Solution 3 (53,79 kb)

kick it on DotNetKicks.com

Tags:

Powered by BlogEngine.NET 1.5.0.7

Programming Blogs - BlogCatalog Blog Directory Add to Technorati Favorites

MVP Award

Ioannis Panagopoulos





This blog is using BlogEngine.Net and is hosted in the hoster below. I have not experienced any problems installing BlogEngine.Net in the host and I am satisfied with the host's response times. Therefore I recommend it.


DiscountASP Add