Archive

Posts Tagged ‘Databinding’

Data Binding and StringFormat in Silverlight

February 14, 2011 5 comments

Sometimes, when you are designing screens for Silverlight, it is nice to use the StringFormat feature to get the values to show up correctly. This paradigm only works when you are using strongly typed objects. Consider the following example of a strongly typed model:

public class TypedEntity : INotifyPropertyChanged
{
    #region Properties

    private string _employeeName;
    public string EmployeeName
    {
        get { return _employeeName; }
        set
        {
            _employeeName = value;
            OnPropertyChanged(() => EmployeeName);
        }
    }

    private DateTime _startDate;
    public DateTime StartDate
    {
        get { return _startDate; }
        set
        {
            _startDate = value;
            OnPropertyChanged(() => StartDate);
        }
    }

    private double _salary;
    public double Salary
    {
        get { return _salary; }
        set
        {
            _salary = value;
            OnPropertyChanged(() => Salary);
        }
    }

    private double _numberOfDependents;
    public double NumberOfDependents
    {
        get { return _numberOfDependents; }
        set
        {
            _numberOfDependents = value;
            OnPropertyChanged(() => NumberOfDependents);
        }
    }

    #endregion

    #region INotifyPropertyChanged Members

	// ...

    #endregion
};

As you can see, it uses the correct corresponding type so that StringFormat will as expected. Now let’s look at the corresponding Xaml:

        <TextBlock Grid.Row="0" Grid.Column="3" VerticalAlignment="Center" Text="Using StringFormat strongly typed" Grid.ColumnSpan="2" FontSize="16" />
        <TextBlock Grid.Row="1" Grid.Column="3" VerticalAlignment="Center" Text="Employee Name:" />
        <TextBlock Grid.Row="2" Grid.Column="3" VerticalAlignment="Center" Text="Start Date:" />
        <TextBlock Grid.Row="3" Grid.Column="3" VerticalAlignment="Center" Text="Salary:" />
        <TextBlock Grid.Row="4" Grid.Column="3" VerticalAlignment="Center" Text="Number of Dependents:" />
        <TextBox Grid.Row="1" Grid.Column="4" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding TypedEntity.EmployeeName, Mode=TwoWay}" />
        <TextBox Grid.Row="2" Grid.Column="4" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding TypedEntity.StartDate, Mode=TwoWay, StringFormat=d}" />
        <TextBox Grid.Row="3" Grid.Column="4" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding TypedEntity.Salary, Mode=TwoWay, StringFormat=c}" />
        <TextBox Grid.Row="4" Grid.Column="4" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding TypedEntity.NumberOfDependents, Mode=TwoWay, StringFormat=n0}" />

This will render the following output:

Now let’s see where this falls down when using a non-strongly typed object model. Before you start saying, why don’t you plan ahead and make sure that you are data binding to strongly typed objects, there are scenarios when we have an object or table that acts as a property bag for clients in our applications. We still want to provide them with formatting but it is not as intuitive as using what we currently have. Let’s first look at our mode:

public class Entity : INotifyPropertyChanged
{
    #region Properties

    private string _employeeName;
    public string EmployeeName
    {
        get { return _employeeName; }
        set
        {
            _employeeName = value;
            OnPropertyChanged(() => EmployeeName);
        }
    }

    private string _startDate;
    public string StartDate
    {
        get { return _startDate; }
        set
        {
            _startDate = value;
            OnPropertyChanged(() => StartDate);
        }
    }

    private string _salary;
    public string Salary
    {
        get { return _salary; }
        set
        {
            _salary = value;
            OnPropertyChanged(() => Salary);
        }
    }

    private string _numberOfDependents;
    public string NumberOfDependents
    {
        get { return _numberOfDependents; }
        set
        {
            _numberOfDependents = value;
            OnPropertyChanged(() => NumberOfDependents);
        }
    }

    #endregion

    #region INotifyPropertyChanged Members

	// ...

    #endregion
};

As you can see, the model is basically the same with one exception, all of the properties are of type string. Here is the Xaml using the StringFormat paradigm:

<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Text="Using StringFormat" Grid.ColumnSpan="2" FontSize="16" />
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Text="Employee Name:" />
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Text="Start Date:" />
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Text="Salary:" />
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Text="Number of Dependents:" />
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.EmployeeName, Mode=TwoWay}" />
<TextBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.StartDate, Mode=TwoWay, StringFormat=d}" />
<TextBox Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.Salary, Mode=TwoWay, StringFormat=c}" />
<TextBox Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.NumberOfDependents, Mode=TwoWay, StringFormat=n0}" />

Here is what the output would be:

As you can, the data binding ignored the StringFormat. This is a problem. What if we fallback and use a ValueConverter? Let’s take a look at the code:

/// <summary>
/// Sample usage:
/// Text="{Binding ProjectBudget, Mode=TwoWay, Converter={StaticResource StringFormatConverter}, ConverterParameter='\{0:C2\}'}"
/// </summary>
public class StringFormatConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (parameter != null)
        {
            string formatString = parameter.ToString();
            if (!string.IsNullOrEmpty(formatString))
            {
                string strValue = value as string;
                if (strValue != null && !string.IsNullOrWhiteSpace(strValue))
                {
                    return String.Format(formatString, strValue);
                }
            }
        }

        return value.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }

    #endregion
};

So basically, this is trying to do what we get in our Xaml natively but now using the String.Format method. Let’s look at our modified Xaml to see how this looks:

<TextBlock Grid.Row="5" Grid.Column="0" VerticalAlignment="Center" Text="Using a ValueConverter" Grid.ColumnSpan="2" FontSize="16" Margin="0,10,0,0" />
<TextBlock Grid.Row="6" Grid.Column="0" VerticalAlignment="Center" Text="Employee Name:" />
<TextBlock Grid.Row="7" Grid.Column="0" VerticalAlignment="Center" Text="Start Date:" />
<TextBlock Grid.Row="8" Grid.Column="0" VerticalAlignment="Center" Text="Salary:" />
<TextBlock Grid.Row="9" Grid.Column="0" VerticalAlignment="Center" Text="Number of Dependents:" />
<TextBox Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.EmployeeName, Mode=TwoWay}" />
<TextBox Grid.Row="7" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.StartDate, Mode=TwoWay, Converter={StaticResource StringFormatConverter}, ConverterParameter='\{0:d\}'}" />
<TextBox Grid.Row="8" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.Salary, Mode=TwoWay, Converter={StaticResource StringFormatConverter}, ConverterParameter='\{0:c\}'}" />
<TextBox Grid.Row="9" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.NumberOfDependents, Mode=TwoWay, Converter={StaticResource StringFormatConverter}, ConverterParameter='\{0:n0\}'}" />

Now that wasn’t too bad, let’s look and see what the output gives us:

As you can see, this doesn’t work either. It turns out that String.Format relies on the underlying type in order for it to work correctly.

Well, thankfully, .NET comes to the rescue and we get the following converter:

/// <summary>
/// Sample usage:
/// Text="{Binding Budget, Mode=TwoWay, Converter={StaticResource StringFormatConverterWithChangeType}, ConverterParameter='\{0:C2\}'}"
/// </summary>
public class StringFormatConverterWithChangeType : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value != null)
        {
            // Arbitrarily remove dollar sign.
            value = value.ToString().Replace("$", "");
        }

        if (parameter != null)
        {
            string formatString = parameter.ToString();
            if (!string.IsNullOrEmpty(formatString))
            {
                string strValue = value as string;
                if (strValue != null && !string.IsNullOrWhiteSpace(strValue))
                {
                    switch (formatString)
                    {
                        case "{0:n}":   // Number.
                        case "{0:n0}":  // Whole Number.
                        case "{0:c}":   // Currency.
                        case "{0:c0}":  // Whole currency.
                        case "{0:C2}":  // Whole currency.
                            return String.Format(culture, formatString, System.Convert.ChangeType(value, typeof(double), null));
                        case "{0:d}":  // Short date.
                        case "{0:D}":   // Long date.
                            return String.Format(culture, formatString, System.Convert.ChangeType(value, typeof(DateTime), null));
                    }
                }
                else
                {
                    return String.Format(culture, formatString, value);
                }
            }
        }

        return value.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }

    #endregion
};

Perhaps on first glance, you don’t see the difference between the two converters but then as you look more carefully, you will see that we are using the System.Convert.ChangeType method to coerce our underlying property to be of a certain type. I will warn you that this solution will not work for all scenarios and you should read up here to get more information.

Here is the Xaml now using the new converter:

<TextBlock Grid.Row="10" Grid.Column="0" VerticalAlignment="Center" Text="Using a ValueConverter with Convert class" Grid.ColumnSpan="2" FontSize="16" Margin="0,10,0,0" />
<TextBlock Grid.Row="11" Grid.Column="0" VerticalAlignment="Center" Text="Employee Name:" />
<TextBlock Grid.Row="12" Grid.Column="0" VerticalAlignment="Center" Text="Start Date:" />
<TextBlock Grid.Row="13" Grid.Column="0" VerticalAlignment="Center" Text="Salary:" />
<TextBlock Grid.Row="14" Grid.Column="0" VerticalAlignment="Center" Text="Number of Dependents:" />
<TextBox Grid.Row="11" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.EmployeeName, Mode=TwoWay}" />
<TextBox Grid.Row="12" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.StartDate, Mode=TwoWay, Converter={StaticResource StringFormatConverterWithChangeType}, ConverterParameter='\{0:d\}'}" />
<TextBox Grid.Row="13" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.Salary, Mode=TwoWay, Converter={StaticResource StringFormatConverterWithChangeType}, ConverterParameter='\{0:c\}'}" />
<TextBox Grid.Row="14" Grid.Column="1" HorizontalAlignment="Left" Width="125" Margin="5,5,0,0" Text="{Binding Entity.NumberOfDependents, Mode=TwoWay, Converter={StaticResource StringFormatConverterWithChangeType}, ConverterParameter='\{0:n0\}'}" />

And finally, here is the output we want:

So the moral of the story is that if you have some screen that needs to be dynamic and you don’t know ahead of time what user is going to use the property for, you may want to go this route and let your value converter do the parsing. I have a designer screen that allow users to bind to a property bag of strings but they can define what “type” they want to use the property for. By doing this, I can generate the correct Xaml on the fly and render it and bind to it as if it were strongly typed.

You can get download a sample project from here.

Also, you can read more on the syntax for formatting here and here.

Hope this helps….

DomainDataSource Changes – No more ControlParameter

May 26, 2010 3 comments

DomainDataSource Changes – No more ControlParameter

I have been slowly going through all of my existing Silverlight 3 modules that used RIA Services on top of Linq2Sql and I have been porting to Silverlight 4 using WCF RIA Services on top of Linq2Entities. One of the things that I encountered is that the ControlParameter is no longer available. This is actually a good thing since you can now accomplish the same thing with Databinding.

Here is an example of some Xaml where I had previously used the ControlParameter object.

<UserControl
    x:Class="SampleView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:riaCtls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls" 
    xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Ria.Controls"
    >
    <Grid x:Name="LayoutRoot">
            
        <riaCtls:DomainDataSource x:Name="dds" QueryName="GetSl_d_DatabasesByAllButUserId">
            <riaCtls:DomainDataSource.QueryParameters>
                <riaData:ControlParameter ParameterName="userId"
                    ControlName="txtUserId"
                    PropertyName="Text" />
            </riaCtls:DomainDataSource.QueryParameters>
        </riaCtls:DomainDataSource>
        <TextBox x:Name="txtUserId" />
        ...
    </Grid>
</UserControl>

Now with the release of WCF RIA Services, you would write your Xaml as shown in the following example:

<UserControl
    x:Class="SampleView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:riaCtls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices" 
    >
    <Grid x:Name="LayoutRoot">
            
        <riaCtls:DomainDataSource x:Name="dds" QueryName="GetSl_d_DatabasesByAllButUserId">
            <riaCtls:DomainDataSource.QueryParameters>
                <riaCtls:Parameter ParameterName="userId" 
                    Value="{Binding ElementName=txtUserId, Path=Text}" />
            </riaCtls:DomainDataSource.QueryParameters>
        </riaCtls:DomainDataSource>
        <TextBox x:Name="txtUserId" />
        ...
    </Grid>
</UserControl>

This adds a lot of extensibility and since we are just using databinding, we can now do binding to controls or bind to any property exposed on the ViewModel.

Hope this helps…

Databinding and Triggers – Part III

May 14, 2010 2 comments

This is our final post in a series to provide a simple yet effective user enhancement. Without happy customers, we would be out of business :). We left our las post looking at a set of Xaml:

<i:Interaction.Triggers>
    <int:KeyTrigger Key="F5">
        <int:ExecuteDataMethod Method="RunQuery" />
    </int:KeyTrigger>
    <int:KeyTrigger Key="F5"  Modifiers="Control">
        <int:ExecuteDataMethod Method="ParseQuery" />
    </int:KeyTrigger>
</i:Interaction.Triggers>

The culprit in question is the ExecuteDataMethod object. Let’s take a look and see how that is implemented:

/// <summary>
/// This trigger looks up the VisualTree from AssociatedObject and finds the
/// first UserControl and takes its Datacontext.  It then takes
/// the Method property and invokes it when ever the trigger is fired.
/// Also, you cannot overload methods or it will throw an execption.
/// </summary>
public class ExecuteDataMethod : TriggerAction<FrameworkElement>
{
    private FrameworkElement _element = null;

    #region Method
    /// <summary>
    /// This property the method name to invoke when the trigger is fired.
    /// </summary>
    public string Method
    {
        get { return (string)GetValue(MethodProperty); }
        set { SetValue(MethodProperty, value); }
    }
    public static readonly DependencyProperty MethodProperty =
        DependencyProperty.Register("Method",
        typeof(string), typeof(ExecuteDataMethod), null);

    #endregion

    #region UserState
    /// <summary>
    /// The UserState provides a means to supply state information.
    /// </summary>
    public string UserState
    {
        get { return (string)GetValue(UserStateProperty); }
        set { SetValue(UserStateProperty, value); }
    }
    public static readonly DependencyProperty UserStateProperty =
        DependencyProperty.Register("UserState", typeof(string),
        typeof(ExecuteDataMethod), null);

    #endregion

    #region Overrides
    protected override void Invoke(object parameter)
    {
        // Get the root of the UI.
        if (_element == null)
        {
            _element = Utilities.VisualTreeUtility
                .FindAncestor<UserControl>(this.AssociatedObject);
            if (_element == null && this.AssociatedObject.DataContext != null)
                _element = this.AssociatedObject;
        }

        if (_element != null && _element.DataContext != null)
        {
            object bindingTarget = _element.DataContext;
            MethodInfo method = null;
            try
            {
                method = bindingTarget.GetType().GetMethod(this.Method);
            }
            catch (AmbiguousMatchException ex)
            {
                throw new InvalidOperationException(
                  "Method name cannot be overloaded!", ex);
            }

            if (method != null)
            {
                ParameterInfo[] parameters = method.GetParameters();
                if (parameters.Length == 0)
                {
                    method.Invoke(bindingTarget, null);
                }
                else if (parameters.Length == 1)
                {
                    if (UserState != null &&
                        parameters[0].ParameterType.IsAssignableFrom(
                        this.UserState.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] { UserState });
                    }
                    else if (this.AssociatedObject != null &&
                             parameters[0].ParameterType.IsAssignableFrom(
                                 this.AssociatedObject.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] { AssociatedObject });
                    }
                }
                else if (parameters.Length == 2 && this.AssociatedObject != null && 
                    parameter != null)
                {
                    if (parameters[0].ParameterType.IsAssignableFrom(
                        this.AssociatedObject.GetType()) && 
                        parameters[1].ParameterType.IsAssignableFrom(
                        parameter.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] {
                            this.AssociatedObject, parameter 
                        });
                    }
                }
                else if (parameters.Length == 3 && 
                    this.AssociatedObject != null && parameter != null 
                    && this.UserState != null)
                {
                    if (parameters[0].ParameterType.IsAssignableFrom(
                        this.AssociatedObject.GetType()) &&
                        parameters[1].ParameterType.IsAssignableFrom(
                        parameter.GetType()) &&
                        parameters[2].ParameterType.IsAssignableFrom(
                        this.UserState.GetType()))
                    {
                        method.Invoke(bindingTarget, new object[] {
                            this.AssociatedObject, parameter, UserState 
                        });
                    }
                }
            }
        }
    }
    #endregion
};

This trigger exposes two DependencyProperties: Method and UserState. The Method property allow you to specify the method name that you want to be fired and the UserState property allows you to provide any custom information that you would like.

The Invoke method is where all the exciting stuff lives. The first thing it tries to do is grab the FrameworkElement and store it in a class level variable. It tries to walk the VisualTree and looks for the host control which is a UserControl. If it cannot find a UserControl than it makes sure that the current AssociatedObject is not null and has a DataContext.

Once we have our _elmeent object hydrated, we then use Reflection to try and find a method on the object with the same name as the one provided in our DependencyProperty.

If we have a valid method, we then check and see what parameters are associated with the method. If there are no parameters, we can go ahead and invoke the method immediately. Otherwise, we have currently support one, two , or three parameters. One parameter will either call the method passing in the UserState or passing in the AssociatedObject itself. It also performs a check to be sure that type of each object can be passed in as the parameter.

Two parameters will pass in the AssociatedObject as the first parameter and the parameter object that is passed into the Invoke method. Again a check is performed to be sure that the type of each object can be passed in as the correct corresponding parameter.

Three parameters will do the same as two parameters but it will also pass in the UserState object as the third parameter.

Here is what the ViewModel method looks like for the Xaml sample provided above:

public void ParseQuery()
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery()
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

These methods could also look like this with the UserState passed in:

public void ParseQuery(object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery(object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

They could also look like this as you have seen in normal EventHandlers:

public void ParseQuery(object sender, EventArgs e)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery(object sender, EventArgs e)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

Finally, they could look like this with the added UserState passed in as well:

public void ParseQuery(object sender, EventArgs e, object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("parse");
}

public void RunQuery(object sender, EventArgs e, object userState)
{
    _eventAggregator.GetEvent(Of QueryManagerClickEvent)().Publish("run");
}

I typically use the UserState when I have a generic method in my ViewModel and I need to switch on what control or business logic needs to be performed.

Hopefully this gives you some ideas and options as to how you would go about providing some user enhancements. The real power of these Triggers is that you write them once and then you can use them everywhere. It adds to your overall consistency of your application and provides better maintenance.

Categories: English Tags: , ,

Databinding and Triggers – Part II

In our last post, we looked at creating a TextBoxBindingTrigger in order to fire updates on the TextChanged event instead of LostFocus. This time around we are going to be looking at another Trigger that will facilitate handling key press events. Let’s go ahead and take a look at the class that provides this functionality:

public class KeyTrigger : TriggerBase<FrameworkElement>
{
    private FrameworkElement _root;

    #region Key
    public Key Key
    {
        get { return (Key)GetValue(KeyProperty); }
        set { SetValue(KeyProperty, value); }
    }
    public static readonly DependencyProperty KeyProperty =
        DependencyProperty.Register("Key", typeof(Key),
        typeof(KeyTrigger), new PropertyMetadata(Key.None));
    #endregion

    #region Modifiers
    public ModifierKeys Modifiers
    {
        get { return (ModifierKeys)GetValue(ModifiersProperty); }
        set { SetValue(ModifiersProperty, value); }
    }
    public static readonly DependencyProperty ModifiersProperty =
        DependencyProperty.Register("Modifiers", typeof(ModifierKeys),
        typeof(KeyTrigger), new PropertyMetadata(ModifierKeys.None));
    #endregion

    private FrameworkElement FindRoot(FrameworkElement element)
    {
        FrameworkElement parent = element;
        while (parent != null)
        {
            element = parent;
            parent = element.Parent as FrameworkElement;
        }

        return element;
    }

    #region Overrides
    protected override void OnAttached()
    {
        base.OnAttached();

        _root = FindRoot(this.AssociatedObject);
        _root.KeyDown += HandleHostKeyDown;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        _root.KeyDown -= HandleHostKeyDown;
    }
    #endregion

    #region Events
    private void HandleHostKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == this.Key && Keyboard.Modifiers == this.Modifiers)
        {
            this.InvokeActions(e);
        }
    }
    #endregion

};

This class exposes two DependencyProperties: Key and Modifiers. The allow you to specify what key and modifier(s) you want to provide a response. The FindRoot method takes in the underlying FrameworkElement and attempts to walk up the VisualTree to find the root. The overrides OnAttahced() and OnDetaching() provide us with the ability to add an event handler to the KeyDown event. This in turn call the HandleHostKeyDown event handler with in turn fires our newly created trigger.

Let take a look and see how this is used in Xaml:

<i:Interaction.Triggers>
    <int:KeyTrigger Key="F5">
        <int:ExecuteDataMethod Method="RunQuery" />
    </int:KeyTrigger>
    <int:KeyTrigger Key="F5"  Modifiers="Control">
        <int:ExecuteDataMethod Method="ParseQuery" />
    </int:KeyTrigger>
</i:Interaction.Triggers>

Finally we can see that when an F5 or Ctl+F5 is pressed, we fire the RunQuery or ParseQuery methods correspondingly. As you have noticed, there is one more Trigger that is being used to allow us to call a given method on the DataContext or your ViewModel of your control. We will look at this Trigger in the following post.

Categories: English Tags: , ,

Databinding and Triggers – Part I

As I develop Silverlight applications, one of my main goals is to maintain a high user experience. I want the application to be simple, intuitive, and very responsive. With that said, I ran into a problem recently when I had published a new feature. I was trying to enhance the user’s experience by providing hot keys for common scenarios while out of the browser. The enhancement was on a screen that allowed users to enter in a query. The two key presses provided an alternative of needing to use the mouse. When a user clicked on the Play (F5) button, the screen would submit the query to the service and the server would return a resultset. When the user clicked on the Parse (Ctl+F5) button, the screen would submit the query to the service and server would return whether or not the query was valid.

Now comes the fun part. The default behavior for updating the binding on TextBox controls is on the LostFocus event. When a user clicks on a button this works perfectly fine but if a user presses the hot key(s), the control never loses focus and therefore the actual query is never pushed onto the ViewModel. Here is where Triggers come to the rescue.

The following code is a sample of a Trigger that will allow us to fire the UpdateSource method on the binding:

public class TextBoxBindingTrigger : TriggerAction<TextBox>
{
    protected override void Invoke(object parameter)
    {
        BindingExpression binding = 
            AssociatedObject.GetBindingExpression(TextBox.TextProperty);
        if (binding != null)
        {
            binding.UpdateSource();
        }
    }
};

As you can see, there really isn’t a lot of logic required to allow us to call the UpdateSource() method on the underlying binding. Obviously, in order for this to work, we do need to have a binding defined on the TextBox control.

Here is the Xaml that uses this newly created trigger:

<TextBox Grid.Row="2" TextWrapping="Wrap" 
    Text="{Binding QueryManagerQuery, Mode=TwoWay}" 
    AcceptsReturn="True">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="TextChanged">
            <int:TextBoxBindingTrigger />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

This now allows us to markup certain TextBox controls to fire updates on TextChanged instead of on LostFocus.

The final piece to this puzzle is how to respond to a key press and call a method on the ViewModel to respond just like a Button click. We will cover this on next blog post.

Categories: English Tags: , ,