views:

280

answers:

2

Hi,

all I found so far about databinding to XML always only seems to be binding to a fixed XML structure. Would be nice, if anyone could point me in any direction, everything I tried so far seems very awkward.

This is what I would like to do:

Binding the Checked state of a Checkbox to a node within the XML. So it is checked if this specific node exists in the XML and unchecked if not.

For a OneWay binding this is not hard (binding to the node with a Converter that returns "value!=null")

Now i also would like to see this specific node added to/removed from the underlying XmlDocument.

Is there a simple way to do this?

What I tried so far is doing the Binding OneWay and adding a CheckedChanged Eventhandler to Handle the adding/removing part. The code to this is terrible and it doesn't update the Bindings to this XmlDataProvider.

As a general and simplified Question: What is a good way to add new XmlNodes to a bound Xml structure in WPF?

Thanks for your help

A: 

To the best of my knowledge, XmlDataProvider does not offer functionality to manipulate node structure, only to manipulate values. Therefore if you want to manipulate nodes you will need to write code to do so against the XmlDocument instance provided via the XmlDataProvider's Document property.

Drew Marsh
A: 

If anyone is interested, this is what i came up with:

The XAML:

<Window.Resources>
    <local:NodeAvailableConverter x:Key="MyConverter"/>
    <XmlDataProvider x:Key="xmlsource">
        <x:XData>
            <main xmlns="">
                <sub/>
            </main>
        </x:XData>
    </XmlDataProvider>

</Window.Resources>

<DockPanel DataContext="{Binding Source={StaticResource xmlsource}}">

    <CheckBox Content="CheckBox1" DockPanel.Dock="Top">
        <i:Interaction.Behaviors>
            <local:MyBehavior SourceProv="{StaticResource xmlsource}" XPath="main">
                <local:Node Name="sub">
                    <local:Attribute Name="yeah" Value="added1"/>
                </local:Node>
            </local:MyBehavior>
        </i:Interaction.Behaviors>
    </CheckBox>
    <CheckBox Content="CheckBox2" DockPanel.Dock="Top">
        <i:Interaction.Behaviors>
            <local:MyBehavior SourceProv="{StaticResource xmlsource}" XPath="main">
                <local:Node Name="sub">
                    <local:Attribute Name="yeah" Value="added2"/>
                </local:Node>
            </local:MyBehavior>
        </i:Interaction.Behaviors>
    </CheckBox>
    <Label Content="{Binding OuterXml}" />
</DockPanel>

The Behavior:

[ContentProperty("CustomNode")]
public class MyBehavior : Behavior<CheckBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        binding = new Binding();
        binding.XPath = XPath + "/" + CustomNode.Name;
        binding.Mode = BindingMode.OneWay;
        binding.Converter = NodeAvailableConverter.Instance;
        AssociatedObject.SetBinding(CheckBox.IsCheckedProperty, binding);
        AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
        SourceProv.Refresh();
}

    void AssociatedObject_Click(object sender, RoutedEventArgs e)
    {
        var parent = SourceProv.Document.SelectSingleNode(XPath);
        if(AssociatedObject.IsChecked==true)
        {
            var newelement = SourceProv.Document.CreateElement(CustomNode.Name);
            foreach (var at in CustomNode.Attributes)
                newelement.SetAttribute(at.Name, at.Value);
            parent.AppendChild(newelement);
        }
        else if (AssociatedObject.IsChecked == false)            
            parent.RemoveChild(parent.SelectSingleNode(CustomNode.Name));
        SourceProv.Refresh();
        AssociatedObject.SetBinding(CheckBox.IsCheckedProperty, binding);
    }

    public XmlDataProvider SourceProv { get; set; }
    public Node CustomNode { get; set; }
    public String XPath { get; set; }
    private Binding binding { get; set; }
}

public class NodeAvailableConverter : IValueConverter
{

    private static NodeAvailableConverter _instance;
    public static NodeAvailableConverter Instance
    {
        get
        {
            if (_instance == null)
                _instance = new NodeAvailableConverter();
            return _instance;
        }
    }
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value != null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

[ContentProperty("Attributes")]
public class Node
{
    public string Name { get; set; }
    private List<Attribute> _attributes;
    public List<Attribute> Attributes { get 
    {
        if (_attributes == null)
            _attributes = new List<Attribute>();
        return _attributes;
    } }
}
public class Attribute
{
    public string Name { get; set; }
    public string Value { get; set; }
}

Now this whole thing doesn't look nice at all, but gets the job done. It does reveal some strange behaviors though:

  1. The Binding seems to be unset every time the Click event gets triggered (thats why i set it every time again)

  2. XmlDataProvider's Refresh method has to be called once before the clickhandler executes, as when Refresh gets executed the first time, it seems like pulling the original XmlDocument back to the XmlDataProvider and dismisses the current XmlDocument.

very awkward... if anyone can clear anything up, it's much appreciated.

redoced