Reputation: 365
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
Upvotes: 1
Views: 1523
Reputation: 244878
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>
.
Upvotes: 2
Reputation: 118
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();
}
}
}
Upvotes: 0
Reputation: 630
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;
}
}
Upvotes: 0