Search code examples
c#wpfxamlmvvmstyles

Change style in ModelView (MVVM + WPF)


I have an application developed in WPF using the MVVM pattern (MVVM Light Toolkit).

So far, I had no problems, until it is time to change at runtime the style associated with some of my controls, a set of MenuItems. (They can have up to three different styles).

If I was not working with MVVM I could solve it using the command:

MenuElement_Left4.Style = (Style)FindResource("MenuButtonTabsLeft");

But because I want do it completely in MVVM, I've done these tests to achieve that:

1) Try to change the style with a binding (this has not worked):

<MenuItem x:Name="MenuElement_Left4" Header="Test" Style="{Binding SelectedStyle}">

And in the ViewModel:

public string SelectedStyle
    {
       get { return this.selectedStyle; }
       set { this.selectedStyle = value; 
             RaisePropertyChanged("SelectedStyle");
           }
    }
    private string selectedStyle;

2) Change the style with DataTrigger (this has not worked too. Raises an exception (Style Trigger to Apply another Style)):

<MenuItem.Style>
    <Style TargetType="{x:Type MenuItem}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=TestStyle}" Value="True">
                <Setter Property="Style" Value="{StaticResource  MenuButtonTabsLeftArrow}"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=TestStyle}" Value="False">
                <Setter Property="Style" Value="{StaticResource  MenuButtonTabsLeft}"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</MenuItem.Style>

At the end, I managed to solve it, using a Combox using the following code (I only use the ComboBox to change the style of the MenuItems so it is invisible). Got the idea from (How can I change an elements style at runtime?):

<MenuItem x:Name="MenuElement_Left4" Header="Test" Style="{Binding ElementName=AvailableStyles, Path=SelectedItem.Tag}">
<ComboBox Name="AvailableStyles" SelectedIndex="{Binding AvailableStylesIndex}" Visibility="Collapsed">
    <ComboBoxItem Tag="{x:Null}">None</ComboBoxItem>
    <ComboBoxItem Tag="{StaticResource MenuButtonTabsLeftArrow}">MenuButtonTabsLeftArrow</ComboBoxItem>
    <ComboBoxItem Tag="{StaticResource MenuButtonTabsLeft}">MenuButtonTabsLeft</ComboBoxItem>
</ComboBox>

And in my ViewModel:

public int AvailableStylesIndex
{
    get { return this.availableStylesIndex; }
    set
    {
        this.availableStylesIndex = value;
        RaisePropertyChanged("AvailableStylesIndex");
    }
}

I'd rather use a cleaner way. Any suggestions? A piece of code would be very helpful.


Solution

  • Since you are keeping your styles in your resources, cleaner approch would be to use IMultiValueConverter's with you first approch something like this:

    ViewModel

    public string SelectedStyle
    {
       get { return this.selectedStyle; }
       set { this.selectedStyle = value; 
             RaisePropertyChanged("SelectedStyle");
           }
    }
    private string selectedStyle;
    

    Xaml:

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

    In the converter find the style you want and apply it

    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();
        }
    }
    

    Taken out from Steven Robbins's answer in this post