Элементы и атрибуты связывания XDocument - PullRequest
1 голос
/ 29 февраля 2012

У меня есть XDocument как этот, установленный как DataContext моего 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

Теперь в моем интерфейсе я тестирую пару различных путей привязки.Все они работают с Elements, Element, Attribute, но Attributes, похоже, не работает для меня.Я считаю это довольно странным, потому что Elements - это IEnumerable<XElement>, а Attributes - это IEnumerable<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?Любая помощь приветствуется.Я думаю, что это (возможно) имеет какое-то отношение к факту, что Element и Elements наследуются от XContainer, но это не объясняет, почему XElements очень собственный Attribute работает ...

Заранее спасибо!Аарон

Ответы [ 2 ]

1 голос
/ 01 марта 2012

Нет свойства Attributes для XElement (только метод Attributes(), который нельзя использовать непосредственно в привязке), поэтому неудивительно, что привязка не работает.

Но также нет свойства Elements, так почему это работает? Это связано с тем, что объекты LINQ to XML имеют специальные «динамические свойства», специально предназначенные для использования в WPF, см. Динамические свойства LINQ to XML в MSND . Существует динамическое свойство Elements для XElement, но нет Attributes.

Однако есть еще одна вещь, которую я не понимаю: динамическое свойство Elements задокументировано для работы только в форме elem.Elements[elementName]. Поэтому мне все еще удивительно, что твой код работает.

Если вы хотите узнать о каких-либо обходных путях, я не могу думать ни о каких, кроме вызова метода Attributes() с использованием <ObjectDataProvider>.

0 голосов
/ 24 декабря 2016

Свик прав в своем ответе. Причина, по которой Elements работает так, как вы обнаружили, заключается в том, что пользовательский CustomTypeDescriptor для XElement (определяемый наличием TypeDescriptionProviderAttribute в XElement) предоставляет пользовательский PropertyDescriptor с именем Elements, который возвращает IEnumerable . Если за этим в пути привязки следует индексатор, то возвращается XContainer.Elements (XName), в противном случае будет XContainer.Elements (). Причина, по которой атрибуты не работают, заключается в том, что такой динамический дескриптор свойства не предоставляется.

Приведенный ниже код обеспечивает эту недостающую функциональность (а также свойство Nodes) аналогично динамическому свойству Elements.

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

Классы, представленные ниже, предоставляют функциональность, и этот код аналогичен тому, как работают элементы.

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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...