XDocument绑定元素和属性

时间:2012-02-29 16:13:05

标签: wpf linq xaml binding linq-to-xml

我有XDocument这样的DataContext设置为Window的{​​{1}}:

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

现在在我的前端,我测试了几个不同的绑定路径。所有这些都适用于ElementsElementAttribute,但Attributes似乎对我不起作用。我认为这很奇怪,因为ElementsIEnumerable<XElement>AttributesIEnumerable<XAttribute> - 完全相同的集合和所有内容。

<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>

除了Attributes之外,你知道为什么一切都能正确绑定吗?任何帮助表示赞赏。我认为(可能)与事实有关,ElementElements是从XContainer继承的,但这并不能解释为什么XElements非常拥有Attribute有效......

提前致谢! 亚伦

3 个答案:

答案 0 :(得分:1)

Attributes上没有属性XElement(只有方法Attributes()无法直接用于绑定),因此绑定不起作用并不奇怪。

但是也没有属性Elements,那为什么会这样呢?这是因为LINQ to XML对象具有专门用于WPF的特殊“动态属性”,请参阅LINQ to XML Dynamic Properties on MSNDElements上有动态属性XElement,但没有Attributes

我仍然有一件事情我不明白:Elements动态属性被记录为仅以elem.Elements[elementName]形式工作。所以我的代码仍然令人惊讶。

如果您想了解任何变通方法,除了使用Attributes()调用<ObjectDataProvider>方法外,我无法想到任何变通方法。

答案 1 :(得分:0)

Svick对他的回答是正确的。 Elements之所以如你所知,是因为XElement的自定义CustomTypeDescriptor(由XElement上TypeDescriptionProviderAttribute的存在决定)提供了一个名为Elements的自定义PropertyDescriptor,它返回一个IEnumerable&lt; XElement&gt;。如果索引器在绑定路径中遵循了这一点,那么返回的是XContainer.Elements(XName),否则将是XContainer.Elements()。 Attributes不起作用的原因是没有提供这样的动态属性描述符。

下面的代码以与动态Elements属性类似的方式提供了这个缺少的功能(以及Nodes属性)。

 //Add this code in App start up    
 TypeDescriptor.AddProvider(new XElementAdditionalDynamicPropertiesTypeDescriptionProvider(),
 typeof(XElement));

下面的类提供了功能,此代码与Elements的工作方式类似。

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;
    }
}

答案 2 :(得分:0)

此问题的快速可移植解决方案是通过返回其Attributes结果的转换器运行XElement。然后,您可以简单地绑定到元素。

我还在下面过滤掉了易于删除的名称空间。

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();
        }

    }
}