Binding a Button's visibility to a bool value in ViewModel - c#

How do I bind the visibility of a button to a bool value in my ViewModel?
<Button Height="50" Width="50" Style="{StaticResource MyButtonStyle}"
Command="{Binding SmallDisp}" CommandParameter="{Binding}" Cursor="Hand"
Visibility="{Binding Path=AdvancedFormat}" />

Assuming AdvancedFormat is a bool, you need to declare and use a BooleanToVisibilityConverter:
<!-- In your resources section of the XAML -->
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<!-- In your Button declaration -->
<Button
Height="50" Width="50"
Style="{StaticResource MyButtonStyle}"
Command="{Binding SmallDisp}" CommandParameter="{Binding}"
Cursor="Hand" Visibility="{Binding Path=AdvancedFormat, Converter={StaticResource BoolToVis}}"/>
Note the added Converter={StaticResource BoolToVis}.
This is a very common pattern when working with MVVM. In theory you could do the conversion yourself on the ViewModel property (i.e. just make the property itself of type Visibility) though I would prefer not to do that, since now you are messing with the separation of concerns. An item's visbility should really be up to the View.

There's a third way that doesn't require a converter or a change to your view model: use a style:
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
I tend to prefer this technique because I use it in a lot of cases where what I'm binding to is not boolean - e.g. displaying an element only if its DataContext is not null, or implementing multi-state displays where different layouts appear based on the setting of an enum in the view model.

2 way conversion in c# from boolean to visibility
using System;
using System.Windows;
using System.Windows.Data;
namespace FaceTheWall.converters
{
class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Boolean && (bool)value)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Visibility && (Visibility)value == Visibility.Visible)
{
return true;
}
return false;
}
}
}

Since Windows 10 15063 upwards
Since Windows 10 build 15063, there is a new feature called "Implicit Visibility conversion" that binds Visibility to bool value natively - There is no need anymore to use a converter.
(see https://social.technet.microsoft.com/wiki/contents/articles/34846.uwp-compiled-binding-windows-10-anniversary-update.aspx#Implicit_Visibility_conversion).
My code (which supposes that MVVM is used, and Template 10 as well):
<!-- In XAML -->
<StackPanel x:Name="Msg_StackPanel" Visibility="{x:Bind ViewModel.ShowInlineHelp}" Orientation="Horizontal" Margin="0,24,0,0">
<TextBlock Text="Frosty the snowman was a jolly happy soul" Margin="0,0,8,0"/>
<SymbolIcon Symbol="OutlineStar "/>
<TextBlock Text="With a corncob pipe and a button nose" Margin="8,0,0,0"/>
</StackPanel>
<!-- in companion View-Model -->
public bool ShowInlineHelp // using T10 SettingsService
{
get { return (_settings.ShowInlineHelp); }
set { _settings.ShowInlineHelp = !value; base.RaisePropertyChanged(); }
}

Generally there are two ways to do it, a converter class or a property in the Viewmodel that essentially converts the value for you.
I tend to use the property approach if it is a one off conversion. If you want to reuse it, use the converter. Below, find an example of the converter:
<ValueConversion(GetType(Boolean), GetType(Visibility))> _
Public Class BoolToVisibilityConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
If value IsNot Nothing Then
If value = True Then
Return Visibility.Visible
Else
Return Visibility.Collapsed
End If
Else
Return Visibility.Collapsed
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Throw New NotImplementedException
End Function
End Class
A ViewModel property method would just check the boolean property value, and return a visibility based on that. Be sure to implement INotifyPropertyChanged and call it on both the Boolean and Visibility properties to updated properly.

This can be achieved in a very simple way
1. Write this in the view.
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Width="50" Height="30">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsHide}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
The following is the Boolean property which holds the true/ false value. The following is the code snippet. In my example this property is in UserNote class.
public bool _isHide = false;
public bool IsHide
{
get { return _isHide; }
set
{
_isHide = value;
OnPropertyChanged("IsHide");
}
}
This is the way the IsHide property gets the value.
userNote.IsHide = userNote.IsNoteDeleted;

In View:
<Button
Height="50" Width="50"
Style="{StaticResource MyButtonStyle}"
Command="{Binding SmallDisp}" CommandParameter="{Binding}"
Cursor="Hand" Visibility="{Binding Path=AdvancedFormat}"/>
In view Model:
public _advancedFormat = Visibility.visible (whatever you start with)
public Visibility AdvancedFormat
{
get{return _advancedFormat;}
set{
_advancedFormat = value;
//raise property changed here
}
You will need to have a property changed event
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged.Raise(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
This is how they use Model-view-viewmodel
But since you want it binded to a boolean, You will need some converter.
Another way is to set a boolean outside and when that button is clicked then set the property_advancedFormat to your desired visibility.

Related

Trigger by ElementName from other UserControl

I have two UserControls(A,B) and I want to get A_TextBox Errors in UserControl A from the UserControl B. Is it possible?
<Usercontrol x:Class="A" (...)>
<TextBox x:name="A_TextBox (...)/>
</Usercontrol>
<Usercontrol x:Class="B" (...)>
(...)
<Controls:A/>
<Button (...)>
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=A_TextBox }" value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
This code causes an error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=A_TextBox '. BindingExpression:Path=(0); DataItem=null; target element is 'Button' (Name=''); target property is 'NoTarget' (type 'Object')
I've created two UserControls, A and B. A has a textbox whose Text is bound to an integer property of the UserControl, and A also has public readonly dependency property HasError. I get an error saying that Validation.HasError can't be data-bound, so I'm updating that property manually in a text-changed event handler. I created and included the Integer property so that I can type "xx" into the textbox and cause Validation.HasError to be true. Anything with working validation will work the same.
In the common parent, I bind A.HasError to B.IsEnabled, via a boolean-negation value converter. I could have written a trigger like yours as well. The advantage of this approach, in addition to the fact that it works, is that the two UserControls don't have to know about each others' internals, and neither one is dependent on the other. In addition, if you want to create nine of these pairs in a ListBox's ItemTemplate, you can do that without any problems.
A.xaml
<TextBox
VerticalAlignment="Top"
TextChanged="IntegerTextBox_TextChanged"
Text="{Binding Integer, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged}"
/>
A.xaml.cs
private void IntegerTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
HasError = Validation.GetHasError(sender as TextBox);
}
public bool HasError
{
get { return (bool)GetValue(HasErrorProperty); }
protected set { SetValue(HasErrorPropertyKey, value); }
}
internal static readonly DependencyPropertyKey HasErrorPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(HasError), typeof(bool), typeof(A),
new PropertyMetadata(false));
public static readonly DependencyProperty HasErrorProperty =
HasErrorPropertyKey.DependencyProperty;
public int Integer
{
get { return (int)GetValue(IntegerProperty); }
set { SetValue(IntegerProperty, value); }
}
public static readonly DependencyProperty IntegerProperty =
DependencyProperty.Register(nameof(Integer), typeof(int), typeof(A),
new PropertyMetadata(0));
MainWindow.xaml
<Window.Resources>
<local:NotConverter x:Key="Not" />
</Window.Resources>
<Grid>
<StackPanel>
<local:A
Integer="34"
x:Name="MyAInstance"
/>
<local:B
IsEnabled="{Binding HasError, ElementName=MyAInstance,
Converter={StaticResource Not}}"
/>
</StackPanel>
</Grid>
MainWindow.xaml.cs
public class NotConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !System.Convert.ToBoolean(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !System.Convert.ToBoolean(value);
}
}

Update DataGrid Background from Binding in ViewModel

Today I found something strange (for me). If I want to update a DataGrid through a property in my ViewModel the Binding wont get notified. The special think here is (I think) the Binding is bound to another object (part of an Collection) not directly the property i change.
I have prepared some sample code for you. But first a little (Depper) explanation.
Of course it is just a sample but here I have a ViewModel with two public Properties (Items and CurrentItem). Items is a ObservableCollection and serves as the ItemsSource of my DataGrid. CurrentItem is a String which serves as indicator for a converter to set the background colour (for the grid).
I add two instances o String to my Collection and after the program is started the behaviour is as expected. The first line is green the second is white (set through the converter and the properties).
But if I change the value of CurrentItem after the program was loaded (lets say through the button) the colours wont update on my Datagrid.
If I create a breakpoint at the beginning of the converter I can see (after the loading process) the converter wont execute again so it has to be a Problem with the Binding. I think the problem is my property which is not part of the items in my Collection. The OnPropertyChange method seems to not trigger the update for the RowStyle properly.
In real life the model class of the collections is not a string and the model class implements INotifyPropertyChange (but I don´t think this is the problem cause i just don´t update anything in the model).
I need this kind of behaviour to visible highlight more rows based on a dynamic indicator (similar to the example). If no one knows a better way I think I will implement some kind of Property in my models and update the property with a method from the ViewModel.
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public static MainWindowViewModel instance = null;
private string _CurrentItem;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
public string CurrentItem
{
get
{
return _CurrentItem;
}
set
{
if (value != _CurrentItem)
{
_CurrentItem = value;
OnPropertyChanged("CurrentItem");
OnPropertyChanged("Items");
}
}
}
public ObservableCollection<String> Items { get; set; }
public MainWindowViewModel()
{
instance = this;
Items = new ObservableCollection<string>();
CurrentItem = "First";
Items.Add("First");
Items.Add("Second");
Items.Add("First");
}
View XAML
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<local:StringToColorConverter x:Key="StringToColorConverter" />
</Window.Resources>
<DockPanel Margin="30">
<Button DockPanel.Dock="bottom" Content="From First to Second" Click="Button_Click" />
<DataGrid IsReadOnly="True" ItemsSource="{Binding Items}" ColumnWidth="*" AutoGenerateColumns="False">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background"
Value="{Binding Converter={StaticResource StringToColorConverter}}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Text" Binding="{Binding}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
Converter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var item = value as string;
if (item == MainWindowViewModel.instance?.CurrentItem)
return "Green";
return "White";
}
So sorry for the long post I hope you can comprehend my problem and of course maybe help me :)
You can involve CurrentItem using an IMultiValueConverter
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource MultiStringToColorConverter}">
<Binding />
<Binding Path="DataContext.CurrentItem"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
The Converter
public class MultiStringToColorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
var item = values[0] as string;
var current = values[1] as string;
if (item == current)
return new SolidColorBrush(Colors.Green);
return new SolidColorBrush(Colors.White);
}
public object[] ConvertBack(object values, Type[] targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Everything is normal !
It is by design.
Your background is bound to the n-th object of the DataGrid.
It is not bound to CurrentItem, so there is no reason the binding updates the n-th line background.
Because you have an ObservableCollection, you could put a IsSelected property in MyItem class
And you should make MyItem ràise a PropertyChanged event on IsSelected property.
Of course MyItem would implement INotifyPropertyChanged
Last, you should change the binding :
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background"
Value="{Binding Path=IsSelected,
Converter={StaticResource BooleanToColorConverter}}" />
</Style>
</DataGrid.RowStyle>
Of course changing the StringToColorConverter into BooleanToColorConvert wouldbe trivial.
Regards

Storing information in ViewModel or using IValueConverter

If I want to change the Background-color of a Button in wpf to red, if the property Amount in my view model is 0 and to green if it is greater than 0, is it better to use a value converter for this, or should I simply implement a custom Background-property in my view model? This Background-property would wrap the Amount-value to a SolidColorBrush, which will be bound to the Background of the Button.
Which way is more straight forward?
Thank you!
I would use a DataTrigger.
Apply the following style to your button.
It has a binding to the Amount property in your view model.
It sets the default background color to 'green' and changes to 'red' if the value of Amount is 0.
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Green" />
<Style.Triggers>
<DataTrigger Binding="{Binding Amount}" Value="0">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
Additional info
You can also check for more than one codition using a MultiDataTrigger.
It looks like this:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{...}, Value="..."/>
<Condition Binding="{...}, Value="..."/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="A" Value="..."/>
<Setter Property="B" Value="..."/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
Check out this article on how to use it.
It seems that for range checking you would need to implement a IValueConverter like mentioned in the other responses or in this answer.
I would do it with Trigger, but Converter is Ok too. But I definitely won't make property Background in ViewModel, because Background is about design, about view so it is better to define it in View
I'd make bool property in viewmodel, which is calculated when Amount is changed:
public bool IsAmountZero
{
get { return Amount == 0; }
}
private int _amount;
public int Amount
{
get { return _amount; }
set
{
_amount = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsAmountZero));
}
}
And then write converter BoolToColorConverter (where colors could be via ConverterParameter somehow).
// in current form it's actually BoolToColorRedGreenConverter
public class BoolToColorConverter : MarkupExtension, IValueConverter
{
public BoolToColorConverter() { }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
var colorFalse = Colors.Green;
var colorTrue = Colors.Red;
if (parameter != null)
{
//...
}
return (bool)value ? colorTrue : colorFalse;
}
throw new InvalidCastException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
use it like this
<Button.Background>
<SolidColorBrush Color="{Binding IsAmountZero, Converter={local:BoolToColorConverter}}" />
</Button.Background>
This would be quite reusable solution.
Otherwise just make converter IntZeroCheckToColorGreenRedConverter, but it will not be very reusable compared to one with bool property.
Idea with Brush property in view model is bad, because viewmodel doesn't realy care about colors. Viewmodel should only contain logic related to model which is then used by view. If you want to simply change color (e.g use Blue instead of Green) - this change has to be done in the view. Therefore bool property and BoolToColorConverter (or BoolToSolidBrushConverter to use directly with Background attribute in xaml) converters.
Setting the Button's background is something view's related i don't thing that setting it from the ViewModel is a good idea, i think that it is much better if you define the Amount property in the ViewModel, define a DataTrigger to check the amount value Against the 0 using a Converter
<Window.Resources>
<YurNs:GreaterThanValConverter x:Key="GreaterThanValConverter"/>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding Amount,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Button">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Green"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Amount,Converter={StaticResource GreaterThanValConverter}}" Value="false">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
and the converter
public class GreaterThanValConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int) value > 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
One more thing you may as well consider passing a parameter to the converter to compare against so that your solution would be as customizable as possible.

Add MouseMove event if value is true

I have DataTemplate selector for my ItemsControl and I'd like to achieve something like:
<DataTemplate x:Key="buttonTemplate">
<Button if(someValue = true -> add thisPreviewMouseUp="button_MouseUp") PreviewMouseLeftButtonDown="button_MouseLeftButtonUp" PreviewMouseMove="button_MouseMove" Click="b_Click">
<Button.Content>
<Image Source="sample.png" Height="{Binding height}" Width="{Binding width}" Stretch="Fill" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Button.Content>
<Button.RenderTransform>
<RotateTransform Angle="{Binding angle}" />
</Button.RenderTransform>
</Button>
</DataTemplate>
Users can move, change size of buttons from manager mode, but I don't want to fire this event in normal mode (now there is if(_fromWhere == "MANAGER") in mouse_move event)
Any idea how can I make it work?
Thanks!
Might help.
Mvvm Approach
I think easy way of doing this , Enable the Event only when a condition is true.
what i'm doing here i Execute a a Command Named DropUser when Event Drop Occurs. I used mvvmlight for easy commanding . you can use ICommand Wpf default commanding reference
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mvvmlight="http://www.galasoft.ch/mvvmlight"
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Drop">
<mvvmlight:EventToCommand
Command="{Binding DataContext.DropUser,
PassEventArgsToCommand="True"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
ViewModel
#region RelayCommand
private RelayCommand<DragEventArgs> _dropUser;
public RelayCommand<DragEventArgs> DropUser
{
get
{
return _dropUser ?? (_dropUser = new RelayCommand<DragEventArgs>(DropMethod,canExecute));
}
}
private bool canExecute(DragEventArgs arg)
{
// check your condition return true . Command is only work when you return true.
}
#endregion
// Method Will Fire here and do action here
private void DropMethod(DragEventArgs eventArgs)
{
if (eventArgs != null)
{
}
}
what you need is like this.
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="PreviewMouseLeftButtonDown">
<mvvmlight:EventToCommand
Command="{Binding CommandName,
PassEventArgsToCommand="True"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
Viewmodel
private RelayCommand<RoutedEventArgs> _commandName;
public RelayCommand<RoutedEventArgs>CommandName
{
get
{
return commandName ?? (commandName = new RelayCommand<RoutedEventArgs>(invokeAction, canExecuteMethod));
}
}
private bool canExecuteMethod()
{
check your condition return true
}
private void invokeAction(RoutedEventArgs event)
{
action you want to do
}
I don't know, if it's possible in XAML to set event to be fired or not.
However you can set someValue in Tag property of button and then in fired event check for Tag as proposed in this link.
<Button Tag="{Binding someValue}" ...>
In event handler:
bool someValue = (bool)(((Button)sender).Tag);
Edit 1:
An alternative way would be to get the DataContext of button if someValue is part of DataContext of button:
var dataContext = ((Button)e.OriginalSource).DataContext;
if (dataContext.someValue)
{
...
}
Edit 2:
After a little investigation I found out that it is possible to add MouseMove event depending on someValue. Unfortunately it is unnecessarily complex I think.
<Button Style="{Binding someValue, Converter={StaticResource BoolToButtonStyleConverter}, ConverterParameter={StaticResource MousePreviewEventSetStyle}, Mode=OneWay}" ...>
In Window.Resources define this:
<l:BoolToButtonStyleConverter x:Key="BoolToButtonStyleConverter" />
<Style x:Key="MousePreviewEventSetStyle" TargetType="{x:Type Button}" >
<EventSetter Event="PreviewMouseUp" Handler="PreviewMouseUpClicked" />
</Style>
And then in Application.Resources define this:
<Style x:Key="clearStyle" TargetType="{x:Type Button}" />
After all you have to define:
public class BoolToButtonStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool someValue = (bool)value;
if (someValue)
return parameter;
else
return Application.Current.Resources["clearStyle"];
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Finally set the handler for event:
void PreviewMouseUpClicked(object sender, RoutedEventArgs e)
{
...
}
Memo: Probably there is better way to set "default style" to button that define "clear style".

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.
Since you are keeping your styles in you 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

Categories