Can't seem to get my IValueConverter to work - c#

Have the following simple xaml
<UserControl.Resources>
<converters:StateToColorConverter x:Key="stateToColorConverter"/>
</UserControl.Resources>
<StackPanel>
<Grid Width="150" Height="100" Background="{Binding State, Converter={StaticResource stateToColorConverter}}"></Grid>
<Button
Width="100"
Height="70"
Command="{Binding InitializeCommand}">Initialize</Button>
</StackPanel>
Its view model has a property State, which has a correct value.
public class MachineControlViewModel :ViewModelBase
{
private readonly IMachine machine;
public RelayCommand InitializeCommand { get; set; }
private MachineStates state;
public MachineStates State
{
get { return state; }
set { Set(() => State, ref state, value); }
}
public MachineControlViewModel(IMachine machine)
{
this.machine = machine;
InitializeCommand = new RelayCommand(Initialize, CanInitialize);
State = machine.State;
machine.StateChanged += MachineOnStateChanged;
}
// left out irrelevant parts
}
Then, the IValueConverter implementation
public class StateToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var state = (MachineStates) Enum.Parse(typeof (MachineStates), value.ToString());
switch (state)
{
case MachineStates.Idle:
return Color.Red;
case MachineStates.Initialized:
return Color.Green;
case MachineStates.Production:
return Color.Blue;
case MachineStates.Error:
return Color.Red;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
When I run my app, the backcolor of the Grid will not show.
When I set the BackColor hardcoded (so without the value converter), it is visualized correctly.
When I DO use my value converter, and put a breakpoint in the convert method, I can see that the code executes just fine, and the a Color is returned. But nothing is shown...
What am I doing wrong?

Background is of type Brush. Change in Converter to this
return new SolidColorBrush(Colors.Red);
Background property

The converter should return a Brush, not a Color, because that is the type of the Background property.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Brush brush = null;
switch ((MachineStates)value)
{
case MachineStates.Idle:
brush = Brushes.Red;
break;
case MachineStates.Initialized:
brush = Brushes.Green;
break;
case MachineStates.Production:
brush = Brushes.Blue;
break;
case MachineStates.Error:
brush = Brushes.Red;
break;
default:
break;
}
return brush;
}
It should especially not return a System.Drawing.Color (as you did), because that is WinForms, not WPF.

Related

Using FindResource in an IValueConverter

I have this value converter which converts a number to a brush color. What I need to do is to change the line return Brushes.Red; into return (Brush)FindResource("PrimaryHueMidBrush");, so I can return the color of the main theme. The problem is that I don't know how to declare (Brush)FindResource("PrimaryHueMidBrush");. Any help is welcome. Thank you in advance.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double.TryParse(value.ToString(), out double val);
if (val == 1)
{
return Brushes.Red;
}
else if(val == 0.5)
{
return Brushes.MediumVioletRed;
}
else if(val==0)
{
return Brushes.Transparent;
}
else
{
return Brushes.Transparent;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
Instead of calling FindResource in the converter, you'd better add one or more properties for the dynamic Brushes:
public class YourConverter : IValueConverter
{
public Brush FirstBrush { get; set; }
public Brush SecondBrush { get; set; }
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
double val = (double)value;
if (val >= 1)
{
return FirstBrush;
}
if (val >= 0.5)
{
return SecondBrush;
}
return Brushes.Transparent;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You would declare it in the Resources of your Application or Window like this:
<local:YourConverter x:Key="YourConverter"
FirstBrush="{StaticResource PrimaryHueMidBrush}"
SecondBrush="MediumVioletRed"/>
To access FindResource you need a FrameworkElement so the best way to do this would probably be to use a MultiValueConverter instead and pass the element that uses the converter as a second value.
Converter:
public class WhateverThisIsCalledConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Insert type- and sanity-checks here
double val = (double)values[0];
FrameworkElement callingElement = (FrameworkElement)values[1];
if (val >= 1)
{
return callingElement.FindResource("PrimaryHueMidBrush");
}
if (val >= 0.5)
{
return Brushes.MediumVioletRed;
}
return Brushes.Transparent;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return Enumerable.Repeat(DependencyProperty.UnsetValue, targetTypes.Length).ToArray();
}
}
Usage in XAML:
<Window.Resources>
<local:WhateverThisIsCalledConverter x:Key="Converter"/>
<SolidColorBrush Color="Red" x:Key="PrimaryHueMidBrush"/>
</Window.Resources>
<Grid>
<Grid.Background>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="Value"/>
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Grid.Background>
</Grid>
Couple of notes on your current implementation:
Try avoiding == on doubles, they are not infinitely precice.
You don't need all those elses when you return in the if before.
The ConvertBack method should be implemented (Free choice of other Exceptions, Binding.DoNothing and DependencyProperty.UnsetValue).
If you know your value is a double, simply cast it instead.

Check if Hex string is correct in Converter

I made a text box to input and show color Hex value.The binding is twoway to a color property of a parent.
Everything is working but, I need to make sure, in case I enter manually a Hex in the text box, and if this a not correct string, then use and display the current Hex value of the color, rather than trying to change it.
Here is what I tried but obviously it's not working, I'm a beginner and I have only a little experience with converter and WPF. If I write anything but not a valid Hex string, at the moment the textbox gets a red outline, but I wish that in this case, the Hex previous string reappears.
[ValueConversion(typeof(Color), typeof(String))]
public class ColorToStringConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
Color colorValue = (Color)value;
return ColorNames.GetColorName(colorValue);
}
public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ColorHexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
//if (string.IsNullOrEmpty(hexCode))
// return null;
try
{
var color = (Color)ColorConverter.ConvertFromString(hexCode);
return color;
}
catch
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
Regex myRegex = new Regex("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
bool isValid = false;
if (string.IsNullOrEmpty(hexCode))
{
isValid = false;
}
else
{
isValid = myRegex.IsMatch(hexCode);
}
try
{
return hexCode;
}
catch
{
return null;
}
}
}
And the C# class for the TextBox
public class ColorHex : TextBox
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter)
{
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(this, TextProperty);
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
}
}
And its xaml in Generic.xaml
<local:ColorHex x:Name="PART_ColorHex" Style="{StaticResource ColorPickerTextBox}" Text="{Binding SelectedColor, Converter={StaticResource ColorToHexConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ColorPicker}}}" />
Any idea ?
thank you
A valid hex color has the form '#nnn' or '#nnnnnn'.
So, the regex for this case would be: (^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)
NOw, you could add these lines of code:
var regex = #"(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)"
var match = Regex.Match(inputYouWantToCheck, regex,RegexOptions.IgnoreCase);
if (!match.Success)
{
// Color is not valid
}
Hope this helps.
What if you did something where you add a label next to the textbox to show an example of what color has been entered. You would just need to change the label color change each time.
I'd do it differently, using property to validate the color:
public Color Color { get; set; } = Colors.Red;
public string ColorText
{
get { return (new ColorConverter()).ConvertToString(Color); }
set
{
Color = (Color)ColorConverter.ConvertFromString(value);
OnPropertyChanged();
}
}
Bind to ColorText, it will throw in case it's wrong hex, and you can use ExceptionValidationRule to display it (as a red border):
<TextBox Text="{local:ExceptionBinding Path=ColorText}" />
where
public class ExceptionBinding : Binding
{
public ExceptionBinding() : base()
{
ValidationRules.Add(new ExceptionValidationRule());
}
}
As a bonus you can enter known colors as text, e.g. "Red":

Map the key and the value of HashTable(System.Collection) to a custom property

I have a HashTable(System.Collection) and a custom property.
The default value of "_sendToDb" should be false, I will change this later in the Grid of my wpf-program.
This is what I have:
My Hashtable:
Hashtable hsh = new Hashtable();
My property:
public class ImagesFromFS : INotifyPropertyChanged
{
string _value;
string _path;
bool _sendToDb;
...
public string Path
{
get
{
return _path;
}
set
{
_path = value;
onPropertyChanged();
}
}
public bool SendToDb
{
get
{
return _sendToDb;
}
set
{
onPropertyChanged();
_sendToDb = value;
}
}
public string Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
}
The final goal should be, binding my new created property to the gridView.
You should use a Convertor for this purpose. which return path according to the value passed to it.
Here is demo of a basic convertor modify it according to your need. It have both methods Convert or ConvertBack. Hope it will help. :)
public class StatusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch ((int)value)
{
case 1:
return "Raised";
case 2:
return "Work in Progress";
case 3:
return "Resolved";
case 4:
return "Closed";
default:
return "undefined";
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch (value.ToString())
{
case "Raised":
return 1;
case "Work in Progress":
return 2;
case "Resolved":
return 3;
case "Closed":
return 4;
default:
return 0;
}
}
}

Bind int property with StringFormat in BorderThinkness

I have a template with a own property BorderWidth:
public static readonly DependencyProperty BorderWidthProperty = DependencyProperty.Register("BorderWidth", typeof(int), typeof(ProjectOverviewSensor), new PropertyMetadata(1));
Now i want to bind this property to BorderThickness with StringFormat in my template for create a border to specific sides. But it is ever 1 in all four sides.
<Border BorderThickness="{Binding Path=BorderWidth, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}{0},0,{0},0'}"></Border>
How i can bind the property only to the sides of border control, that i want? Are there a alternative to stringformat?
Binding.StringFormat only works on properties of type System.String.
It is a different story with a string written in XAML, since here, the XAML parser converts the string to a thickness value during parsing.
To do what you want you will need a converter on the binding:
class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == DependencyProperty.UnsetValue)
return value;
return new Thickness((int)value,0, (int)value, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((Thickness)value).Left;
}
}
And use like this:
<Window.Resources>
<MyConverter x:Key="ToThickness"/>
</Window.Resources>
<Border BorderThickness="{Binding Path=BorderWidth, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ToThickness}"></Border>
Now i created a extended converter and I want him not to hide from you.
public class CustomThicknessConverter : IValueConverter
{
protected readonly char SplitChar = ',';
protected readonly char ReplaceAt = '#';
/// <summary>
/// Create a thickness with custom format(for example #,2,#,5).
/// </summary>
/// Number for default width.
/// # for given border width.
/// ; for split the chars.
/// For example '#,0,#,0' with 1 as value return a new thickness
/// with Left = 1, Top = 0, Right = 1, Bottom = 0.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == DependencyProperty.UnsetValue)
return value;
var borderWidth = value as double?;
var format = parameter.ToString().Split(SplitChar);
if(format.Length == 1)
return new Thickness(GetValue(borderWidth, format[0]));
else if(format.Length == 2)
{
return new Thickness()
{
Left = GetValue(borderWidth, format[0]),
Top = GetValue(borderWidth, format[1]),
Right = GetValue(borderWidth, format[0]),
Bottom = GetValue(borderWidth, format[1])
};
}
else if(format.Length == 4)
{
return new Thickness()
{
Left = GetValue(borderWidth, format[0]),
Top = GetValue(borderWidth, format[1]),
Right = GetValue(borderWidth, format[2]),
Bottom = GetValue(borderWidth, format[3])
};
}
return new Thickness(0);
}
private double GetValue(double? value, string format)
{
if(format.FirstOrDefault() == ReplaceAt) return value ?? 0;
double result;
return (Double.TryParse(format, out result)) ? result : 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in xaml:
<local:CustomThicknessConverter x:Key="CustomThicknessConverter" />
<Border BorderThickness="{Binding BorderWidth, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource CustomThicknessConverter}, ConverterParameter='0,#,0,#'}">
</Border>

Converter cannot be applied to a property that expects the type System.Windows.Data.IValueConverter

I am having a problem in my WPF app that I do not quite understand. I am trying to bind a fill to a different color depending on the value of a certain property.
Here are the snippets involved:
public class GeoLocations
{
private static ObservableCollection<Bus> _locations = new ObservableCollection<Bus>();
public static ObservableCollection<Bus> locations
{
get { return _locations; }
set
{
_locations = value;
}
}
.
.
.
}
public class Bus : INotifyPropertyChanged
{
private double _VSAI;
public double VSAI
{
get
{
return _VSAI;
}
set
{
_VSAI = value;
OnPropertyChanged(new PropertyChangedEventArgs("VSAI"));
}
}
public class VsaiToColorConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double vsai = (double)value;
if (vsai < App.Settings.medVSAI)
return Brushes.Green;
if (vsai >= App.Settings.medVSAI && vsai <= App.Settings.maxVSAI)
return Brushes.Yellow;
if (vsai > App.Settings.maxVSAI)
return Brushes.Red;
return Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then in my xaml I have the following:
in my resources I have
<local:GeoLocations x:Key="geolocs"/>
and then I have a map
<ig:GeographicProportionalSymbolSeries x:Name="ProportionalSeries"
LongitudeMemberPath="Longitude"
LatitudeMemberPath="Latitude"
ItemsSource="{Binding Source={StaticResource geolocs}, Path=locations}"
>
<!-- custom marker template for GeographicProportionalSymbolSeries -->
<ig:GeographicProportionalSymbolSeries.MarkerTemplate>
<DataTemplate>
<Ellipse x:Name="RootElement" Width="10" Height="10"
Stroke="DarkGray"
Opacity=".8"
StrokeThickness="0.5"
Fill="{Binding Path=Item.VSAI, Converter={StaticResource VSAIConverter}}">
</Ellipse>
</DataTemplate>
</ig:GeographicProportionalSymbolSeries.MarkerTemplate>
But the error I'm getting is on the FILL=..... above. I'm hoping this is an easy fix. I'm just a little too new still to understand how to fix this and what this error means.
Your converter needs to implement the IValueConverter interface. You've implemented the two methods but you omitted the IValueConverter interface so as far as the CLR is concerned, you just have a class that happens to have the same methods as IValueConverter but isn't actually implementing it.
public class VsaiToColorConverter : IValueConverter
You generally want to handle null value cases as well
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value == null) return Brushes.Transparent; //or null
double vsai = (double)value;
//..
}

Categories

Resources