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();
}
}
}
Related
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
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.
In my canonical example, I have two tabs the first with a button and the second with a text box that is bound to a validation rule. When the button is clicked an action occurs that should cause the validation to fail and therefore the Validation.Error event to fire. However, the event will only fire when I click the second tab.
The reason this is so important to me is I have a form with complex validation that occurs across multiple tabs and I want to highlight those tabs containing errors in some - and I especially want to display the errors when the form first loads.
I've already used a technique to force the validation to fire when the form loads but I just don't know why when the forms loaded it doesn't fire when the button is clicked.
The XAML for my test case :
<Window x:Class="WpfApplication1.TabsDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Tabs" Height="300" Width="300" Validation.Error="Window_Error">
<TabControl Grid.Row="0">
<TabItem>
<TabItem.Header>First</TabItem.Header>
<StackPanel Margin="5">
<Button Click="Button_Click">Clear the second textbox</Button>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>MyDataItem</TabItem.Header>
<TextBox>
<TextBox.Text>
<local:ValidationBinding Path="MyDataItem" UpdateSourceTrigger="LostFocus">
<local:ValidationBinding.ValidationRules>
<local:ValidateText />
</local:ValidationBinding.ValidationRules>
</local:ValidationBinding>
</TextBox.Text>
</TextBox>
</TabItem>
</TabControl>
</Window>
My code behind :
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class TabsDemo : Window
{
public TabsDemo()
{
InitializeComponent();
DataContext = new MyViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(DataContext as MyViewModel).MyDataItem = String.Empty;
}
private void Window_Error(object sender, ValidationErrorEventArgs e)
{
MessageBox.Show("Validation Error : " + e.Error.RuleInError);
}
}
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _myDataItem = "Default Value";
public string MyDataItem
{
get { return _myDataItem; }
set
{
if (_myDataItem != value)
{
_myDataItem = value;
NotifyPropertyChanged(new PropertyChangedEventArgs("MyDataItem"));
}
}
}
private void NotifyPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(this, args);
}
}
}
And for completeness here's the validation binding markup extension :
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApplication1
{
public class ValidationBinding : MarkupExtension
{
private readonly Binding _binding = new Binding();
private DependencyObject _dependencyObject;
private DependencyProperty _dependencyProperty;
public ValidationBinding()
{
_binding.ValidatesOnDataErrors = true;
_binding.ValidatesOnExceptions = true;
_binding.NotifyOnValidationError = true;
}
public ValidationBinding(string path)
{
_binding.Path = new PropertyPath(path);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var valueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
_dependencyObject = valueTarget.TargetObject as DependencyObject;
_dependencyProperty = valueTarget.TargetProperty as DependencyProperty;
var element = _dependencyObject as FrameworkElement;
if (element != null)
{
if (element.IsLoaded)
ForceValidation();
else
element.Loaded += (sender, args) => ForceValidation();
}
else
{
ForceValidation();
}
Debug.WriteLine("MarkupExtension.ProvideValue called for element " + element.Name);
return _binding.ProvideValue(serviceProvider);
}
private void ForceValidation()
{
BindingOperations.GetBindingExpression(_dependencyObject, _dependencyProperty).UpdateSource();
}
public object FallbackValue
{
get { return _binding.FallbackValue; }
set { _binding.FallbackValue = value; }
}
public string StringFormat
{
get { return _binding.StringFormat; }
set { _binding.StringFormat = value; }
}
public object TargetNullValue
{
get { return _binding.TargetNullValue; }
set { _binding.TargetNullValue = value; }
}
public string BindingGroupName
{
get { return _binding.BindingGroupName; }
set { _binding.BindingGroupName = value; }
}
public Collection<ValidationRule> ValidationRules
{
get { return _binding.ValidationRules; }
}
public bool ValidatesOnExceptions
{
get { return _binding.ValidatesOnExceptions; }
set { _binding.ValidatesOnExceptions = value; }
}
public bool ValidatesOnDataErrors
{
get { return _binding.ValidatesOnDataErrors; }
set { _binding.ValidatesOnDataErrors = value; }
}
public PropertyPath Path
{
get { return _binding.Path; }
set { _binding.Path = value; }
}
public string XPath
{
get { return _binding.XPath; }
set { _binding.XPath = value; }
}
public BindingMode Mode
{
get { return _binding.Mode; }
set { _binding.Mode = value; }
}
public UpdateSourceTrigger UpdateSourceTrigger
{
get { return _binding.UpdateSourceTrigger; }
set { _binding.UpdateSourceTrigger = value; }
}
public bool NotifyOnSourceUpdated
{
get { return _binding.NotifyOnSourceUpdated; }
set { _binding.NotifyOnSourceUpdated = value; }
}
public bool NotifyOnTargetUpdated
{
get { return _binding.NotifyOnTargetUpdated; }
set { _binding.NotifyOnTargetUpdated = value; }
}
public bool NotifyOnValidationError
{
get { return _binding.NotifyOnValidationError; }
set { _binding.NotifyOnValidationError = value; }
}
public IValueConverter Converter
{
get { return _binding.Converter; }
set { _binding.Converter = value; }
}
public object ConverterParameter
{
get { return _binding.ConverterParameter; }
set { _binding.ConverterParameter = value; }
}
public CultureInfo ConverterCulture
{
get { return _binding.ConverterCulture; }
set { _binding.ConverterCulture = value; }
}
public object Source
{
get { return _binding.Source; }
set { _binding.Source = value; }
}
public RelativeSource RelativeSource
{
get { return _binding.RelativeSource; }
set { _binding.RelativeSource = value; }
}
public string ElementName
{
get { return _binding.ElementName; }
set { _binding.ElementName = value; }
}
public bool IsAsync
{
get { return _binding.IsAsync; }
set { _binding.IsAsync = value; }
}
public object AsyncState
{
get { return _binding.AsyncState; }
set { _binding.AsyncState = value; }
}
public bool BindsDirectlyToSource
{
get { return _binding.BindsDirectlyToSource; }
set { _binding.BindsDirectlyToSource = value; }
}
public UpdateSourceExceptionFilterCallback UpdateSourceExceptionFilter
{
get { return _binding.UpdateSourceExceptionFilter; }
set { _binding.UpdateSourceExceptionFilter = value; }
}
}
}
Hi all this is my first question :)
This exemple tested on winform application and wpf application and the problem with binding on WPF
winform all works fine with ICustomTypeDescriptor and grid draw only columns added to Dictionary Properties (Name Age) and Male excluded
WPF all properties of the class person drawed on grid (Name Age Male)
any idea about this situation or interfaces equivalent of ICustomTypeDescriptor in wpf ?
<Grid>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="90,30,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="325" />
</Grid>
List<Person> persons = new List<Person>();
persons.Add(new Person("Aymane", 30));
persons.Add(new Person("Raouia", 30));
grid.ItemsSource = persons; //wpf
grid.DataSource = persons; //winform
public class Person : ICustomTypeDescriptor
{
Dictionary<string, object> Properties = new Dictionary<string, object>();
public Person()
{
Properties.Add("Name", null);
Properties.Add("Age", null);
}
public Person(string name, object value)
: base()
{
Male = true;
Name = name;
Age = value;
}
public bool Male { get; set; }
public object Age { get { return Properties["Age"]; } set { Properties["Age"] = value; } }
public object Name { get { return Properties["Name"]; } set { Properties["Name"] = value; } }
#region ICustomTypeDescriptor Members
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
string ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
string ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(attributes, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return ((ICustomTypeDescriptor)this).GetEvents(null);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<PropertyDescriptor> props = new List<PropertyDescriptor>();
props.Add(new PersonPropertyDescriptor("Name", attributes));
props.Add(new PersonPropertyDescriptor("Age", attributes));
return new PropertyDescriptorCollection(props.ToArray());
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
class PersonPropertyDescriptor : PropertyDescriptor
{
public PersonPropertyDescriptor(string name, Attribute[] attrs)
: base(name, attrs)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return typeof(Person); }
}
public override object GetValue(object component)
{
return ((Person)component).Properties[Name];
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(object); }
}
public override void ResetValue(object component)
{
((Person)component).Properties[Name] = null;
}
public override void SetValue(object component, object value)
{
((Person)component).Properties[Name] = value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
}
To gain control over the column generation handle the AutoGeneratingColumn event, have you can suppress the generation of a column by seting e.Cancel = true;
In your case:
private void DataGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dataGrid = sender as DataGrid;
if (dataGrid != null)
{
ICustomTypeDescriptor typeDescriptor =
dataGrid.Items[0] as ICustomTypeDescriptor;
if (typeDescriptor != null)
{
var props = typeDescriptor.GetProperties();
if (!props.Contains((PropertyDescriptor)e.PropertyDescriptor))
{
e.Cancel = true;
}
}
}
}
With the DataGrid definition of:
<DataGrid
AutoGenerateColumns="True"
Height="311"
HorizontalAlignment="Left"
Name="dataGrid1"
VerticalAlignment="Top"
Width="509"
AutoGeneratingColumn="DataGridAutoGeneratingColumn">
Gives the desired result.
Here the correct implementation of ICustomTypeDescriptor & ITypedList
namespace CustomTypeDescriptor
{
class Row : ICustomTypeDescriptor { }
class RowsCollection : List<Row>, ITypedList { }
class Table : IListSource, IEnumerable<Row>, IEnumerator<Row>
{
RowsCollection Rows { get; set; }
}
}
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.