WPF designer properties window: property does not show nested properties - wpf

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

Related

WPF XAML editor always says Dictionary with custom key type is not a collection

I'm trying to clear up some squigglies in our project's XAML. The issue I'm facing is that we use dictionaries that use a custom type as the key rather than string.
This works fine at runtime, but the XAML editor squigglies the uses of them with the error:
Type 'Dictionary`2' is not a Collection
Example:
<Window x:Class="DictionaryCustomKey.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:DictionaryCustomKey"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800" Height="450"
DataContext="{x:Static local:MainWindow.TheData}"
mc:Ignorable="d">
<StackPanel>
<ListView ItemsSource="{Binding StringKeyDictionary}" />
<TextBlock Text="String First is:" />
<TextBlock Text="{Binding StringKeyDictionary[First]}" />
<ListView ItemsSource="{Binding MyKeyDictionary}" />
<TextBlock Text="MyKey First is:" />
<TextBlock Text="{Binding MyKeyDictionary[First]}" />
</StackPanel>
</Window>
The very last instance of "First" is squigglied.
Here's the codebehind where I try everything I can imagine to make the editor happy that the key type "MyKey" is suitable as a Dictionary key:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
namespace DictionaryCustomKey
{
public class Data
{
public class MyKeyTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
return new MyKey((string)value);
}
return null;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value == null)
{
return null;
}
if (destinationType == typeof(string))
{
return ((MyKey)value).String;
}
return null;
}
}
[TypeConverter(typeof(MyKeyTypeConverter))]
public class MyKey : IComparable, IComparable<MyKey>, IComparable<string>, IEquatable<MyKey>, IEquatable<string>, IConvertible
{
public MyKey(string stringKey)
{
m_String = stringKey;
}
public static implicit operator MyKey(string v)
{
return new MyKey(v);
}
public static implicit operator string(MyKey v)
{
return v.m_String;
}
public String String => m_String;
private String m_String;
public override string ToString()
{
return String;
}
public override bool Equals(object obj)
{
if (obj is MyKey)
{
return ((MyKey)obj) == this;
}
if (obj is string)
{
return ((string)obj) == this.String;
}
return false;
}
public bool Equals(MyKey other)
{
return this == other;
}
public override int GetHashCode()
{
return String.GetHashCode();
}
public int CompareTo(object obj)
{
return String.CompareTo(obj);
}
public int CompareTo(MyKey other)
{
return String.CompareTo(other);
}
public int CompareTo(string other)
{
return String.CompareTo(other);
}
public bool Equals(string other)
{
return String.Equals(other);
}
public TypeCode GetTypeCode()
{
return String.GetTypeCode();
}
public bool ToBoolean(IFormatProvider provider)
{
return ((IConvertible)String).ToBoolean(provider);
}
public char ToChar(IFormatProvider provider)
{
return ((IConvertible)String).ToChar(provider);
}
public sbyte ToSByte(IFormatProvider provider)
{
return ((IConvertible)String).ToSByte(provider);
}
public byte ToByte(IFormatProvider provider)
{
return ((IConvertible)String).ToByte(provider);
}
public short ToInt16(IFormatProvider provider)
{
return ((IConvertible)String).ToInt16(provider);
}
public ushort ToUInt16(IFormatProvider provider)
{
return ((IConvertible)String).ToUInt16(provider);
}
public int ToInt32(IFormatProvider provider)
{
return ((IConvertible)String).ToInt32(provider);
}
public uint ToUInt32(IFormatProvider provider)
{
return ((IConvertible)String).ToUInt32(provider);
}
public long ToInt64(IFormatProvider provider)
{
return ((IConvertible)String).ToInt64(provider);
}
public ulong ToUInt64(IFormatProvider provider)
{
return ((IConvertible)String).ToUInt64(provider);
}
public float ToSingle(IFormatProvider provider)
{
return ((IConvertible)String).ToSingle(provider);
}
public double ToDouble(IFormatProvider provider)
{
return ((IConvertible)String).ToDouble(provider);
}
public decimal ToDecimal(IFormatProvider provider)
{
return ((IConvertible)String).ToDecimal(provider);
}
public DateTime ToDateTime(IFormatProvider provider)
{
return ((IConvertible)String).ToDateTime(provider);
}
public string ToString(IFormatProvider provider)
{
return String.ToString(provider);
}
public object ToType(Type conversionType, IFormatProvider provider)
{
return ((IConvertible)String).ToType(conversionType, provider);
}
public static bool operator ==(MyKey lhs, MyKey rhs)
{
return lhs.String == rhs.String;
}
public static bool operator !=(MyKey lhs, MyKey rhs)
{
return !(lhs == rhs);
}
}
public Dictionary<string, object> StringKeyDictionary { get; set; } = new Dictionary<string, object>
{
{"First", "zero"},
{"Second", "one"}
};
public Dictionary<MyKey, object> MyKeyDictionary { get; set; } = new Dictionary<MyKey, object>
{
{new MyKey("First"), "zero"},
{new MyKey("Second"), "one"}
};
}
public partial class MainWindow : Window
{
static public Data TheData { get; set; } = new Data();
public MainWindow()
{
InitializeComponent();
}
}
}
But nothing seems to work.
Edit: An answer is given that makes the squiggly go away, but it reveals there is actually a worse problem: squiggly or not, the designer has trouble displaying the binding: https://developercommunity.visualstudio.com/content/problem/848331/visual-studio-must-be-restarted-after-every-build.html
This appears to be a bug in the XAML editor. Using nested element syntax doesn't have the error:
<TextBlock>
<TextBlock.Text>
<Binding Path="MyKeyDictionary[First]" />
</TextBlock.Text>
</TextBlock>
I would recommend reporting it through the Visual Studio feedback tool.

Setting property of class type with TypeConverter from Property Panel produces expanded XAML, not string

I'm trying to create a User Control with a property whose type is a class I've defined. I'm using a TypeConverter to allow the property to be processed as a string. The application correctly handles reading XAML where the property is a string, but if the property is set to a string through the property panel, then the XAML contains an expanded syntax breaking out the user-defined class.
Concretely, since that was a bit hard to follow, I'm following this Microsoft tutorial. I have the following code as a result:
Complex.cs
namespace WpfApplication1
{
[TypeConverter(typeof(ComplexTypeConverter))]
public class Complex
{
private double m_real;
private double m_imag;
public Complex() { }
public Complex(double r, double i)
{
m_real = r;
m_imag = i;
}
public double Real
{
get { return m_real; }
set { m_real = value; }
}
public double Imaginary
{
get { return m_imag; }
set { m_imag = value; }
}
public override string ToString()
{
return String.Format("{0},{1}", this.m_real, this.m_imag);
}
public static Complex Parse(string complexNumber)
{
if (String.IsNullOrEmpty(complexNumber))
{
return new Complex();
}
// The parts array holds the real and
// imaginary parts of the object.
string[] parts = complexNumber.Split(',');
return new Complex(double.Parse(parts[0].Trim()), double.Parse(parts[1].Trim()));
}
}
public class ComplexTypeConverter : TypeConverter
{
private static List<Complex> defaultValues = new List<Complex>();
static ComplexTypeConverter()
{
defaultValues.Add(new Complex(0, 0));
defaultValues.Add(new Complex(1, 1));
defaultValues.Add(new Complex(-1, 1));
defaultValues.Add(new Complex(-1, -1));
defaultValues.Add(new Complex(1, -1));
}
// Override CanConvertFrom to return true for String-to-Complex conversions.
public override bool CanConvertFrom(
ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
// Override CanConvertTo to return true for Complex-to-String conversions.
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
// Override ConvertFrom to convert from a string to an instance of Complex.
public override object ConvertFrom(
ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value)
{
string text = value as string;
if (text != null)
return Complex.Parse(text);
return base.ConvertFrom(context, culture, value);
}
// Override ConvertTo to convert from an instance of Complex to string.
public override object ConvertTo(
ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value,
Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
//Convert Complex to a string in a standard format.
Complex c = value as Complex;
if (c != null && this.CanConvertTo(context, destinationType))
{
return c.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override TypeConverter.StandardValuesCollection GetStandardValues(
ITypeDescriptorContext context)
{
StandardValuesCollection svc = new StandardValuesCollection(defaultValues);
return svc;
}
}
}
ComplexNumberControl.xaml.cs
namespace WpfApplication1
{
public partial class ComplexNumberControl : UserControl
{
public ComplexNumberControl()
{
InitializeComponent();
}
public Complex ComplexNumber
{
get
{
return (Complex)this.GetValue(ComplexNumberProperty);
}
set
{
this.SetValue(ComplexNumberProperty, value);
}
}
public static readonly DependencyProperty ComplexNumberProperty = DependencyProperty.Register(
"ComplexNumber",
typeof(Complex),
typeof(ComplexNumberControl),
new PropertyMetadata(new Complex()));
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:ComplexNumberControl HorizontalAlignment="Left" Margin="88,78,0,0" x:Name="complexNumberControl1" VerticalAlignment="Top" />
</Grid>
</Window>
I can add ComplexNumber="0,0" to the ComplexNumberControl with no error (and I know, from more complicated assemblies, that the property is correctly handled as the Complex number 0 + 0i). However, if I edit ComplexNumber in the property panel, the XAML changes to:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:ComplexNumberControl HorizontalAlignment="Left" Margin="88,78,0,0" x:Name="complexNumberControl1" VerticalAlignment="Top">
<my:ComplexNumberControl.ComplexNumber>
<my:Complex Imaginary="-1" Real="1" />
</my:ComplexNumberControl.ComplexNumber>
</my:ComplexNumberControl>
</Grid>
</Window>
How can I ensure the generated XAML simply reads ComplexNumber="1,-1", instead of the verbose ComplexNumberControl.ComplexNumber construct?
You almost there and needs only two minor adjustments on Complex class to make it work as you expect:
1) Remove default public constructor:
public Complex() { } // <- delete this line
2) Add magical DesignerSerializationVisibility attribute to Real and Imaginary properties (or in general - to all properties with public setter):
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double Real
{
get { return m_real; }
set { m_real = value; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double Imaginary
{
get { return m_imag; }
set { m_imag = value; }
}
Hope this helps.

XDocument binding Elements and Attributes

I have an XDocument like this one set as a DataContext of my Window:
Class MainWindow
Public Sub New()
InitializeComponent()
Me.DataContext = <?xml version="1.0" encoding="utf-8"?>
<Sketch Format="A4" Author="Aaron" Created="..." Test="Value">
<Item Kind="Line" X1="50" Y1="50" X2="150" Y2="150">
<Item Kind="Rect" X="10" Y="10" Width="30" Height="30"/>
</Item>
<Item Kind="Line" X1="250" Y1="250" X2="250" Y2="50">
<Item Kind="Ellipse" X="10" Y="10" Width="30" Height="30"/>
</Item>
<Test Param="Value"/>
</Sketch>
End Sub
End Class
Now in my frontend I test couple of different binding paths. All of them works with Elements, Element, Attribute, but Attributes doesn't seem to work for me. I consider it rather odd, because Elements is IEnumerable<XElement> and Attributes is IEnumerable<XAttribute> -- exactly the same kind of collection and everything.
<Window Height="320" Title="Main Window" Width="640" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="MainWindow">
<UniformGrid Columns="3">
<StackPanel>
<Label Foreground="DimGray">Root.Elements.Count</Label>
<Label Content="{Binding Path=Root.Elements.Count, FallbackValue=Loading…}"/>
<Label Foreground="DimGray">Root.Attributes.Count</Label>
<Label Content="{Binding Path=Root.Attributes.Count, FallbackValue=Loading…}"/>
<Label Foreground="DimGray">Root.Element[Test]</Label>
<Label Content="{Binding Path=Root.Element[Test], FallbackValue=Loading…}"/>
<Label Foreground="DimGray">Root.Attribute[Test]</Label>
<Label Content="{Binding Path=Root.Attribute[Test], FallbackValue=Loading…}"/>
</StackPanel>
<StackPanel>
<Label Foreground="DimGray">Root.Elements</Label>
<ListBox ItemsSource="{Binding Root.Elements}"/>
<Label Foreground="DimGray">Root.Attributes</Label>
<ListBox ItemsSource="{Binding Root.Attributes}"/>
</StackPanel>
<StackPanel>
<TreeView ItemsSource="{Binding Root.Elements}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Elements}">
<Label Content="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</UniformGrid>
</Window>
Do you have any idea why everything binds correctly except Attributes? Any help is appreciated. I think is has (maybe) got something to do with a fact, that Element and Elements are inherited from XContainer, but this doesn't explain why XElements very own Attribute works...
Thanks in advance!
Aaron
There is no property Attributes on XElement (only method Attributes() that can't be used directly in binding), so it's not surprising the binding doesn't work.
But there is also no property Elements, so why does that work? It's because LINQ to XML objects have special “dynamic properties” specifically for use in WPF, see LINQ to XML Dynamic Properties. There is a dynamic property Elements on XElement, but no Attributes.
There's still one thing I don't understand though: The Elements dynamic property is documented to work only in the form elem.Elements[elementName]. So it's still surprising to me that your code works.
If you want to know about any workarounds, I can't think of any, except for invoking the Attributes() method using <ObjectDataProvider>.
Svick is correct with his response. The reason why Elements works as you have discovered is that the custom CustomTypeDescriptor for XElement ( determined by the presence of the TypeDescriptionProviderAttribute on XElement ) provides a custom PropertyDescriptor with name Elements which returns an IEnumerable<XElement>. If this is followed in the binding path by an indexer then what is returned is XContainer.Elements(XName) otherwise will be XContainer.Elements(). The reason why Attributes does not work is that there is no such dynamic property descriptor provided.
The code below provides this missing functionality ( and also a Nodes property ) in a similar manner to the dynamic Elements property.
//Add this code in App start up
TypeDescriptor.AddProvider(new XElementAdditionalDynamicPropertiesTypeDescriptionProvider(),
typeof(XElement));
The classes below provide the functionality and this code is similar to how Elements work.
public class XDeferredAxis : IEnumerable<XAttribute>
{
internal XElement element;
private Func<XElement, XName, IEnumerable<XAttribute>> func;
private XName name;
public IEnumerator<XAttribute> GetEnumerator()
{
return this.func(this.element, this.name).GetEnumerator();
}
public XDeferredAxis(Func<XElement, XName, IEnumerable<XAttribute>> func, XElement element, XName name)
{
if (func == null)
{
throw new ArgumentNullException("func");
}
if (element == null)
{
throw new ArgumentNullException("element");
}
this.func = func;
this.element = element;
this.name = name;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public class XElementNodesPropertyDescriptor : PropertyDescriptor
{
private XElement element;
private bool childRemoved;
public XElementNodesPropertyDescriptor() : base("Nodes", null)
{
}
public override void AddValueChanged(object component, EventHandler handler)
{
bool flag = base.GetValueChangedHandler(component) != null;
base.AddValueChanged(component, handler);
if (!flag)
{
XElement local = component as XElement;
if ((local != null) && (base.GetValueChangedHandler(component) != null))
{
element = local;
local.Changing += new EventHandler<XObjectChangeEventArgs>(this.OnChanging);
local.Changed += new EventHandler<XObjectChangeEventArgs>(this.OnChanged);
}
}
}
private void OnChanging(object sender, XObjectChangeEventArgs e)
{
childRemoved = false;
if (e.ObjectChange == XObjectChange.Remove)
{
XObject senderNode = (XObject)sender;
if (senderNode.Parent == element)
{
childRemoved = true;
}
}
}
private void OnChanged(object sender, XObjectChangeEventArgs e)
{
XObject senderNode = (XObject)sender;
switch (e.ObjectChange)
{
case XObjectChange.Add:
case XObjectChange.Value:
case XObjectChange.Name:
if (senderNode.Parent == element)
{
this.OnValueChanged(element, EventArgs.Empty);
}
break;
case XObjectChange.Remove:
if (childRemoved)
{
this.OnValueChanged(element, EventArgs.Empty);
}
break;
}
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
base.RemoveValueChanged(component, handler);
XElement local = component as XElement;
if ((local != null) && (base.GetValueChangedHandler(component) == null))
{
local.Changed -= new EventHandler<XObjectChangeEventArgs>(this.OnChanged);
}
}
public override bool SupportsChangeEvents
{
get
{
return true;
}
}
public override Type ComponentType
{
get
{
return typeof(XElement);
}
}
public override bool IsReadOnly
{
get
{
return true;
}
}
public override Type PropertyType
{
get
{
return typeof(IEnumerable<XNode>);
}
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
var nodes= (component as XElement).Nodes();
return nodes;
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
public class XElementAttributesPropertyDescriptor : PropertyDescriptor
{
private XDeferredAxis value;
private bool removalIsOwnAttribute;
public XElementAttributesPropertyDescriptor() : base("Attributes", null) {
}
public override void AddValueChanged(object component, EventHandler handler)
{
bool flag = base.GetValueChangedHandler(component) != null;
base.AddValueChanged(component, handler);
if (!flag)
{
XElement local = component as XElement;
if ((local != null) && (base.GetValueChangedHandler(component) != null))
{
local.Changing += new EventHandler<XObjectChangeEventArgs>(this.OnChanging);
local.Changed += new EventHandler<XObjectChangeEventArgs>(this.OnChanged);
}
}
}
private void OnChanging(object sender, XObjectChangeEventArgs e)
{
removalIsOwnAttribute = false;
if (e.ObjectChange == XObjectChange.Remove)
{
var xAttribute = sender as XAttribute;
if (xAttribute != null && xAttribute.Parent == value.element)
{
removalIsOwnAttribute = true;
}
}
}
private void OnChanged(object sender, XObjectChangeEventArgs e)
{
var changeRequired = false;
var xAttribute = sender as XAttribute;
if (xAttribute != null)
{
switch (e.ObjectChange)
{
case XObjectChange.Name:
case XObjectChange.Add:
if (xAttribute.Parent == value.element)
{
changeRequired = true;
}
break;
case XObjectChange.Remove:
changeRequired = removalIsOwnAttribute;
break;
}
if (changeRequired)
{
this.OnValueChanged(value.element, EventArgs.Empty);
}
}
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
base.RemoveValueChanged(component, handler);
XElement local = component as XElement;
if ((local != null) && (base.GetValueChangedHandler(component) == null))
{
local.Changed -= new EventHandler<XObjectChangeEventArgs>(this.OnChanged);
}
}
public override bool SupportsChangeEvents
{
get
{
return true;
}
}
public override Type ComponentType
{
get
{
return typeof(XElement);
}
}
public override bool IsReadOnly
{
get
{
return true;
}
}
public override Type PropertyType
{
get
{
return typeof(IEnumerable<XAttribute>);
}
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return (object)(this.value = new XDeferredAxis((Func<XElement, XName, IEnumerable<XAttribute>>)((e, n) =>
{
if (!(n != (XName)null))
return e.Attributes();
return e.Attributes(n);
}), component as XElement, (XName)null));
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
public class XElementAdditionalDynamicPropertiesTypeDescriptionProvider: TypeDescriptionProvider
{
public XElementAdditionalDynamicPropertiesTypeDescriptionProvider() : this(TypeDescriptor.GetProvider(typeof(XElement))) { }
protected XElementAdditionalDynamicPropertiesTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
var baseTypeDescriptor= base.GetTypeDescriptor(objectType, instance);
return new XElementAdditionalDynamicPropertiesTypeDescriptor(baseTypeDescriptor);
}
}
public class XElementAdditionalDynamicPropertiesTypeDescriptor : CustomTypeDescriptor
{
public XElementAdditionalDynamicPropertiesTypeDescriptor(ICustomTypeDescriptor original) : base(original) { }
public override PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection descriptors = new PropertyDescriptorCollection(null);
if (attributes == null)
{
descriptors.Add(new XElementAttributesPropertyDescriptor());
descriptors.Add(new XElementNodesPropertyDescriptor());
}
foreach (PropertyDescriptor pd in base.GetProperties(attributes))
{
descriptors.Add(pd);
}
return descriptors;
}
}
A quick portable workaround for this issue is to run the XElement through a converter that returns its Attributes results. Then you can simply bind to the element.
I also filter out namespaces below, easily removable.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Xml.Linq;
namespace FSW.Core.Utility
{
[ValueConversion(typeof(XElement), typeof(IEnumerable<XAttribute>))]
public class XElementToXAttributesConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var element = value as XElement;
return element?.Attributes().Where(x=>x.Name.LocalName != "xmlns");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

Why does WPF seem to bypass TypeDescriptionProviderAttribute when INotifyPropertyChanged is implemented?

I'm trying to use the [TypeDescriptionProviderAttribute] in order to give my class a custom type descriptor. This works, but when I implement INotifyPropertyChanged WPF seems to ignore the custom type descriptor and go straight for the CLR property (if it exists). Here's a snippet, I'll paste the full example later on:
//[TypeDescriptionProvider(typeof(MyProvider))]
class MyModel : Object
//, INotifyPropertyChanged
//, ICustomTypeDescriptor
{
public string TheProperty { get { return "CLR - TheProperty"; } }
I bind a TextBlock to TheProperty. When I...
Leave everything commented
I see "CLR - TheProperty" as expected.
Use [TypeDescriptionProvider]
I see "MyPropertyDescriptor - TheProperty" as expected.
Use ICustomTypeDescriptor
I see "MyPropertyDescriptor - TheProperty" as expected.
Use ICustomTypeDescriptor and INotifyPropertyChanged
I see "MyPropertyDescriptor - TheProperty" as expected.
Use [TypeDescriptionProvider] and INotifyPropertyChanged
I see "CLR - TheProperty". Why is this? The weird thing is that custom properties without a CLR property are shown normally. My custom type descriptor also returns a "MyPropertyDescriptor - AnotherProperty" which works in all cases because there is no CLR AnotherProperty defined.
In summary, given this XAML
<StackPanel>
<TextBlock Text="{Binding TheProperty}" />
<TextBlock Text="{Binding AnotherProperty}" />
</StackPanel>
AnotherProperty always works as expected because the model does not have a CLR property named "AnotherProperty". TheProperty works as expected except when [TypeDescriptionProvider] and INotifyPropertyChanged are both used.
Here's the full code. It's a bit long but most of it is irrelevant, it's just required by System.ComponentModel
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
DataContext = new MyModel();
}
}
//[TypeDescriptionProvider(typeof(MyProvider))]
class MyModel : Object
//, INotifyPropertyChanged
//, ICustomTypeDescriptor
{
public string TheProperty { get { return "CLR - TheProperty"; } }
public event PropertyChangedEventHandler PropertyChanged;
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this);
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return TypeDescriptor.GetProperties(this, attributes);
}
public PropertyDescriptorCollection GetProperties()
{
return MyTypeDescriptor.GetCustomProperties();
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
}
class MyProvider : TypeDescriptionProvider
{
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new MyTypeDescriptor();
}
}
class MyTypeDescriptor : CustomTypeDescriptor
{
public override PropertyDescriptorCollection GetProperties()
{
return GetCustomProperties();
}
public static PropertyDescriptorCollection GetCustomProperties()
{
return new PropertyDescriptorCollection(
new[] {
new MyPropertyDescriptor("TheProperty"),
new MyPropertyDescriptor("AnotherProperty")
});
}
}
class MyPropertyDescriptor : PropertyDescriptor
{
public MyPropertyDescriptor(string propName)
: base(propName, null)
{
}
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return typeof(MyModel); }
}
public override object GetValue(object component)
{
return "MyPropertyDescriptor - " + Name;
}
public override bool IsReadOnly
{
get { return true; }
}
public override Type PropertyType
{
get { return typeof(string); }
}
public override void ResetValue(object component)
{
throw new InvalidOperationException("cannot reset value");
}
public override void SetValue(object component, object value)
{
throw new InvalidOperationException("property is readonly");
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
}
Old question, but for people looking for an answer..
Problem is in System.Windows.PropertyPath.ResolvePropertyName(String, Object, Type, Object, Boolean) private method. I have found it in PresentationFramework.dll in .NET 4.0.
Extracted from .NET Reflector:
object propertyHelper = DependencyProperty.FromName(str, ownerType);
if ((propertyHelper == null) && (item is ICustomTypeDescriptor))
{
propertyHelper = TypeDescriptor.GetProperties(item)[str];
}
if ((propertyHelper == null) && ((item is INotifyPropertyChanged) || (item is DependencyObject)))
{
propertyHelper = this.GetPropertyHelper(ownerType, str);
}
if (propertyHelper == null)
{
propertyHelper = TypeDescriptor.GetProperties(item)[str];
}
if (propertyHelper == null)
{
propertyHelper = this.GetPropertyHelper(ownerType, str);
}
if ((propertyHelper == null) && throwOnError)
{
throw new InvalidOperationException(SR.Get("PropertyPathNoProperty", new object[] { ownerType.Name, str }));
}
return propertyHelper;
As you can see, retrieving property identifier (DependencyProperty / PropertyDescriptor / PropertyInfo) goes like this:
Try get DependencyProperty,
If item implements ICustomTypeDescriptor, use TypeDescriptor to get PropertyDescriptor,
If item implements INotifyPropertyChanged or is DependencyObject, use System.Reflection to get PropertyInfo,
Else use TypeDescriptor to get PropertyDescriptor,
Else use System.Reflection to get PropertyInfo,
Else throw exception or return null.
So System.Reflection/PropertyInfo gets precedence over TypeDescriptor/PropertyDescriptor if item implements INotifyPropertyChanged interface. I believe they choose this strategy for performance reasons because PropertyInfo is much more lighter than PropertyDescriptor.
Solution to your problem would be to implement ICustomTypeDescriptor (preferably explicitly) so that it transfers ICustomTypeDescriptor method calls to appropriate TypeDescriptor method calls but not with object parameter, but Type parameter (this.GetType()). This way your TypeDescriptionProvider will be used.

How to define value of dynamic resource based on another dynamic resource?

Is it possible to assign value to a dynamic resource from another dynamic resource?
For example
<sys:Double x:Key="ButtonWidth">48</sys:Double>
<sys:Double x:Key="SmallButtonWidth"> ButtonWidth / 2 </sys:Double>
It is possible to transform a value using a custom MarkupExtension.
e.g.
<Window.Resources xmlns:ms="clr-namespace:WpfApplication1.MathShit">
<sys:Double x:Key="ButtonWidth">48</sys:Double>
<ms:Calculation x:Key="SmallButtonWidth">
<ms:Product Operand1="{ms:Value {StaticResource ButtonWidth}}"
Operand2="0.5" />
</ms:Calculation>
</Window.Resources>
namespace WpfApplication1.MathShit
{
[ContentProperty("Expression")]
public class Calculation : MarkupExtension
{
public IExpression Expression { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Expression == null) throw new Exception("Expression cannot be null.");
return Expression.CalculateValue();
}
}
[TypeConverter(typeof(ExpressionConverter))]
public interface IExpression
{
double CalculateValue();
}
public abstract class BinaryOperation : IExpression
{
public IExpression Operand1 { get; set; }
public IExpression Operand2 { get; set; }
public double CalculateValue()
{
if (Operand1 == null) throw new Exception("Operand1 cannot be null.");
if (Operand2 == null) throw new Exception("Operand2 cannot be null.");
return CalculateBinaryOperation();
}
protected abstract double CalculateBinaryOperation();
}
public class Sum : BinaryOperation
{
protected override double CalculateBinaryOperation()
{
return Operand1.CalculateValue() + Operand2.CalculateValue();
}
}
public class Product : BinaryOperation
{
protected override double CalculateBinaryOperation()
{
return Operand1.CalculateValue() * Operand2.CalculateValue();
}
}
public class Value : MarkupExtension, IExpression
{
public double? Double { get; set; }
public Value() { }
public Value(double #double)
: this()
{
this.Double = #double;
}
public double CalculateValue()
{
if (Double == null) throw new Exception("Double");
return Double.Value;
}
// Allows easy object instantiation in XAML attributes. (Result of StaticResource is not piped through ExpressionConverter.)
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class ExpressionConverter : DoubleConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
var doubleValue = (double)base.ConvertFrom(context, culture, value);
return (IExpression)new Value(doubleValue);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
var val = (Value)value;
return base.ConvertTo(context, culture, val.CalculateValue(), destinationType);
}
}
}
With this you can build arbitrary expression trees (which is a lot more easy than parsing math strings which you can do as well of course if you do not mind the trouble).

Resources