I need to define a DependencyProperty in a converter class because I need this data to make the conversion and this data is in another object, not the one I'm binding to.
My converter class is the following:
public class LEGOMaterialConverter : DependencyObject, IValueConverter
{
public DependencyProperty MaterialsListProperty = DependencyProperty.Register("MaterialsList", typeof(Dictionary<int, LEGOMaterial>), typeof(LEGOMaterialConverter));
public Dictionary<int, LEGOMaterial> MaterialsList
{
get
{
return (Dictionary<int, LEGOMaterial>)GetValue(MaterialsListProperty);
}
set
{
SetValue(MaterialsListProperty, value);
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
LEGOMaterial material = null;
MaterialsList.TryGetValue((int)value, out material);
return material;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then I'm instanciating it on the Window.REsources area:
<Window.Resources>
<local:LEGOMaterialConverter x:Key="MaterialsConverter" MaterialsList="{Binding Path=Materials}" />
</Window.Resources>
I'm getting the following error:
'MaterialsList' property was already registered by 'LEGOMaterialConverter'.
Does anyone have a clue on this error?
Try doing it like this (just an example):
public class ValueConverterWithProperties : MarkupExtension, IValueConverter
{
public int TrueValue { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((int) value == TrueValue)
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
notice I derive from markup extension to allow me to use it like this:
<Window x:Class="Converter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converter="clr-namespace:Converter"
Title="MainWindow" Height="350" Width="525">
<Grid>
<CheckBox IsChecked="{Binding item, Converter={converter:ValueConverterWithProperties TrueValue=5}}"></CheckBox>
<CheckBox IsChecked="{Binding item2, Converter={converter:ValueConverterWithProperties TrueValue=10}}"></CheckBox>
</Grid>
Btw, this error is caused by your dependency property in the converter not being static (and after having created an instance of this converter somewhere before).
EDIT
So the problem is with this line:
public DependencyProperty MaterialsListProperty = DependencyProperty.Register("MaterialsList", typeof(Dictionary<int, LEGOMaterial>), typeof(LEGOMaterialConverter));
Here the Dependency Property MaterialsListProperty is being registered with every instantiation of an object of this type (i.e. LEGOMaterialConverter).
However Dependency Properties should be defined static, like so:
public static readonly DependencyProperty MaterialsListProperty = DependencyProperty.Register("MaterialsList", typeof(Dictionary<int, LEGOMaterial>), typeof(LEGOMaterialConverter));
A static variable is initialized (and the Dependency Property is registered) only once for all future instances of this type and that is what we need here. Failing to declare a Dependency Property as static results in the error from above.
Related
I have a property of an enum type. I bind the content of a wpf control to this property. This will display the name of the enum value. So the ToString Method of enum is called.
But I need to display the value, not the string value. Does anyone know how to do this?
This is my C# code:
public enum Animal
{
cat = 0,
dog = 1,
mouse = 2
}
public Animal MyAnimal { get; set; }
void SomeMethod() { MyAnimal = dog; }
This is in my XAML:
<Label Content="{Binding MyAnimal}">
When you bind to a value of one type and want to display it in another format than the default ToString() method provides you should either use a DataTemplate or an IValueConverter. Since XAML is a markup language you cannot really cast the enumeration value to an int in your markup so you should use a converter:
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
animals enumValue = (animals)value;
return System.Convert.ToInt32(enumValue);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window.Resources>
<local:EnumConverter x:Key="conv" />
</Window.Resources>
...
<ContentControl Content="{Binding TheEnumProperty, Converter={StaticResource conv}}" />
I have found a solution:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Enum)) return value;
return Enum.IsDefined(value.GetType(), value) ? value : System.Convert.ToInt32(value);
}
I have a enum to string converter
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
MailSettingsStateEnum enumValue = (MailSettingsStateEnum)value;
// extension method on the enum, to return a string based on enum.
return enumValue.Description();
}
// ConvertBack not relevant here.
}
I am using this in wpf xaml easily as follows to set the Content property of a label.
<Label Content="{Binding MailSettingState, Converter={StaticResource
EnumConverterString}}"
BorderBrush="{Binding MailSettingState, Converter={StaticResource
EnumConverterBorderBrush}}" />
Now as you can see, I have another property BorderBrush. I also have to set this based on the same enum. And so I had to write another converter EnumConverterBorderBrush
So is there a way by which I have only one converter, and it return an object which has two properties and i can use these properties in the xaml? I can create the converter, its easy, but I dont know how to use it in xaml. Say the converter returned an object and has tow property called MessageString(of type string), and another BorderBrush of the type Brush, how do I use it the xaml?
You can switch the output based on the targetType you receive in your converter.
So you could do something like this:
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
var enumValue = (MailSettingsStateEnum)value;
switch(targetType)
{
case typeof(string)
return enumValue.Description();
case typeof(Brush)
return enumValue.GetBrush();
default:
throw new NotSupportedException("Type not supported")
}
}
// ConvertBack not relevant here.
}
Now you'll have one converter to rule them all!
converter should return object which match requested targetType. converter can return different values for input enum value depending on parameter. I think it is more flexible than relying on targetType only.
public class SpecEnumConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Enum)
{
if ((string) parameter == "brush")
return "Red"; // return brush here!
// if not pre-defined parameter (null or any other), return description
return (int) value; // return enum description here!
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
usage:
<Label Content="{Binding MailSettingState, Converter={StaticResource
EnumConverterSpec}}"
BorderBrush="{Binding MailSettingState, Converter={StaticResource
EnumConverterSpec}, ConverterParameter='brush'}" />
I already commented above, but here's the solution.
<Label DataContext="{Binding MailSettingState, Converter={converters:EnumConverter}}" Content="{Binding Label}" BorderBrush="{Binding BorderBrush}"/>
public class EnumConverter: MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var enumValue = (MailSettingsStateEnum) value;
return new ConvertedEnum { Label = enumValue.Description(),
BorderBrush = new BorderBrush()};
}
// ConvertBack not relevant here.
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class ConvertedEnum
{
public string Label {get; set;}
public BorderBrush {get; set;}
}
Separate converters still look prettier to me.
When you drag the slider, you get a ConvertBack (expected), but why do I then get a "Convert" straight after that? I'd only expect Convert to be called when its first initialized, or if it was raising a property change notification, but it doesn't.
<Window x:Class="WpfApplication10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication10"
Title="MainWindow">
<Slider Value="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={l:Converter}}"/>
</Window>
public class Converter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
public partial class MainWindow : Window
{
public double Value { get; set; }
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
}
This is likely occurring because you're using .NET 4, which changed the way bindings work a little bit : http://karlshifflett.wordpress.com/2009/05/27/wpf-4-0-data-binding-change-great-feature/
Whereas prior to 4 the binding wouldn't update back (assuming that it didn't need to), the new behavior is to do that by default. Rationale is explained in the linked blog.
Edit: I suppose I should ask if there is a reason you don't want it to convert back, or if it's just curiosity? If you need to prevent converting back, the clearest strategy is probably to keep track of your last converted value to parrot back :
public class Converter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object lastValue;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return lastValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
lastValue = value;
return value;
}
}
There's also the option of setting the binding to OneWayToSource, which would keep it from setting back to the slider value... assuming you don't need to push data back to the slider.
I wanted to experiment with being able to have a converter whose arguments can be bound with the current data context. Can anyone tell me why when reaching the Convert() function, the Source property is always null?
namespace WpfApplication32
{
public class ConverterTest : DependencyObject, IValueConverter
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(DependencyObject), typeof(ConverterTest));
public DependencyObject Source
{
get { return (DependencyObject)this.GetValue(SourceProperty); }
set { this.SetValue(SourceProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
Value = 7;
InitializeComponent();
DataContext = this;
}
public float Value
{
get;
set;
}
}
}
<Window x:Class="WpfApplication32.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication32">
<Slider>
<Slider.Value>
<Binding Path="Value">
<Binding.Converter>
<local:ConverterTest Source="{Binding}"/>
</Binding.Converter>
</Binding>
</Slider.Value>
</Slider>
</Window>
One possible solution is to make your Converter inherit from Freezable instead (Hillberg Freezable trick). Then you can even define your Converter in your Resources and reference it in your binding as an attribute instead of an extra child element.
To convert Enums to Icons I use a value converter like that:
public class IconConverter : IValueConverter
{
public ResourceDictionary Items { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string key = Enum.GetName(value.GetType(), value);
return Items[key];
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I use it in my XAML like this:
<UserControl.Resources>
<local:IconConverter x:Key="IconConverter">
<ResourceDictionary Source="/Leister.WPFControls;component/ButtonStyles.xaml" />
</local:IconConverter>
</UserControl.Resources>
When I start the application all works fine, the Converter converts the Name of a value and gets the Icon from the ResourceDictionary by its key. But in my Designer, Visual Studio 2010 alwas complains:
The object of type System.Windows.ResourceDictionary" can not be cast to type "Microsoft.Expression.DesignModel.DocumentModel.DocumentNode".
at Microsoft.Expression.DesignModel.Core.InstanceBuilderOperations.SetValue(Object target, IProperty propertyKey, Object value)
at Microsoft.Expression.DesignModel.InstanceBuilders.ClrObjectInstanceBuilder.ModifyValue(IInstanceBuilderContext context, ViewNode target, IProperty propertyKey, Object value, PropertyModification modification)
at Microsoft.Expression.DesignModel.InstanceBuilders.ClrObjectInstanceBuilder.UpdateProperty(IInstanceBuilderContext context, ViewNode viewNode, IProperty propertyKey, DocumentNode valueNode)
This is anoying! Any idea? Is there a simpler solution to convert Enums to XAML-Icon Resources?
I know its late but maybe the ContentPropertyAttribute would help the designer:
[ContentProperty("Items")]
public class IconConverter : IValueConverter
{
public ResourceDictionary Items { get; set; }
http://msdn.microsoft.com/en-us/library/system.windows.markup.contentpropertyattribute.aspx