I have a WPF ListBox ItemsSource that's bound to an ObservableCollection<string>. The listbox values are:
Blue,
Red,
Green.
I am wanting the Item's background color to match it's value. For example, I want the Blue Item's background color to be Blue, Red to Red, and so forth. I cannot figure out a way to change each ListBoxItem since I'm using an ItemsSource. How can I bind the ListBoxItems background color to these respective values?
Thank you in advance!!!
This can be achieved in a couple of ways depending on your use case.
Solution 1 - using ValueConverters:
You can use ListBox.ItemContainerStyle to style your ListBoxItem and then use a ValueConverter to convert string values to respective
SolidColorBrushes.
XAML:
<ListBox ItemsSource="{Binding MyObservableCollection}" >
<ListBox.Resources>
<local:ColorConverter x:Key="converter"/>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="{Binding Path=.,
Converter={StaticResource converter}}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Value Converter:
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var color = value.ToString();
switch (color)
{
case "Blue":
return new SolidColorBrush(Colors.Blue);
case "Red":
return new SolidColorBrush(Colors.Red);
case "Green":
return new SolidColorBrush(Colors.Green);
}
return SystemColors.ControlColor;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Soultion 2 - using DataTriggers:
Again you can use ListBox.ItemContainerStyle to style your ListBoxItem and then define several DataTriggers to change ListBoxItem.Background based on item's value.
This approach is pure XAML:
<ListBox ItemsSource="{Binding MyObservableCollection}" >
<ListBox.Resources>
<System:String x:Key="red">Red</System:String>
<System:String x:Key="green">Green</System:String>
<System:String x:Key="blue">Blue</System:String>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="{StaticResource red}">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding }" Value="{StaticResource green}">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding }" Value="{StaticResource blue}">
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Edit:
Assuming dynamically loaded color names are the same as the names of System.Windows.Media.Colors then you can replace the Convert method in the 1st soultion with this:
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var colorStr = value.ToString();
var color = (Color)System.Windows.Media.ColorConverter.ConvertFromString(colorStr);
return new SolidColorBrush(color);
}
If your color names follow no specific format and they can be anything (i.e. your external file could contain names like "VeryLightGreen" or "PrettyYellow"!) then you should define your own ColorDictionary to translate these names to the desired colors:
public static class ColorTranslator
{
private static Dictionary<string, Color> ColorDictionary = LoadColors();
public static Color FromName(string name)
{
return ColorDictionary[name];
}
private static Dictionary<string, Color> LoadColors()
{
var dictionary = new Dictionary<string, Color>();
dictionary.Add("Red", Colors.Red);
dictionary.Add("Blue", Colors.Blue);
dictionary.Add("Green", Colors.Green);
dictionary.Add("VeryLightGreen", Colors.Honeydew);
dictionary.Add("PrettyYellow", Color.FromArgb(200,255,215,0));
return dictionary;
}
}
And the Convert method:
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var colorStr = value.ToString();
var color = ColorTranslator.FromName(colorStr);
return new SolidColorBrush(color);
}
Related
I would like to change my image source from:
<Image Source="{svg:SvgImage image.svg}"/>
To something that use binding on an enum property instead:
XAML:
<Resources>
<local:MyConverter x:Key="MyConverter" />
</Resources>
<Image Source="{svg:SvgImage Binding MyEnumProperty, Converter={StaticResource MyConverter}}" />
Code behind:
public enum MyEnum
{
Value1,
Value2
}
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var myValue = (MyEnum)(value);
switch (myValue)
{
case MyEnum.Value1:
return "image1.svg";
case MyEnum.Value2:
return "image2.svg";
default:
throw new NotImplementedException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This doesn't work and I suspect that is has something to do with the svg:SvgImage and Binding MyEnumProperty being combined in the same statement.
I get the following errors:
The member "Converter" is not recognized or is not accessible.
And
The property 'Converter' was not found in type 'SvgImageExtension'.
Question:
What is the correct way to do this?
The expression
{svg:SvgImage Binding MyEnumProperty ...}
is not valid XAML, and because SvgImage is a markup extension, you can't bind its properties.
You may however use DataTriggers in an Image Style instead of a Binding with a Converter:
<Image>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding MyEnumProperty}" Value="Value1">
<Setter Property="Source" Value="{svg:SvgImage image1.svg}"/>
</DataTrigger>
<DataTrigger Binding="{Binding MyEnumProperty}" Value="Value2">
<Setter Property="Source" Value="{svg:SvgImage image2.svg}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
I have a DataGridTemplateColumn, whose Width I'd like to change based on the related boolean property.
Now I'm using a converter as shown in the code below in order to achieve my purpose.
<DataGridTemplateColumn Width="{Binding IsFull, Converter={StaticResource BooleanToColumnWidthConverter}}">
</DataGridTemplateColumn>
public class BooleanToColumnWidthConverter : IValueConverter
{
private static readonly DataGridLength NORMAL_WIDTH = new DataGridLength(10, DataGridLengthUnitType.Star);
private static readonly DataGridLength NARROWED_WIDTH = new DataGridLength(3, DataGridLengthUnitType.Star);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is bool)) { return DependencyProperty.UnsetValue; }
var isFull = (bool) value;
return isFull ? FULL_WIDTH : NARROWED_WIDTH;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Is it possible to get the same result without using a converter, for example by defining something like a style or a template as shown in the following?
<!-- This does not compile. -->
<Style TargetType="DataGridTemplateColumn">
<Setter Property="Width" Value="10*"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsFull}" Value="False">
<Setter Property="Width" Value="3*"/>
</DataTrigger>
</Style.Triggers>
</Style>
How can change the fill color of an object I'm being to in my mvvm setup using xaml in wpf. I want to change the fill color to red when the attribute being bound to is set to True.
The attribute is called IsRound.
I'll post code if necessary. I'm not on a pc at the moment.
UPDATED
Could someone show an example of how to do this using style triggers?
And set the value based on the bind property bool?
First of all you don't need any Binding for what you are trying to do. DataTrigger is enough. In the example below IsCyan is a boolean property of ViewModel. But Background of TextBlock is not bound at all.
<TextBlock Text="Inside content">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCyan}" Value="True">
<Setter Property="Background" Value="DarkCyan"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsCyan}" Value="False">
<Setter Property="Background" Value="DarkGoldenrod"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
But if at all you need Binding, solution by the user benPearce to use Converter is the way to go.
You need to use an IValueConverter on the binding.
BackgroundColor="{Binding Path=IsRound, Converter={StaticResource BoolToFillColorConverter}}"
public class BoolToFillColorConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool b;
if (bool.TryParse(value, out b))
{
if (b) return Red
else return Blue;
}
else
{
return SomeDefaultColour;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
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.
UPDATED : Clean subject, and summarize it.
Hi,
I've a datable filled, where each cell is a class like this
class CValue{
public object Value;
public Brush Quality;
private int m_quality;
public override String toString(){
return Value.toString();
}
}
My datagrid is bind on the datable, and it's working well.
But my aim is to switch the background color of the cell depending of the Quality value.
I intend tu use datatemplate but don't know how it's working at all...
<dg:DataGrid Name="DataGridResult" IsReadOnly="True" AutoGenerateColumns="False"
BorderThickness="1" BorderBrush="{DynamicResource clBLACK}"
CanUserReorderColumns="False"
ItemsSource="{Binding Path=Result}">
<dg:DataGrid.Resources>
<Style TargetType="{x:Type dg:DataGridCell}">
<Style.Setters>
<Setter Property="Background" Value="{Binding [1].Quality}"/>
</Style.Setters>
</Style>
</dg:DataGrid.Resources>
<dg:DataGrid.ItemTemplate>
<DataTemplate>
<dg:DataGridCell>
</dg:DataGridCell>
</DataTemplate>
</dg:DataGrid.ItemTemplate>
</dg:DataGrid>
Actually, if the Value of the background's setter is set to "Blue", all cells are blued, so it's fine, but I can't find a way to bind it to my property.
the [1] seems to return the column 1 of the row...
How to set to the cell dynamically ?
'Cause i've got a dynamic number of columns but there all of the CValue Type.
Ok. So for an entire example databinding to the Brush of the model instead of using converters, styles etc. For the following cs -code:
class CValue
{
public string Value { get; set; } // Notice we use properties for binding and not fields
public Brush Quality { get; set; } // Notice we use properties for binding and not fields
private int m_quality;
public override String ToString()
{
return Value.ToString();
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
// Databind the list
myGrid.ItemsSource = new List<CValue>
{
new CValue
{
Value = "First",
Quality = new SolidColorBrush(Color.FromArgb(255, 0, 255, 255))},
new CValue
{
Value = "Second",
Quality = new SolidColorBrush(Color.FromArgb(255, 255, 0, 255))
},
new CValue
{
Value = "Third",
Quality = new SolidColorBrush(Color.FromArgb(0, 255, 255, 255))
}
};
}
}
You would use a xaml for the rowstyle (notice the TargetType on the style and the AutoGenerateColumns="false") to bind the row-color and the value:
<Controls:DataGrid x:Name="myGrid" AutoGenerateColumns="False">
<Controls:DataGrid.RowStyle>
<Style TargetType="{x:Type Controls:DataGridRow}">
<Setter Property="Background" Value="{Binding Quality}" />
</Style>
</Controls:DataGrid.RowStyle>
<Controls:DataGrid.Columns>
<Controls:DataGridTemplateColumn Header="Value">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Value}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
</Controls:DataGridTemplateColumn>
</Controls:DataGrid.Columns>
</Controls:DataGrid>
Hope it helps!
One good way of doing this which keeps the coloring visible in the XAML is to use a style with binding to the quality. We put this style in the some resourcedictionary above the template, like in my case in the DataGrid.Resources.
<Controls:DataGrid>
<Controls:DataGrid.Resources>
<Style TargetType="{x:Type Controls:DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Quality}" Value="0">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Quality}" Value="0">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:DataGrid.Resources>
<Controls:DataGrid.ItemTemplate>
<DataTemplate>
<Controls:DataGridCell>
</Controls:DataGridCell>
</DataTemplate>
</Controls:DataGrid.ItemTemplate>
</Controls:DataGrid>
Update:
To be able to databind the values or whatever use a converter like this:
[ValueConversion(typeof(int), typeof(SolidColorBrush))]
public class QualityToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Cast value
int intValue = (int) value;
if (intValue == 1)
return new SolidColorBrush(Color.FromArgb(255, 255, 255, 255));
return new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("TwoWay binding not supported!");
}
}
Bind it in the XAML like the follwing:
<Window.Resources>
<WpfApplication1:QualityToColorConverter x:Key="ColorConverter" />
</Window.Resources>
<Controls:DataGridCell Background="{Binding Quality, Converter={StaticResource ColorConverter}}">
</Controls:DataGridCell>
You should use DataTemplateSelector class to perform this logic.
The scenario is described below:
Create the set of DataTemplates;
Derive from DataTemplateSelector Class and implement there logic of selecting appropriate DataTemplate as described in MSDN Article;
Define your custom DataTemplateSelector as the resource specifying the x:Key attribute;
Bind needed object to defined DataTemplateSelector resource.
UPDATE
The upper approach works best when you need to completely redesign cells as the guys mentioned in comment.
So for this task you should create your converter, define it as a resource and add it to your binding:
<!--somewhere in resources-->
<QualityToBackgroundConverter x:Key="qualityToBackgroundConverter "/>
then binding will look like:
Background="{Binding Quality, Converter={StaticResource qualityToBackgroundConverter }}"
and finally the converter:
[ValueConversion(typeof(Quality), typeof(Brush))]
public class QualityToBackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
Quality quality = (Quality)value;
switch (quality)
{
case 0: return Brushes.Red;
case 1: return Brushes.Yellow;
case 2: return Brushes.Green;
default: return Brushes.Transparent;
}
}
return Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw NotImplementedException();
}
}