How to tell WPF to convert target to my source type without IValueConverter - wpf

I assume there is a way to tell WPF how to convert a string target to my source type without having to specify an IValueConverter. WPF will convert string to color, for example. I thought if my type supported an explicit conversion from string to my type, that this would be enough. But this did not work. Is there a way?

Yes, but you have to define a type converter for your type. You can convert from your type to string simply by overriding ToString(). To go the other direction you need a type converter. For example...
public class PartNumberConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if(value is string)
{
return new PartNumber(value as string);
}
return null;
}
}
[TypeConverter(typeof(PartNumberConverter))]
public class PartNumber
{
public PartNumber(string s)
{
string[] e = s.Split('-');
if(e.Length==3)
{
_a = Convert.ToInt32(e[0]);
_b = Convert.ToInt32(e[1]);
_c = Convert.ToInt32(e[2]);
}
}
public PartNumber(int a, int b, int c)
{
_a = a;
_b = b;
_c = c;
}
int _a = 0;
int _b = 0;
int _c = 0;
public override string ToString()
{
return _a + "-" + _b + "-" + _c;
}
}
So not a IValueConverter but still a converter :)

Related

Can <x:Double x:Key="fontsize">50</x:Double> be converted into pure attribute syntax or pure element syntax?

Motivation:
Hybrid syntax
<Label FontSize="50">Hello</Label>
can be converted into pure attribute syntax,
<Label Text="Hello" FontSize="50"/>
or pure element syntax
<Label>
<Label.Text>Hello</Label.Text>
<Label.FontSize>50</Label.FontSize>
</Label>
Question:
Can we also convert <x:Double x:Key="fontsize">50</x:Double> into pure attribute syntax and pure element syntax?
In my attempt, I cannot find the name of attribute associated with 50.
To set values via attributes or tags, a type must have properties with public setters. And you can only assign values to these properties.
But to create a type as a value in the content, the type must have a converter from String.
Example:
[TypeConverter(typeof(SomeStructConverter))]
public struct SomeStruct
{
private int X;
private int Y;
public override string ToString() => $"({X}, {Y})";
public static SomeStruct Parse(string text)
{
if (text is null)
throw new ArgumentNullException(nameof(text));
var split = text.Split(" \t\r\n,()".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (split.Length != 1 && split.Length != 2)
throw new ArgumentException(nameof(text));
int y, x = int.Parse(split[0]);
if (split.Length == 2)
y = int.Parse(split[1]);
else
y = x;
return new SomeStruct() { X = y, Y = x };
}
public static bool TryParse(string text, out SomeStruct some)
{
try
{
some = Parse(text);
return true;
}
catch (Exception)
{
some = new SomeStruct();
return false;
}
}
public static implicit operator SomeStruct(int number)
{
return new SomeStruct() { X = number, Y = number };
}
public static implicit operator int(SomeStruct some)
{
if (some.X != some.Y)
throw new InvalidCastException("X and Y must be the equals.");
return some.X;
}
}
public class SomeStructConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
if (sourceType == typeof(string) || sourceType == typeof(int) || sourceType == typeof(SomeStruct))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
{
if (destinationType == typeof(string) || destinationType == typeof(int) || destinationType == typeof(SomeStruct))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
Type sourceType = value.GetType();
if (sourceType == typeof(SomeStruct))
return value;
if (sourceType == typeof(int))
return (SomeStruct)(int)value;
if (sourceType == typeof(string))
return SomeStruct.Parse((string)value);
return base.ConvertFrom(context, culture, value);
}
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (value is not null)
{
if (destinationType == typeof(SomeStruct))
return value;
if (destinationType == typeof(int))
{
return (int)(SomeStruct)value;
}
if (destinationType == typeof(string))
return value.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool IsValid(ITypeDescriptorContext? context, object? value)
{
if (value != null)
{
Type sourceType = value.GetType();
if (sourceType == typeof(SomeStruct) || sourceType == typeof(int))
return true;
if (sourceType == typeof(string))
return SomeStruct.TryParse((string)value, out _);
}
return base.IsValid(context, value);
}
}
<local:SomeStruct x:Key="some">123 -456</local:SomeStruct>
The Double type (like other numeric types) has no properties. Therefore, it is impossible to set a value through attributes or tags for a number.
You already have accepted an answer but if you want something a little less wordy, you could try a MarkupExtension that provides the double - or other strongly typed value
public class ValExtension<T> : MarkupExtension where T : struct
{
protected ValExtension(T val) => Value = val;
private object Value { get; }
public override object ProvideValue(IServiceProvider sp) { return Value; }
}
public class DoubleValExtension : ValExtension<double>
{
public DoubleValExtension(double v) : base(v) {}
}
I find this useful in places like anywhere I don't want to rely on some implicit type conversion, like in the ConverterParameter for a value converter in binding. Since the ConverterParameter is simply an object, merely putting, say "0.3" in it will not result in the actual converter getting a double.
For example, suppose I had a converter that took a Category enum and an optional double parameter to adjust the computed value. I could use the markup extension to force XAML to pass a boxed double.
Opacity="{Binding SelectedItem.Category,
Converter={StaticResource ConvertCategoryToOpacity},
ConverterParameter={local:DoubleVal 0.3}
If I tried to do it like this, the converter would just get an unconverted string.
Opacity="{Binding SelectedItem.Category,
Converter={StaticResource ConvertCategoryToOpacity},
ConverterParameter='0.3'}"
That might be fine in some cases, but not if my converter is counting on checking the type of the parameter.
You can just derive from the generic for other POD types too.
public class IntValExtension : ValExtension<int> { public IntValExtension(int v) : base(v) {} }
public class BoolValExtension : ValExtension<bool> { public BoolValExtension(bool v) : base(v) {} }

WPF designer properties window: property does not show nested properties

I am trying to edit a complex property of a WPF custom UserControl in the properties windows. But, the nested properties of the property are not always expandable. In the verbose form (don't know the corrent name):
<local:PointControl>
<local:PointControl.Point>
<local:Point X="3" Y="4"/>
</local:PointControl.Point>
</local:PointControl>
I can edit the nested properties:
In the short form, provided by a TypeConverter:
<local:PointControl Point="0,0"/>
I cannot edit the nested properties:
The code is as follows:
Point
namespace WpfApplication1
{
[TypeConverter(typeof(PointConverter))]
public class Point
{
public int X { get; set; }
public int Y { get; set; }
public Point() { }
public Point(int x, int y)
{
X = x;
Y = y;
}
public static bool CanParse(string value); // not important
public static Point Parse(string value); // not important
public override string ToString()
{
return string.Format("{0},{1}",X,Y);
}
}
}
PointConverter
namespace WpfApplication1
{
public class PointConverter : TypeConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is Point)
{
return (value as Point).ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); ;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
var point = Point.Parse(value as string);
return point;
}
return base.ConvertFrom(context, culture, value);
}
public override bool IsValid(ITypeDescriptorContext context, object value)
{
if (!(value is string))
{
return false;
}
return Point.CanParse(value as string);
}
}
}
UserControl
namespace WpfApplication1
{
public partial class PointControl : UserControl
{
public Point Point
{
get;set;
}
public PointControl()
{
InitializeComponent();
}
}
}
UPDATE
When I add an inline value editor it won't update the XAML... :(.
xaml:
<local:PointControl Point="2,2">
MetaData.cs
[assembly: ProvideMetadata(typeof(WpfApplication1.Design.Metadata))]
namespace WpfApplication1.Design
{
internal class Metadata : IProvideAttributeTable
{
public AttributeTable AttributeTable
{
get
{
AttributeTableBuilder builder = new AttributeTableBuilder();
builder.AddCustomAttributes(typeof(WpfApplication1.PointControl), nameof(WpfApplication1.PointControl.Point), PropertyValueEditor.CreateEditorAttribute(typeof(PointerValueEditor)));
return builder.CreateTable();
}
}
}
}
PointerValueEditor
namespace WpfApplication1.Design
{
class PointerValueEditor : PropertyValueEditor
{
public PointerValueEditor()
{
var stringReader = new System.IO.StringReader(
#"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
<StackPanel>
<TextBox Text=""{Binding Value.X, Mode=TwoWay, UpdateSourceTrigger=LostFocus}""/>
<TextBox Text=""{Binding Value.Y, Mode=TwoWay, UpdateSourceTrigger=LostFocus}""/>
</StackPanel>
</DataTemplate>");
var xmlReader = System.Xml.XmlReader.Create(stringReader);
var dataTemplate = System.Windows.Markup.XamlReader.Load(xmlReader) as DataTemplate;
this.InlineEditorTemplate = dataTemplate;
}
}
}
The Code is based on https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2013/ayybcxe5(v=vs.120)
You will want to implment an inline value editor

How to use custom object's list with typeconverter in winform?

I made custom type which can use in my custom winform control.
I want to display this custom type property in to default winform property window or my custom smart grid.
So i made type converter for my custom type.
[Serializable]
public class TestObj
{
private int a;
private int b;
public int A { get { return a; } set { a = value; } }
public int B { get { return b; } set { b = value; } }
public TestObj()
{
}
public TestObj(int a, int b)
{
this.a = a;
this.b = b;
}
}
// TestObj Converter
public class TestObjConverter : TypeConverter
{
//can convert string -> testobj?
public override bool CanConvertFrom(ITypeDescriptorContext context, >
Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
// string -> TestObj
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string[] v = ((string)value).Split(new char[] { ' ' });
return new TestObj(Int32.Parse(v[0]), Int32.Parse(v[1]));
}
return base.ConvertFrom(context, culture, value);
}
// TestObj -> string
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type
destinationType)
{
if (destinationType == typeof(string))
{
return ((TestObj)value).A + " " + ((TestObj)value).B;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
(If i did not made this typeconverter then resx error will occurred.)
Now i can use TestObj type for my custom control's property.
But i want to use list collection property for this type in my custom control class.
private List<TestObj> testData = new List<TestObj>();
[Category("CustomControl"), Description("Property")]
[TypeConverter(typeof(TestObjConverter))]
public List<TestObj> TestData
{
get { return testData; } set { testData = value; }
}
But i can not use this type's list for property.
If i use this, the collection editor was opened and i can add TestObj element too. But visual studio invoke error [invalid resx file, Culture=newtral, PublicKeyTokrn=null ] when i compile this.
How to use my custom type list in winform property?
But
This may be a little late but someone else may run across this. Your TestObj class that is being used for the property must be declared with the type converter and not the actual property you are exposing. the property is typed with the class as any property should.
[TypeConverter(typeof(TestObjConverter))]
public class TestObj
{
...
}
then in your main control class you just expose the property like normal without the type converter declaration like you have done
[Category("CustomControl"), Description("Property")]
public List<TestObj> TestData { get; set; }
I have several custom controls for winforms if you want a working example of this. Just ask me and i will send one to you

How to get enum type from another project in solution?

I have a wpf converter which Im passing a parameter of the enum type for which it converts the bound integer value to the enumerated text. In order to do this so its generic I need to pull the enum type for a passed in qualified type name.
namespace SomeOtherProject.MyClass
//
public enum MyTypes
{
MyType1 = 0,
MyType2 = 100,
MyType3 = 200,
MyType4 = 300
}
namespace SomeProject.SomeClass
{
//
var typeName = SomeOtherProject.MyClass.MyTypes;
type = Type.GetType(typeName);
This does not retrieve the type and produces a null.
Thanks for any assistance
You can use typeof to get a System.Type for any type. It doesn't matter if it's in the same project or not (as long as the other project is referenced):
Type theType = typeof(SomeOtherProject.MyClass.MyTypes);
This is the custom converter I used to produce the translation from an enum int value to its text value.
WPF Xaml gridviewdatacolumn snippet
... DataMemberBinding="{Binding Path=ProductTypeId, Converter={StaticResource IntEnumValueToDisplayNameConverter1}, ConverterParameter=Solution1.SomePath.SomeOtherPath\, Solution1\.SomePath\, Version\=1\.0\.0\.0\, Culture\=neutral\, PublicKeyToken\=null}"
Converter code example:
namespace Solution1.SomePath.Converters
{
using System;
using System.Globalization;
using System.Windows.Data;
internal class IntEnumValueToDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if (value == null || parameter == null)
return null;
var type = GetTypeFromParameter(parameter);
if (type == null)
return null;
var enumValue = Enum.Parse(type, value.ToString(), true);
if (enumValue == null)
return null;
return enumValue.ToString();
}
catch (Exception)
{
return null;
}
}
private static Type GetTypeFromParameter(object parameter)
{
if (parameter == null)
return null;
Type type;
if (parameter is Type)
{
type = parameter as Type;
if (type == null || !type.IsEnum)
return null;
}
else if (parameter is string)
{
// Must be fully qualified assembly name to work
// Example: Solution1.SomePath.SomeOtherPath, Solution1.SomePath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
var typeName = parameter as string;
if (typeName.IsEmpty())
return null;
type = Type.GetType(typeName);
if (type == null || !type.IsEnum)
return null;
}
else
return null;
return type;
}
}
}

DataTemplate.DataType = Collection<Entity>?

Is there a way to create a data template that handles a list of items?
I have Contact.Phones (EntityCollection<Phone>) and I want the data template to handle the list - add remove edit etc.
Is there a way to set the DataType property of the DataTemplate to generic EntityCollection<Phone>?
In my Emerald Data Foundation (EDF) tool I solved this by creating a more powerful MarkupExtension than x:Type that could specify generic types too. That way I could write:
<DataTemplate TargetType="{edf:Type generic:ICollection{local:Entity}}" />
Here's what I used:
[MarkupExtensionReturnType(typeof(Type))]
public class TypeExtension : MarkupExtension
{
public TypeExtension() { }
public TypeExtension(string typeName) { TypeName = typeName; }
public TypeExtension(Type type) { Type = type; }
public string TypeName { get; set; }
public Type Type { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if(Type==null)
{
IXamlTypeResolver typeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
if(typeResolver==null) throw new InvalidOperationException("EDF Type markup extension used without XAML context");
if(TypeName==null) throw new InvalidOperationException("EDF Type markup extension used without Type or TypeName");
Type = ResolveGenericTypeName(TypeName, (name) =>
{
Type result = typeResolver.Resolve(name);
if(result==null) throw new Exception("EDF Type markup extension could not resolve type " + name);
return result;
});
}
return Type;
}
public static Type ResolveGenericTypeName(string name, Func<string, Type> resolveSimpleName)
{
if(name.Contains('{'))
name = name.Replace('{', '<').Replace('}', '>'); // Note: For convenience working with XAML, we allow {} instead of <> for generic type parameters
if(name.Contains('<'))
{
var match = _genericTypeRegex.Match(name);
if(match.Success)
{
Type[] typeArgs = (
from arg in match.Groups["typeArgs"].Value.SplitOutsideParenthesis(',')
select ResolveGenericTypeName(arg, resolveSimpleName)
).ToArray();
string genericTypeName = match.Groups["genericTypeName"].Value + "`" + typeArgs.Length;
Type genericType = resolveSimpleName(genericTypeName);
if(genericType!=null && !typeArgs.Contains(null))
return genericType.MakeGenericType(typeArgs);
}
}
return resolveSimpleName(name);
}
static Regex _genericTypeRegex = new Regex(#"^(?<genericTypeName>\w+)<(?<typeArgs>\w+(,\w+)*)>$");
}
The generic type name parsing code is in a separate method because it is also used by some other code in EDF. You could combine it all into one method.
Wrap your generic list in a new class, which subclasses your generic list without adding anything.
This makes it possible to bind to the list from XAML.
This is described here:
Can I specify a generic type in XAML (pre .NET 4 Framework)?

Resources