One of my views consists of 5 UserControls that each display data about a certain object. Let's say for example that the view displays the cows our company has, and on the screen cows 1 through 5 are displayed (each in their own UserControl).
What I want to do (but not sure is possible) is to bind the status of a cow to the style used in its respective UserControl. So we have a property status that could be ok, hungry, dead for example. In case the cow is ok I want to display a 'normal' style, if it's hungry I want the background to be red and if it's dead I want the text to be black and the fontsize increased.
I've added a simplified version of what I'm trying to achieve. My knowledge of WPF styles/resource dictionaries is still somewhat limited though.
What I basically want in code
A ViewModel with a Status property
class CowInfoViewModel : Screen
{
public string Name { get; set; }
public string Status { get; set; } //"ok", "hungry", "dead"
}
A View that retrieves a style or resourcedictionary
<UserControl x:Class="WpfModifyDifferentView.Views.CowInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- A reference to a ResourceDictionary with styles, that is bound to the 'Status' property -->
<StackPanel>
<TextBlock x:Name="Name" Text="Cow Name"/>
<TextBlock x:Name="Status" Text="Ok" />
</StackPanel>
</UserControl>
EDIT - Solution:
I did the following using Vale's answer:
In the xaml (reference to the converter):
<UserControl.Resources>
<Converters:CowStyleConverter x:Key="styleConverter" />
</UserControl.Resources>
In the xaml (elements):
<TextBlock x:Name="Name" Text="Cow Name" Style="{Binding Path=Style, ConverterParameter='TextBlockCowName', Converter={StaticResource styleConverter}}" />
The converter (note I left out the checks):
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var status = value.ToString();
var styleName = parameter.ToString();
_resourceDictionary.Source = new System.Uri(string.Format("pack://application:,,,/Resources/ScreenI2Style{0}.xaml", status));
return _resourceDictionary[styleName];
}
Then I created multiple ResourceDictionaries with styles such as:
<Style x:Key="TextBlockCowName" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource SomeBrush}" />
</Style>
You can bind UserControl Style property to Status and use a converter.
<UserControl x:Class="WpfModifyDifferentView.Views.CowInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfModifyDifferentView"
Style="{Binding Path=Status, Converter={StaticResource myConverter}}">
<UserControl.Resources>
<local:MyConverter x:Key="myConverter" />
</UserControl.Resources>
I assume that your converter is in WpfModifyDifferentView directly.
Converter will look like this:
public class MyConverter : IValueConverter {
private ResourceDictionary dictionary;
public MyConverter() {
if (dictionary == null) {
dictionary = new ResourceDictionary();
dictionary.Source = new Uri("pack://application:,,,/WpfModifyDifferentView;Component/Resources/Styles.xaml");
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
switch (value.ToString()) {
case "ok":
return dictionary["myKeyForOkStyle"] as Style;
case "hungry":
return dictionary["myKeyForHungryStyle"] as Style;
case "dead":
return dictionary["myKeyForDeadStyle"] as Style;
default:
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
You need to specify the correct URI of course.
Related
I am having trouble with what seems like a fairly straightforward task. I have a treeview with nodes, and I would like to set the TreeViewItem 'IsExpanded' property to True for the whole treeview if a specific string property 'SearchTerm' of my View Model is not empty. In other words, if string property is not null, IsExpanded value should be True. I have already done this in codebehind but I prefer to do this in XAML for cleanness.
To describe the code below, I created a converter which will convert a null string to 'False' and non-null to 'True'. In my XAML I call this converter when I attempt to bind the string value from the viewmodel in the TreeView ItemContainerStyle. It appears that the converter is never even fired.
My XAML (simplified):
<UserControl.Resources>
<cv:ExpandNodesIfSearchConverter x:Key="ExpandAll">
</cv:ExpandNodesIfSearchConverter>
</UserControl.Resources>
<TreeView Grid.Row="2" x:Name="myTreeView"
ItemsSource="{Binding Sponsors}"
SelectedItemChanged="TreeView_SelectedItemChanged" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- if SearchTerm is not null, use converter to set value to true and expand all nodes -->
<Setter Property="IsExpanded" Value="{Binding Path=SearchTerm, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ExpandAll}}" />
</Style>
</TreeView.ItemContainerStyle>
<!-- TreeView data -->
</TreeView>
My View Model:
public class TreeViewVM : INotifyPropertyChanged
{
private string _searchterm;
public string SearchTerm
{
get
{
return _searchterm;
}
set
{
_searchterm = value;
OnPropertyChanged("SearchTerm");
}
}
}
My Converter:
class ExpandNodesIfSearchConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//if searchterm is not null, return true to expand all items, otherwise return false
string searchterm = value.ToString();
if (string.IsNullOrEmpty(searchterm))
return false;
else
return true;
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I solved my issue by using the ElementName in the Setter tag rather than the viewmodel property. searchTxt being the name of the TextBox which SeachTerm was bound to.
<Setter Property="IsExpanded" Value="{Binding ElementName=searchTxt, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ExpandAll}}" />
I need to bind a button to a Control Template. The XAML looks something like this:
Button Template="{Binding Status, Converter={StaticResource StatustoTemplate}}"
The converter (StatustoTemplate) runs fine as the status (which is an integer, but happy for it to be a string) changes:
public class StatustoTemplate : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value==1)
{
return ControlTemplateName1;
}
if (value==2)
{
return ControlTemplateName2;
}
}
}
Now, in what format can I send back ControlTemplate1, or ControlTemplate2? Let us assume that ControlTemplate1 and ControlTemplate2 are valid Control Templates as defined in the XAML. I now that it needs to return a ControlTemplate - but how to set it up?
my preferred approach is to use a Style with DataTriggers to switch Template, without converters
<Style TargetType="Button" x:Key="StatusButton"> <!--set BasedOn if there is a base Style-->
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="1">
<Setter Property="Template" Value="{StaticResource ControlTemplateName1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="2">
<Setter Property="Template" Value="{StaticResource ControlTemplateName2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
and then apply this Style:
<Button Style="{StaticResource StatusButton}"/>
It is not easy for converter to find resource defined in XAML. I usually define one control template which has both definitions and switch them using Visibility. The code is just a short sample.
XAML
<Window>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<local:MyConverter x:Key="MyConverter"/>
<ControlTemplate x:Key="template">
<Grid>
<Grid Visibility="{Binding Status, Converter={StaticResource MyConverter}, ConverterParameter=1}">
<TextBox Text="1"/>
</Grid>
<Grid Visibility="{Binding Status, Converter={StaticResource MyConverter}, ConverterParameter=2}">
<TextBox Text="2"/>
</Grid>
</Grid>
</ControlTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button Template="{StaticResource template}"/>
</Grid>
</Window>
Converter
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((string)parameter == "1") ? Visibility.Visible : Visibility.Collapsed;
}
// ConvertBack
}
I have a MarkupExtension that might work for you. It is getting xaml defined Resources by ResourceKey.
First: abstract class StyleRefExtension:
public abstract class StyleRefExtension : MarkupExtension
{
protected static ResourceDictionary RD;
public string ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue();
}
public object ProvideValue()
{
if (RD == null)
throw new Exception(
#"You should define RD before usage.
Please make it in static constructor of extending class!");
return RD[ResourceKey];
}
}
Second: class implementation as StyleRefExt
public class StyleRefExt : StyleRefExtension
{
static StyleRefExt()
{
RD = new ResourceDictionary
{
Source = new Uri("pack://application:,,,/##YOUR_ASSEMBLYNAME##;component/Styles/##YOUR_RESOURCE_DICTIONARY_FILE##.xaml")
};
}
}
Just replace ##YOUR_ASSEMBLYNAME## with the name of your assembly and ##YOUR_RESOURCE_DICTIONARY_FILE## with the filename of your ResourceDictionary (that is located in folder Styles).
Your Converter should look like this:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value==1) {
StyleRefExt sr = new StyleRefExt {ResourceKey = "ControlTemplateName1"};
return sr.ProvideValue();
}
if (value==2) {
StyleRefExt sr = new StyleRefExt {ResourceKey = "ControlTemplateName2"};
return sr.ProvideValue();
}
}
I created a ComboBox listing the colors that System.Windows.Media.Colors predefines, using the approach told in this question: How can I list colors in WPF with XAML?
My XAML code now is:
<Window ...>
<Window.Resources>
<ObjectDataProvider
ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="ColorList" />
<local:StringToBrushConverter x:Key="FontColorConversions" />
</Window.Resources>
<Grid Background="Black">
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource ColorList}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource FontColorConversions}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
...
</Grid>
</Window>
And besides, please note that I bind SelectedValue to a VM class's FontColor property, which is of string type.
class FontSetting : INotifyPropertyChanged
{
private string _fontColor = "Lavender"; // initial color
public event PropertyChangedEventHandler PropertyChanged;
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
OnPropertyChanged("FontColor");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I set the DataContext of the Window containing this ComboBox to an instance of FontSetting.
So each item in the ComboBox actually display a string representing a certain color now, what I want to do is set an item's Foreground color to that color its content indicates, like this:
Can anyone help? Thanks.
UPDATED:
Since most of the solutions have a converter which converts string to Brush and actually I already have it, now I want to put mine here, as I binded a TextBox's Foreground to FontSetting's FontColor property, so that when you change the ComboBox, the color of that TextBox changes accordingly.
Here is my converter class, and it works fine by now:
class StringToBrushConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = conv.ConvertFromString("Lavender") as SolidColorBrush;
if (null != value)
{
brush = conv.ConvertFromString(value.ToString()) as SolidColorBrush;
}
return brush;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
When I click the ComboBox to open the dropdown list, I got an exception:
CONCLUSION
Amine's solution works, it's my mistake. I explain briefly now, if you bind a ComboBox to System.Windows.Media.Colors like what I am doing, when the item is rendered, the Convert() method of the converter class (which you assign to the binding) is executed, and actually the value passed to Convert() as its first parameter is a Syetem.Windows.Media.Color instance. I made mistake coz I thought it was of string type.
Therefore, in my case I need two converter classes, one converting string to Brush, and the other one converting Color to Brush. So I will keep my own StringToBrush converter and add Amine's ColorToBrush converter.
However, I simplified Amine's implementation a bit:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = SolidColorBrush)conv.ConvertFromString(FontSetting.DEFAULT_FONT_COLOR);
if (null != value)
{
PropertyInfo pi = value as PropertyInfo;
if (null != pi)
{
brush = conv.ConvertFromString(pi.Name) as SolidColorBrush;
}
}
return brush;
}
Moreover, Joe's input is also valuable, put all them together, I can keep the items' color consistent, which is perfect.
You can set Style of ComboBoxItem as bellow :
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240" x:Name="CB"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource converter}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
By using this converter:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
object obj = ((System.Reflection.PropertyInfo)value).GetValue(this,null);
return (SolidColorBrush)new BrushConverter().ConvertFromString(obj.ToString());
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return value;
}
}
Two ways, use a value converter or an intermediate property. The easiest is probably an intermediate property as you are using well structured bindings already for your SelectedItem (but value converters are fun too!).
SelectedValue is bound to FontColor, so in your setter set another value:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
ForegroundColorToDisplay = GetBrushFromColorString(value);
OnPropertyChanged("FontColor");
}
}
public Brush _foregroundColorToDisplay
public Brush ForegroundColorToDisplay
{
get
{
return _foregroundColorToDisplay;
}
set
{
_foregroundColorToDisplay= value;
OnPropertyChanged("ForegroundColorToDisplay");
}
}
or, if you don't want to store it:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
//note it fires two changed events!
OnPropertyChanged("ForegroundColorToDisplay");
OnPropertyChanged("FontColor");
}
}
public Brush ForegroundColorToDisplay
{
get
{
return GetBrushFromColorString(value);;
}
}
You can bind to this new property in your xaml:
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding ForegroundColorToDisplay, Mode=OneWay}"/>
If you are interested, a value converter would work like this:
Create a class in your code behind (or elsewhere if it's going to be used a lot) that implements IValueConverter, takes the string and returns a Brush:
public class StringToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return GetBrushFromString(value as string)
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
Throw new SomeException();
}
}
and use it in your binding in xaml to convert the selectedItem binding to Foreground brush:
<Window.Resources>
<local:StringToBrushConverter x:Key="converter" />
</Window.Resources>
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding FontColor, Mode=OneWay, Converter={StaticResource converter}}"/>
I have a requirement to change a button's style based on a value in the data. It looks like a StyleSelector would work perfectly but there doesn't seem to be a way to set one for a button.
Is there a way to set a button style dynamically from data? Maybe even a pure XAML approach?
A more general way to accomplish the same thing:
SomeView.xaml
<UserControl>
<UserControl.Resources>
<converters:BooleanToStyleConverter x:Key="MyButtonStyleConverter"
TrueStyle="{StaticResource AmazingButtonStyle}"
FalseStyle="{StaticResource BoringButtonStyle}"/>
</UserControl.Resources>
<Grid>
<Button Style={Binding IsAmazingButton, Converter={StaticResource MyButtonStyleConverter}}/>
</Grid>
</UserControl>
BooleanToStyleConverter.cs
public class BooleanToStyleConverter : IValueConverter
{
public Style TrueStyle { get; set; }
public Style FalseStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool && (bool) value)
{
return TrueStyle;
}
return FalseStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This converter works in any view with any kind of control using whatever style you choose as long as you are binding to a Boolean property in your ViewModel to control the style switching. Easy to adapt it to other binding requirements though. This works in a DataTemplate as well.
You could place your Button Styles in a Resource Dictionary and bind the Style for the Button and use a Converter
ButtonStyles.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ButtonStyle1" TargetType="Button">
<Setter Property="Background" Value="Green"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<Style x:Key="ButtonStyle2" TargetType="Button">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</ResourceDictionary>
Then for the Button that has this requirement you bind Style to the property of interest
<Button ...
Style="{Binding Path=MyDataProperty,
Converter={StaticResource ButtonStyleConverter}}"/>
And in the Converter you load the ButtonStyles Resource Dictionary and return the desired Style based on the value
public class ButtonStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Uri resourceLocater = new Uri("/YourNameSpace;component/ButtonStyles.xaml", System.UriKind.Relative);
ResourceDictionary resourceDictionary = (ResourceDictionary)Application.LoadComponent(resourceLocater);
if (value.ToString() == "Some Value")
{
return resourceDictionary["ButtonStyle1"] as Style;
}
return resourceDictionary["ButtonStyle2"] as Style;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Ok I have a Observable collection containing string defined like so.
public ObservableCollection<string> OCGroundType { get; set; }
This collection is having resources key so what I'm trying to do is with this code
<ListBox HorizontalAlignment="Left" Height="110" Margin="156,23,0,0" VerticalAlignment="Top" Width="314" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=OCGroundType}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=, Source={StaticResource Resources}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So what I'm trying to do is give the path the value of the itemsource is that possible?
Edit
That's what the staticresource is 'binded' to , a resourceDictionary
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cultures="clr-namespace:Marcam.Cultures"
xmlns:properties="clr-namespace:Marcam.Properties">
<ObjectDataProvider x:Key="Resources" ObjectType="{x:Type cultures:CultureResources}" MethodName="GetResourceInstance"/>
</ResourceDictionary>
What the ObjectType is 'binded' to is a method who return the current Resources.resx, If I've understanded well how it's work because I've based this code on this WPF Localization
It's not possible, because a Binding is not a DependencyObject, so its properties cannot be bound.
However, you could achieve the desired result by using a converter that fetches the appropriate value from the resources.
EDIT: first, you need a converter like this:
public class ResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string key = (string)value;
return Properties.Resources.ResourceManager.GetObject(key, culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then, declare an instance of this converter in the XAML resources:
<local:ResourceConverter x:Key="resourceConverter" />
And finally, just use this converter in your binding:
<Label Content="{Binding Path=., Converter={StaticResource resourceConverter}}"/>
Assuming Resources are mere string values declared under resources section of Window or UserControl, you can achieve that using IMultiValueConverter.
First of all you need to pass resource key and second ResourceDictionary of Window/UserControl wherever your resources are defined.
XAML will look like this:
<Label>
<Label.Content>
<MultiBinding Converter="{StaticResource ResourceToValueConverter}">
<Binding/>
<Binding Path="Resources" RelativeSource="{RelativeSource
Mode=FindAncestor, AncestorType=Window}"/>
</MultiBinding>
</Label.Content>
</Label>
Ofcourse you need to add instance of ResourceToValueConverter in XAML.
Second here goes your converter code:
public class ResourceToValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return ((ResourceDictionary)values[1])[values[0]];
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
UPDATE
As you edited the question to include that resource is ObjectDataProvider, you can modify converter code.
You have ObjectDataProvider in your converter which you can get and call it's method to get the resource file(.resx) file. Get string value from the resource file and return it.
var provider = ((ResourceDictionary)values[1])["Resources"] as ObjectDataProvider;
var output = provider.Data;
return // Get string from resource file and return it.