Matthew Savage
Matthew Savage

Reputation: 3814

Binding for WPF Styles

I'm trying to create a custom control - a button - which will have multiple styles applied to it depending on the value of a property within the data context.

What I was thinking is using something similar to:

<Button Style="{Binding Path=ButtonStyleProperty, Converter={StaticResource styleConverter}}" Text="{Binding Path=TextProp}" />

And in code... Implement an IValueConverter which does something similar to the code below in the ConvertTo method:

switch(value as ValueEnums)
{
    case ValueEnums.Enum1:
        FindResource("Enum1ButtonStyle") as Style;
    break;

    ... and so on.
} 

However I'm not entirely sure about how to pull out the style object and even if this is possible at all...

What I am doing in the mean time is handling the DataContextChanged event, then attaching a handler to the PropertyChanged event of the object being bound to the button - then running the switch statement in there.

Its not quite perfect but until I can find a better solution it seems like that is what I'll have to use.

Upvotes: 39

Views: 69038

Answers (4)

CHANDRA
CHANDRA

Reputation: 4928

ViewModel

private Style _dynamicStyle = (Style)Application.Current.FindResource("Style1");
        public Style DynamicStyle
        {
            get { return _dynamicStyle; }
            set
            {
                _dynamicStyle = value;
                OnPropertyChanged("DynamicStyle");
            }

        }

public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Implement a property in your ViewModel and then dynamically change style where ever you want like below.

DynamicStyle=(Style)Application.Current.FindResource("Style2");// you can place this code where the action get fired

View

Then set DataContext value and then implement the following code in your view

    <Button Style="{Binding DynamicStyle,Mode=TwoWay}"/>

Upvotes: 4

si618
si618

Reputation: 16848

For those of us who can't use multi value converter (I'm looking at you SL4 and WP7:), thanks to Steven's answer I found a way using an ordinary value converter.

The only assumption is the style value is contained within the property of the style being set.

So if you're using the MVVM pattern then the style value (such as TextSmall, TextMedium, TextLarge) is assumed to be part of the view model, and all you have to do is pass the converter parameter defining the name of style.

For example, say your view model has property:

public string ProjectNameStyle
{
    get { return string.Format("ProjectNameStyle{0}", _displaySize.ToString()); }
}

Application style:

<Application.Resources>
    <Style x:Key="ProjectNameStyleSmall" TargetType="TextBlock">
        <Setter Property="FontSize" Value="40" />
    </Style>
    <Style x:Key="ProjectNameStyleMedium" TargetType="TextBlock">
        <Setter Property="FontSize" Value="64" />
    </Style>
    <Style x:Key="ProjectNameStyleLarge" TargetType="TextBlock">
        <Setter Property="FontSize" Value="90" />
    </Style>

XAML view:

   <TextBlock 
        Text="{Binding Name}"
        Style="{Binding ., Mode=OneWay, Converter={cv:StyleConverter}, ConverterParameter=ProjectNameStyle}">

With your StyleConverter class implementing IValueConverter:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if (targetType != typeof(Style))
    {
        throw new InvalidOperationException("The target must be a Style");
    }

    var styleProperty = parameter as string;
    if (value == null || styleProperty == null)
    {
        return null;
    }

    string styleValue = value.GetType()
        .GetProperty(styleProperty)
        .GetValue(value, null)
        .ToString();
    if (styleValue == null)
    {
        return null;
    }

    Style newStyle = (Style)Application.Current.TryFindResource(styleValue);
    return newStyle;
}

Note that this is WPF code, as the converter is derived from a MarkupExtension as well as IValueConverter, but it will work in SL4 and WP7 if you use static resource and add a bit more leg work as the TryFindResource method doesn't exist.

Hope that helps someone, and thanks again Steven!

Upvotes: 8

Steven Robbins
Steven Robbins

Reputation: 26599

If you want to replace the whole style (rather than just elements of it) then you'll probably be storing those styles in resources. You should be able to do something along the lines of:

<Button>
    <Button.Style>
        <MultiBinding Converter="{StaticResource StyleConverter}">
            <MultiBinding.Bindings>
                <Binding RelativeSource="{RelativeSource Self}"/>
                <Binding Path="MyStyleString"/>
            </MultiBinding.Bindings>
        </MultiBinding>
    </Button.Style>
</Button>

By using a MultiBinding and using Self as the first binding we can then lookup resources in our converter. The converter needs to implement IMultiValueConverter (rather than IValueConverter) and can look something like this:

class StyleConverter : IMultiValueConverter 
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        FrameworkElement targetElement = values[0] as FrameworkElement; 
        string styleName = values[1] as string;

        if (styleName == null)
            return null;

        Style newStyle = (Style)targetElement.TryFindResource(styleName);

        if (newStyle == null)
            newStyle = (Style)targetElement.TryFindResource("MyDefaultStyleName");

        return newStyle;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

It's not something I do very often, but that should work from memory :)

Upvotes: 41

aku
aku

Reputation: 123966

It seems that you need to use DataTrigger class. It allows you to apply different styles to your button based on it's content.

For example following style will change button's background property to red based on value of data context object's property

<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path="Some property"}" 
                     Value="some property value">
            <Setter Property="Background" Value="Red"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

Upvotes: 20

Related Questions