XAML colour binding converter from bool not working - c#

I'm trying to change the text colour in a datagrid depending on a bool in my model using a converter but I get the following error.
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement
or FrameworkContentElement for target element.
BindingExpression:Path=DiameterCustom; DataItem=null; target element
is 'DataGridTextColumn' (HashCode=7886611); target property is
'Foreground' (type 'Brush')
Does anyone know why this is?
My xaml is as follows:
<UserControl.Resources>
<conv:UnitConverter x:Key="UnitConverter"></conv:UnitConverter>
<conv:CustomColourConverter x:Key="CustomColourConverter"></conv:CustomColourConverter>
</UserControl.Resources>
<DataGridTextColumn
Header="Diameter
(mm)"
Binding="{Binding Diameter, Mode=TwoWay, StringFormat={}{0:n0}, Converter={StaticResource UnitConverter}, ConverterParameter=1000}"
Foreground="{Binding DiameterCustom, Converter={StaticResource CustomColourConverter}}"/>
This is my converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool v = (bool)value;
if (v == true)
{
//return return System.Windows.Media.Brushes.Red;
return new SolidColorBrush(Colors.Red);
}
//return return System.Windows.Media.Brushes.Blue;
return new SolidColorBrush(Colors.Blue);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
and just for completeness, here is my model property:
private bool diameterCustom;
public bool DiameterCustom
{
get { return diameterCustom; }
set { SetAndNotify(ref this.diameterCustom, value); }
}
Note, the data binding for Diameter and the unit converter work fine.

To change the text colour cell by cell the following solution worked:
<DataGridTextColumn
Header="Diameter
(mm)"
Binding="{Binding Diameter, Mode=TwoWay, StringFormat={}{0:n0}, Converter={StaticResource UnitConverter}, ConverterParameter=1000}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{Binding DiameterCustom, Converter={StaticResource CustomColourConverter}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

Related

WPF SVG image binding using a converter

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>

How to return a resource style from a converter?

Suppose that I need to set a different Foreground based on the value of the current item of a DataTemplate:
<DataGridTemplateColumn Header="5">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Text="{Binding Match5}" TextAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Match5, Converter={StaticResource NameToBrushConverter}}"/>
<Setter Property="Foreground" Value="{Binding Match5, Converter={StaticResource ForegroundConverter}}"/>
<Setter Property="Width" Value="{Binding Match5, Converter={StaticResource NameToWidthConverter}}" />
<Style.Triggers>
</TextBox.Style>
...
I created a converter:
public class ForegroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var input = value as string;
if (input.Contains("-"))
return "MaterialDesignBody";
return "White";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
now when the item does not contain - the white foreground is setted, but when the value contains - I need to return a DynamicResource provided by MaterialDesignInXaml, but I get this error in the output console:
System.Windows.Data Error: 6 : 'DynamicValueConverter' converter failed to convert value 'MaterialDesignBody' (type 'String'); fallback value will be used, if available. BindingExpression:Path=Match1; DataItem='LatestFiveMatchRow' (HashCode=87685); target element is 'TextBlock' (Name=''); target property is 'Foreground' (type 'Brush') FormatException:'System.FormatException: Invalid Token.
any idea or hint? Thanks.
Be aware that
return "White";
only works due to built-in automatic type conversion. There is a BrushConverter class registered as TypeConverter for the target property or its type, i.e. Brush:
[TypeConverterAttribute(typeof(BrushConverter))]
[LocalizabilityAttribute(LocalizationCategory.None, Readability = Readability.Unreadable)]
public abstract class Brush : Animatable, IFormattable
This TypeConverter (which must not be confused with a Binding's IValueConverter) is capable of converting well-known Brush names like "White" to their equivalent in the Brushes class, i.e. Brushes.White. However, it can't convert "MaterialDesignBody". You must perform a resource lookup and return the appropriate Brush resource yourself:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.ToString().Contains("-"))
{
return (Brush)Application.Current.FindResource("MaterialDesignBody");
}
return Brushes.White;
}

How to change the Width of a DataGridTemplateColumn without using a converter?

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>

WPF datagrid: converter and StringFormat

I have a standard (WPF toolkit) data grid. Some of the columns (which are explicitly defined) have to be shown as percentages. Some columns have to be shown in red if the values are below 0. (The two sets of columns are not the same). I tried to implement these requirements using a StringFormat and Style, respectively. My XAML:
<Window xmlns:local="clr-namespace:myNamespace"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit">
<Window.Resources>
<local:ValueConverter x:Key="valueToForeground" />
<Style TargetType="{x:Type tk:DataGridCell}">
<Setter Property="Foreground"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Text, Converter={StaticResource valueToForeground}}" />
</Style>
</Window.Resources>
<Grid>
<tk:DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding Path=myClass/myProperty}">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="A"
Binding="{Binding colA}" />
<tk:DataGridTextColumn Header="B"
Binding="{Binding colB, StringFormat=\{0:P\}}" />
<tk:DataGridTextColumn Header="C"
Binding="{Binding colC, StringFormat=\{0:P\}}" />
<tk:DataGridTextColumn Header="D"
Binding="{Binding colD, StringFormat=\{0:P\}}" />
</tk:DataGrid.Columns>
</tk:DataGrid>
</Grid>
</Window>
And the relevant converter:
namespace myNamespace
{
public class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = new SolidColorBrush(Colors.Black);
Double doubleValue = 0.0;
if (value != null)
{
if (Double.TryParse(value.ToString(), out doubleValue))
{
if (doubleValue < 0)
brush = new SolidColorBrush(Colors.Red);
}
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I think it's all pretty standard, but the problem is that the converter gets the Text value after it's gone through the StringFormat, and at that point it's difficult to parse it correctly (since in reality, not all columns have the same format). If I take out the StringFormats, the converter works fine and the text shows up in red. Am I missing something obvious? Is there an easy way to work around this? The only thing that I can think of right now is moving the formatting into a different converter, and I'm not convinced that would work.
We had a similar situation where we needed a different Path Property for the Binding but otherwise a similar CellStyle for each DataGridColumn. We solved this with a custom MarkupExtension. In your case it would look like this
<tk:DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding MyItems}">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="A"
Binding="{Binding colA}" />
<tk:DataGridTextColumn Header="B"
Binding="{Binding colB, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle PropertyName=colB}"/>
<tk:DataGridTextColumn Header="C"
Binding="{Binding colC, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle PropertyName=colC}"/>
<tk:DataGridTextColumn Header="D"
Binding="{Binding colD, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle PropertyName=colD}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
and then ForegroundCellStyleExtension creates the Style for DataGridCell depending on PropertyName
ForegroundCellStyleExtension
public class ForegroundCellStyleExtension : MarkupExtension
{
public ForegroundCellStyleExtension() { }
public ForegroundCellStyleExtension(string propertyName)
{
PropertyName = propertyName;
}
public string PropertyName
{
get;
set;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
DependencyObject targetObject = service.TargetObject as DependencyObject;
if (targetObject == null)
{
return null;
}
Binding foregroundBinding = new Binding
{
Path = new PropertyPath(PropertyName),
Converter = new ValueConverter()
};
Style foregroundCellStyle = new Style(typeof(DataGridCell));
foregroundCellStyle.Setters.Add(new Setter(DataGridCell.ForegroundProperty, foregroundBinding));
return foregroundCellStyle;
}
}
Also, if you have some other Setters etc. that you would like to use then they can be included by another parameter to the MarkupExtension.
<Window.Resources>
<Style x:Key="dataGridCellStyle" TargetType="{x:Type tk:DataGridCell}">
<Setter Property="Background" Value="Blue"/>
</Style>
</Window.Resources>
<!-- ... -->
<tk:DataGridTextColumn Header="B"
Binding="{Binding colB, StringFormat=\{0:P\}}"
CellStyle="{markup:ForegroundCellStyle colB, {StaticResource dataGridCellStyle}}"/>
And ForegroundCellStyleExtension would then use the second parameter as BasedOn for the DataGridCell Style
ForegroundCellStyleExtension with BasedOn
public class ForegroundCellStyleExtension : MarkupExtension
{
public ForegroundCellStyleExtension() { }
public ForegroundCellStyleExtension(string propertyName, Style basedOnCellStyle)
{
PropertyName = propertyName;
BasedOnCellStyle = basedOnCellStyle;
}
public string PropertyName
{
get;
set;
}
public Style BasedOnCellStyle
{
get;
set;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
DependencyObject targetObject = service.TargetObject as DependencyObject;
if (targetObject == null)
{
return null;
}
Binding foregroundBinding = new Binding
{
Path = new PropertyPath(PropertyName),
Converter = new ValueConverter()
};
Style foregroundCellStyle = new Style(typeof(DataGridCell), BasedOnCellStyle);
foregroundCellStyle.Setters.Add(new Setter(DataGridCell.ForegroundProperty, foregroundBinding));
return foregroundCellStyle;
}
}
Specify a cell style for each column as follows:
<DataGridTextColumn Header="ColA" Binding="{Binding colA, StringFormat=\{0:P\}}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Foreground"
Value="{Binding colA, Converter={StaticResource valueToForeground}}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="ColB" Binding="{Binding colB, StringFormat=\{0:P\}}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Foreground"
Value="{Binding colB, Converter={StaticResource valueToForeground}}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
...
and modify your converter
public class ValueConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double) value < 0) ? Brushes.Red : Brushes.Black;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
The simplest way I figured out is to bind your full item instead of the item/content.text only to your converter. Then you will be able to do what you wanted to do with your cells with need to worry about the item and parameter values.
In your Cell Style:
<Setter Property="Foreground"
Value="{Binding Converter={StaticResource valueToForeground}}" />
and in your Converter code:
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = new SolidColorBrush(Colors.Black);
Double doubleValue = 0.0;
if (value != null)
{
mydatatype data = value as mydatatype;
//your logic goes here and also can play here with your dataitem.
if (Double.TryParse(data.CollD.ToString(), out doubleValue))
{
if (doubleValue < 0)
brush = new SolidColorBrush(Colors.Red);
}
}
return brush;
}

WPF Trigger that would work if the value is equal or greater

I wrote an application in WPF that has a button and slider. I would like to create a trigger for the button, which would set the button's 'IsEnable' property to false when the slider value is greater than another value.
Right now I have:
<Style x:Key="zoomOutButton" TargetType="Button" BasedOn="{StaticResource ResourceKey=buttonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentAltitude}" Value="24000">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
But I would like to set isEnable not when the value of CurrentAltitude equal 24000, but when it is equal or greater than 24000.
Any ideas?
You can achieve this using a converter:
public class IsEqualOrGreaterThanConverter : IValueConverter {
public static readonly IValueConverter Instance = new IsEqualOrGreaterThanConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
int intValue = (int) value;
int compareToValue = (int) parameter;
return intValue >= compareToValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Then your trigger will look like this:
<Style x:Key="zoomOutButton" TargetType="Button" BasedOn="{StaticResource ResourceKey=buttonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentAltitude, Converter={x:Static my:IsEqualOrGreaterThanConverter.Instance}, ConverterParameter=24000}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
A more generic converter, usable with any comparable type, could be :
public class IsGreaterOrEqualThanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IComparable v = value as IComparable;
IComparable p = parameter as IComparable;
if (v == null || p == null)
throw new FormatException("to use this converter, value and parameter shall inherit from IComparable");
return (v.CompareTo(p) >= 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
But in this case, the ConverterParameter must be interpreted with the same type as the value transmitted to your Converter. For example, to compare an int property 'MyIntProperty' with the contant int value 1, in your XAML, you can use this syntax :
<UserControl x:Class="MyNamespace.MyControl"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:genconverters="clr-namespace:MyConverterNamespace;assembly=MyConvertersAssembly">
<Grid>
<Grid.Resources>
<genconverters:IsGreaterOrEqualThanConverter x:Key="IsEqualOrGreaterThanConverter"/>
<sys:Int32 x:Key="Int1">1</sys:Int32>
</Grid.Resources>
<ComboBox IsEnabled="{Binding MyIntProperty,
Converter={StaticResource IsEqualOrGreaterThanConverter},
ConverterParameter={StaticResource Int1}}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"/>
</Grid>

Categories