Shahzad Shahzad - 3 months ago 8
C# Question

Binding XML file to WPF Treeview

I am trying to build a XML file editor, where I want to add XML file to treeview control, and based on it's nodes and attributes, i have to show editable textboxes or other controls. I serializing and deserializing this xml data to process in someother module.

I am trying to follow this Stackoverflow question but I cannot get it working, nothing binds with the control. I am fairly new to XAML and MVVM. so any help , suggestion will be really appreciated.

All I am getting is this

PS: Input is a xml file, so based on your suggestions i am open to bind xml directly to treeview or serialzed classes through MVVM[preffered].

XML File Structure:

<?xml version="1.0" encoding="utf-8"?>
<interlocking xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<signals>
<signal ref="SignalRef_1">
<aspectSpeedDependencies>
<aspectSpeedDependency aspect="REF" vApproach="VApproach" vPass="Vpas">
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
</aspectSpeedDependency>
<aspectSpeedDependency aspect="REF" vApproach="VApproach" vPass="Vpas">
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
</aspectSpeedDependency>
<aspectSpeedDependency aspect="REF" vApproach="VApproach" vPass="Vpas">
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
</aspectSpeedDependency>
</aspectSpeedDependencies>
</signal>
<signal ref="SignalRef_1">
<aspectSpeedDependencies>
<aspectSpeedDependency aspect="REF" vApproach="VApproach" vPass="Vpas">
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
</aspectSpeedDependency>
<aspectSpeedDependency aspect="REF" vApproach="VApproach" vPass="Vpas">
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
</aspectSpeedDependency>
<aspectSpeedDependency aspect="REF" vApproach="VApproach" vPass="Vpas">
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
<targetRef ref="TargetRef" />
</aspectSpeedDependency>
</aspectSpeedDependencies>
</signal>
</signals>
<routes>
<route id="1">
<start>
<signalRef ref="pro.Routes.Route.Start.SignalRef.Ref" />
</start>
<target>
<signalRef ref="Target.SingalRef" />
</target>
<elements>
<switchRef>
<switch ref="Ref" course="Left" />
</switchRef>
<levelcrossingRef>
<levelcrossing ref="Ref" beam="Beam" />
<levelcrossing ref="Ref" beam="Beam" />
<levelcrossing ref="Ref" beam="Beam" />
</levelcrossingRef>
<trainDetectorRef>
<trackCircuitBorder ref="Ref" />
<trackCircuitBorder ref="Ref" />
<trackCircuitBorder ref="Ref" />
</trainDetectorRef>
</elements>
<flankElements>Flank</flankElements>
<routePriority rank="1" />
</route>
</routes>
</interlocking>


Code Behind

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Xml;
using System.Xml.Serialization;
//using CoreElements.Core.Interlocking;
using System.Xml.Linq;
using System.Reflection;

namespace TreeviewTest.Stck
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}


private void ExecutedLoadXML(object sender, ExecutedRoutedEventArgs e)
{

string executableLocation = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string xslLocation = System.IO.Path.Combine(executableLocation, "Interlocking.xml");

XDocument xmlData = XDocument.Load(xslLocation, LoadOptions.None);
var Interlocking = XmlSerializationHelper.LoadFromXML<Interlocking>(xmlData.ToString());

var children = new List<Interlocking>();
children.Add(Interlocking);

treeView1.ItemsSource = null;
treeView1.Items.Clear();
treeView1.ItemsSource = children;
}

private void ExecutedSaveXML(object sender, ExecutedRoutedEventArgs e)
{
var planList = treeView1.ItemsSource as IList<Interlocking>;
if (planList != null && planList.Count > 0)
{
// Kludge to force pending edits to update
treeView1.Focus();
// Replace with actual save code!
Debug.WriteLine(planList[0].GetXml());
}
}
}

public static class CustomCommands
{
public static readonly RoutedUICommand LoadXMLCommand = new RoutedUICommand("Load XML", "LoadXML", typeof(Window1));

public static readonly RoutedUICommand SaveXMLCommand = new RoutedUICommand("Save XML", "SaveXML", typeof(Window1));
}

public static class XmlSerializationHelper
{
public static string GetXml<T>(T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
using (var textWriter = new StringWriter())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; // For cosmetic purposes.
settings.IndentChars = " "; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
if (omitStandardNamespaces)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
serializer.Serialize(xmlWriter, obj, ns);
}
else
{
serializer.Serialize(xmlWriter, obj);
}
}
return textWriter.ToString();
}
}

public static string GetXml<T>(this T obj, bool omitNamespace)
{
XmlSerializer serializer = new XmlSerializer(obj.GetType());
return GetXml(obj, serializer, omitNamespace);
}

public static string GetXml<T>(this T obj)
{
return GetXml(obj, false);
}

public static T LoadFromXML<T>(this string xmlString)
{
return xmlString.LoadFromXML<T>(new XmlSerializer(typeof(T)));
}

public static T LoadFromXML<T>(this string xmlString, XmlSerializer serial)
{
T returnValue = default(T);

using (StringReader reader = new StringReader(xmlString))
{
object result = serial.Deserialize(reader);
if (result is T)
{
returnValue = (T)result;
}
}
return returnValue;
}

public static T LoadFromFile<T>(string filename)
{
XmlSerializer serial = new XmlSerializer(typeof(T));
try
{
using (var fs = new FileStream(filename, FileMode.Open))
{
object result = serial.Deserialize(fs);
if (result is T)
{
return (T)result;
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
throw;
}
return default(T);
}
}
}


XAML:

<Window x:Class="Test_Thesis.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="clr-namespace:Test_Thesis"
mc:Ignorable="d"
Title="Window1" Height="300" Width="600">
<Window.CommandBindings>
<CommandBinding Command="o:CustomCommands.LoadXMLCommand" Executed="ExecutedLoadXML"/>
<CommandBinding Command="o:CustomCommands.SaveXMLCommand" Executed="ExecutedSaveXML"/>
</Window.CommandBindings>
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type o:Interlocking}" ItemsSource="{Binding Path=Signals}">
<TextBlock Text="Interlocking">
</TextBlock>
</HierarchicalDataTemplate >
<HierarchicalDataTemplate DataType="{x:Type o:Signals}" ItemsSource="{Binding Path=Signal}">
<TextBlock Text="Signal">
</TextBlock>
</HierarchicalDataTemplate >

<!--<HierarchicalDataTemplate DataType="{x:Type o:Signal}" ItemsSource="{Binding Path=AspectSpeedDependencies}">
<TextBlock Text="Signals">
</TextBlock>
</HierarchicalDataTemplate >
<HierarchicalDataTemplate DataType="{x:Type o:AspectSpeedDependencies}" ItemsSource="{Binding Path=AspectSpeedDependency}">
<TextBlock Text="AspectSpeedDependencies">
</TextBlock>
</HierarchicalDataTemplate >-->

<HierarchicalDataTemplate DataType="{x:Type o:Signal}" ItemsSource="{Binding Path=AspectSpeedDependencies}">
<Grid Margin="3" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Ref" Grid.Column="0" Grid.Row="0"/>
<TextBox Text="{Binding Path=Ref, Mode=TwoWay}" Grid.Column="1" Grid.Row="0"/>
</Grid>
</HierarchicalDataTemplate >
<HierarchicalDataTemplate DataType="{x:Type o:AspectSpeedDependencies}" ItemsSource="{Binding Path=AspectSpeedDependency}">
<TextBlock Text="AspectSpeedDependencies">
</TextBlock>
</HierarchicalDataTemplate >

<HierarchicalDataTemplate DataType="{x:Type o:AspectSpeedDependency}" ItemsSource="{Binding Path=TargetRef}">

<Border BorderBrush="Gray" BorderThickness="1" MinWidth="300">
<StackPanel Height="auto" Width="auto">
<TextBlock Text="AspectSpeedDependency:" />

<StackPanel Orientation="Horizontal">

<TextBlock Text="Aspect:" Margin="1"/>
<TextBox Text="{Binding Path=Aspect, Mode=TwoWay}" Margin="1"/>
<TextBlock Text="VApproach:" Margin="1"/>
<TextBox Text="{Binding Path=VApproach, Mode=TwoWay}" Margin="1"/>
<TextBlock Text="VPass:" Margin="1"/>
<TextBox Text="{Binding Path=VPass, Mode=TwoWay}" Margin="1"/>
</StackPanel>



<!--<TextBlock Text="Aspect:" Grid.Column="0" Grid.Row="2"/>
<TextBox Text="{Binding Path=Aspect, Mode=TwoWay}" Grid.Column="1" Grid.Row="2"/>
<TextBlock Text="VApproach:" Grid.Column="0" Grid.Row="3"/>
<TextBox Text="{Binding Path=VApproach, Mode=TwoWay}" Grid.Column="1" Grid.Row="3"/>
<TextBlock Text="VPass:" Grid.Column="0" Grid.Row="4"/>
<TextBox Text="{Binding Path=VPass, Mode=TwoWay}" Grid.Column="1" Grid.Row="4"/>-->



</StackPanel>

</Border>
</HierarchicalDataTemplate >

<DataTemplate DataType="{x:Type o:TargetRef}">
<Border BorderBrush="Brown" BorderThickness="1" MinWidth="300">
<Grid Margin="3" >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Ref:" Grid.Column="0" Grid.Row="0"/>
<TextBox Text="{Binding Path=Ref, Mode=TwoWay}" Grid.Column="1" Grid.Row="0"/>



</Grid>
</Border>
</DataTemplate >








</Window.Resources>
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="o:CustomCommands.LoadXMLCommand" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
<Button Command="o:CustomCommands.SaveXMLCommand" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
</ToolBar>
</ToolBarTray>
<Grid DockPanel.Dock="Bottom">
<TreeView Margin="3" Name="treeView1">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</DockPanel>
</Window>


XML C# code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;


namespace TreeviewTest.Stck
{


[XmlRoot(ElementName = "targetRef")]
public class TargetRef
{
[XmlAttribute(AttributeName = "ref")]
public string Ref { get; set; }
}

[XmlRoot(ElementName = "aspectSpeedDependency")]
public class AspectSpeedDependency
{
[XmlElement(ElementName = "targetRef")]
public List<TargetRef> TargetRef { get; set; }
[XmlAttribute(AttributeName = "aspect")]
public string Aspect { get; set; }
[XmlAttribute(AttributeName = "vApproach")]
public string VApproach { get; set; }
[XmlAttribute(AttributeName = "vPass")]
public string VPass { get; set; }
}

[XmlRoot(ElementName = "aspectSpeedDependencies")]
public class AspectSpeedDependencies
{
[XmlElement(ElementName = "aspectSpeedDependency")]
public List<AspectSpeedDependency> AspectSpeedDependency { get; set; }
}

[XmlRoot(ElementName = "signal")]
public class Signal
{
[XmlElement(ElementName = "aspectSpeedDependencies")]
public List<AspectSpeedDependencies> AspectSpeedDependencies { get; set; }
[XmlAttribute(AttributeName = "ref")]
public string Ref { get; set; }
}

[XmlRoot(ElementName = "signals")]
public class Signals
{
[XmlElement(ElementName = "signal")]
public List<Signal> Signal { get; set; }
}

[XmlRoot(ElementName = "signalRef")]
public class SignalRef
{
[XmlAttribute(AttributeName = "ref")]
public string Ref { get; set; }
}

[XmlRoot(ElementName = "start")]
public class Start
{
[XmlElement(ElementName = "signalRef")]
public SignalRef SignalRef { get; set; }
}

[XmlRoot(ElementName = "target")]
public class Target
{
[XmlElement(ElementName = "signalRef")]
public SignalRef SignalRef { get; set; }
}

[XmlRoot(ElementName = "switch")]
public class Switch
{
[XmlAttribute(AttributeName = "ref")]
public string Ref { get; set; }
[XmlAttribute(AttributeName = "course")]
public string Course { get; set; }
}

[XmlRoot(ElementName = "switchRef")]
public class SwitchRef
{
[XmlElement(ElementName = "switch")]
public Switch Switch { get; set; }
}

[XmlRoot(ElementName = "levelcrossing")]
public class Levelcrossing
{
[XmlAttribute(AttributeName = "ref")]
public string Ref { get; set; }
[XmlAttribute(AttributeName = "beam")]
public string Beam { get; set; }
}

[XmlRoot(ElementName = "levelcrossingRef")]
public class LevelcrossingRef
{
[XmlElement(ElementName = "levelcrossing")]
public List<Levelcrossing> Levelcrossing { get; set; }
}

[XmlRoot(ElementName = "trackCircuitBorder")]
public class TrackCircuitBorder
{
[XmlAttribute(AttributeName = "ref")]
public string Ref { get; set; }
}

[XmlRoot(ElementName = "trainDetectorRef")]
public class TrainDetectorRef
{
[XmlElement(ElementName = "trackCircuitBorder")]
public List<TrackCircuitBorder> TrackCircuitBorder { get; set; }
}

[XmlRoot(ElementName = "elements")]
public class Elements
{
[XmlElement(ElementName = "switchRef")]
public SwitchRef SwitchRef { get; set; }
[XmlElement(ElementName = "levelcrossingRef")]
public LevelcrossingRef LevelcrossingRef { get; set; }
[XmlElement(ElementName = "trainDetectorRef")]
public TrainDetectorRef TrainDetectorRef { get; set; }
}

[XmlRoot(ElementName = "routePriority")]
public class RoutePriority
{
[XmlAttribute(AttributeName = "rank")]
public string Rank { get; set; }
}

[XmlRoot(ElementName = "route")]
public class Route
{
[XmlElement(ElementName = "start")]
public Start Start { get; set; }
[XmlElement(ElementName = "target")]
public Target Target { get; set; }
[XmlElement(ElementName = "elements")]
public Elements Elements { get; set; }
[XmlElement(ElementName = "flankElements")]
public string FlankElements { get; set; }
[XmlElement(ElementName = "routePriority")]
public RoutePriority RoutePriority { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}

[XmlRoot(ElementName = "routes")]
public class Routes
{
[XmlElement(ElementName = "route")]
public Route Route { get; set; }
}

[XmlRoot(ElementName = "interlocking")]
public class Interlocking
{
[XmlElement(ElementName = "signals")]
public List<Signals> Signals { get; set; }
[XmlElement(ElementName = "routes")]
public List<Routes> Routes { get; set; }
[XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsi { get; set; }
[XmlAttribute(AttributeName = "xsd", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsd { get; set; }
}
}


*********UPDATE***********
update

I am able to populate Signals, but I am not getting how would I will populate the second child , Routes now

*************Update***************

As per @J.H answer using CompositeCollection solved the issue. Save XML Command seems to be throwing exception but, XML is populated in Treeview now.

Answer

In class Interlocking, you have:

public Signals Signals { get; set; }

But it should be:

public List<Signals> Signals { get; set; }

Answer to your edit:

You can only have one item source so I suggest you combine the Signals and Routes into one collection. Change your Interlocking class to this:

[XmlRoot(ElementName = "interlocking")]
public class Interlocking
{
    [XmlElement(ElementName = "signals")]
    public List<Signals> Signals { get; set; }
    [XmlElement(ElementName = "routes")]
    public List<Routes> Routes { get; set; }
    [XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
    public string Xsi { get; set; }
    [XmlAttribute(AttributeName = "xsd", Namespace = "http://www.w3.org/2000/xmlns/")]
    public string Xsd { get; set; }

    public IList Children
    {
        get
        {
            return new CompositeCollection()
            {
                new CollectionContainer() { Collection = Signals },
                new CollectionContainer() { Collection = Routes }
            };
        }
    }
}

Notice the new property Children? It combines both Signals and Routes into one collection. You will then need to modify the XAML to use Children for it's ItemSource:

<HierarchicalDataTemplate DataType="{x:Type o:Interlocking}" ItemsSource="{Binding Path=Children}">
    <TextBlock Text="Interlocking">
    </TextBlock>
</HierarchicalDataTemplate >

And, of course, add the HierarchicalDataTemplate for the Routes type:

<HierarchicalDataTemplate  DataType="{x:Type o:Routes}" ItemsSource="{Binding Path=Route}">
    <TextBlock Text="Route">
    </TextBlock>
</HierarchicalDataTemplate >

FYI - this solution came from:

WPF Treeview Databinding Hierarchal Data with mixed types

Comments