Binding WPF Style

This may be old news but it is exciting to me so I want to document it. Often times when working with the MVVM/Presentation Model Pattern you want to apply a style based on the state of your Model/Business Object. For example if a Business Object ‘IsNew’ you may want to apply a editable style to a TextBox control so the user can supply some data. Yet when the same Business Object ‘IsOld’ you may want disable a field preventing any data entry. My first thought was to use a converter to determine the style in a binding statement similar to this.

<TextBox Style={Binding Path=ViewModel.Customer, Converter={StaticResource StyleConverter}}/>

When data binding does its thing the Convert method will be passed the Customer Business Object. We can then inspect the Business Object to determine what style to apply to the TextBox. The rub however comes when we try to locate the style we want to apply. See code below.

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var customer = value as Customer;
            if(customer != null && customer.IsNew)
            {
                //Locate and return the EditableTextboxStyle
                //Wait!!!! How?!!?
                return null;
            }
            return null;
        }

The crux of the problem is that most often styles exist as resources of your UserControl/Window/Page. In order to resolve these resources the converter will need a reference to the FrameworkElement that contains said resources. So how about this?.

<TextBox Style={Binding Path=ViewModel.Customer, Converter={StaticResource StyleConverter}}, ConverterParameter={Binding Path=DataContext}/>

And the converter….

           /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var customer = (Customer)value;
            var viewModel = (ViewModel)parameter;
            var view = viewModel.View;
            var fe = (FrameworkElement)view;

            if (customer != null && customer.IsNew)
            {
                return fe.TryFindResource("EditableTextboxStyle");
            }

            return fe.TryFindResource("NonEditableTextboxStyle");
        }

Though this seems like it may work(not really) there is one major issue that we run into. The ConverterParameter property in the binding statement is a CLR property that is it is not a DependencyProperty and because of this fact it cannot be data bound!

So just to restate the problem because most of the time I forget what problem I am actually trying to solve. I want a specific style to be applied to a TextBox based on the state of my Customer Business Object. For a converter to do this it needs a reference to the both the Business Object(to interrogate its state) as well as the View(To locate resources i.e. styles). So how can we pass two data bound objects to a converter? Well you cant! Not without some craziness.

Thankfully we have the Multibinding

I am not going to explain what you can read for yourself in the above article. I will however give you the code that did what I wanted to do 🙂 I am Felling a bit lazy tonight!

 <TextBox>
        <TextBox.Style>
            <MultiBinding Converter="{StaticResource StyleConverter}">
                <Binding Path="Customer"/>
                <Binding Path="DataContext"/>
            </MultiBinding>
        </TextBox.Style>
    </TextBox>

And the converter… When we multibinding we also have to use a multiconverter. So basically your converter has to implement IMultiConverter instead of IValueConverter. The only difference is that the first parameter of the Convert method is an array of objects. And in my case the first element in the array is a Customer object and the second a ViewModel! See below.

           /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object Convert(object [] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var customer = (Customer)value;
            var viewModel = (ViewModel)parameter;
            var view = viewModel.View;
            var fe = (FrameworkElement)view;

            if (customer != null && customer.IsNew)
            {
                return fe.TryFindResource("EditableTextboxStyle");
            }

            return fe.TryFindResource("NonEditableTextboxStyle");
        }

Like I said in the beginning this is far from groundbreaking but I thought it was worth a short post!

Advertisements
Tagged

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Advertisements
%d bloggers like this: