如何绑定特定的,从 XML class 结构反序列化到 Treeview

How to bind specific, deserialized from XML class structure to Treeview

我想制作将数据从我的 xml 文件反序列化为 class 结构的应用程序。我通过 'Paste XML as classes' 工具准备了 classes,但是所有内容都是在公共字段或表上创建的,当我尝试将其更改为 List 或 ObservableCollections 时,序列化程序停止正确加载 xml 文档。

接下来我想做的是从 treeview 中选择一些元素,编辑它,然后再次保存到 xml 文件。我不想直接在 .xml 上这样做。 这是我的 XML 示例:

    <plan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="schema.xsd">
<nagłówek>
<autorzy>
<nazwa>Autorzy:</nazwa>
<autor atr="one">
<numer>222</numer>
<imię>Rust</imię>
<nazwisko>Snow</nazwisko>
</autor>

<autor>
<numer>111</numer>
<imię>Ian</imię>
<nazwisko>Nower</nazwisko>
</autor>
</autorzy>
</nagłówek>
...

这里是 classes

的例子
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class plan
{

    private planNagłówek nagłówekField;

    private planGłówny głównyField;

    /// <remarks/>
    public planNagłówek nagłówek
    {
        get
        {
            return this.nagłówekField;
        }
        set
        {
            this.nagłówekField = value;
        }
    }

    /// <remarks/>
    public planGłówny główny
    {
        get
        {
            return this.głównyField;
        }
        set
        {
            this.głównyField = value;
        }
    }
}

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class planNagłówek 
{

    private planNagłówekAutorzy autorzyField;

    /// <remarks/>
    public planNagłówekAutorzy autorzy
    {
        get
        {
            return this.autorzyField;
        }
        set
        {
            this.autorzyField = value;
        }
    }
}

以及我如何加载 xml:

// Create an instance of the XmlSerializer specifying type and namespace.
            XmlSerializer serializer = new XmlSerializer(typeof(XML.plan));
            // A FileStream is needed to read the XML document.
            FileStream fs = new FileStream("...somepath.../Untitled4.xml", FileMode.Open);
            XmlReader reader = XmlReader.Create(fs);

            // Use the Deserialize method to restore the object's state.
            i = (XML.plan)serializer.Deserialize(reader);
            fs.Close();    

这是我的 xml 文件,也许这会对你有所帮助:) https://drive.google.com/file/d/0B0wPodV30rnJSVA1ckVxWldDRDA/view

要在 WPF TreeView 中显示 class 的层次结构,在 XAML 中,您需要定义一个 HierarchicalDataTemplate corresponding to each type of class in your hierarchy that might have children, and a DataTemplate 对应于每个类型的 class 在您的层次结构中不会有 children。每个数据模板都应该定义一个框架元素(例如 TextBlock,或者一个容器控件,例如 DockPanel 和任意数量的嵌入式框架元素,如 children)来显示数据树中那种类型的 class,具有适当的绑定。

首先,auto-generate class 为您的 XML 使用 xsd.exe。要简单地在树中显示数据,您不需要实现 INotifyPropertyChanged 或对 children:

使用 ObservableCollection<T>
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class plan
{

    private planNagłówek[] itemsField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("nagłówek", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public planNagłówek[] Items
    {
        get
        {
            return this.itemsField;
        }
        set
        {
            this.itemsField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class planNagłówek
{

    private planNagłówekAutorzy[] autorzyField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("autorzy", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public planNagłówekAutorzy[] autorzy
    {
        get
        {
            return this.autorzyField;
        }
        set
        {
            this.autorzyField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class planNagłówekAutorzy
{

    private string nazwaField;

    private planNagłówekAutorzyAutor[] autorField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string nazwa
    {
        get
        {
            return this.nazwaField;
        }
        set
        {
            this.nazwaField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("autor", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public planNagłówekAutorzyAutor[] autor
    {
        get
        {
            return this.autorField;
        }
        set
        {
            this.autorField = value;
        }
    }
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class planNagłówekAutorzyAutor
{

    private string numerField;

    private string imięField;

    private string nazwiskoField;

    private string atrField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string numer
    {
        get
        {
            return this.numerField;
        }
        set
        {
            this.numerField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string imię
    {
        get
        {
            return this.imięField;
        }
        set
        {
            this.imięField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string nazwisko
    {
        get
        {
            return this.nazwiskoField;
        }
        set
        {
            this.nazwiskoField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string atr
    {
        get
        {
            return this.atrField;
        }
        set
        {
            this.atrField = value;
        }
    }
}

接下来,在 XAML 中定义一个用户界面,在其中显示这些 classes,为每个级别手动创建适当的数据模板:

<Window x:Class="WpfTreeViewNew.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:w="clr-namespace:WpfTreeViewNew"
    Title="Window1" Height="300" Width="600">
    <Window.Resources>
        <HierarchicalDataTemplate  DataType="{x:Type w:plan}" ItemsSource="{Binding Path=Items}">
            <TextBlock Text="Plan">
            </TextBlock>
        </HierarchicalDataTemplate >
        <HierarchicalDataTemplate  DataType="{x:Type w:planNagłówek}" ItemsSource="{Binding Path=autorzy}">
            <TextBlock Text="Nagłówek">
            </TextBlock>
        </HierarchicalDataTemplate >
        <HierarchicalDataTemplate  DataType="{x:Type w:planNagłówekAutorzy}" ItemsSource="{Binding Path=autor}">
            <TextBlock Text="{Binding Path=nazwa}"/>
        </HierarchicalDataTemplate >
        <DataTemplate  DataType="{x:Type w:planNagłówekAutorzyAutor}">
            <Border BorderBrush="Gray" BorderThickness="1" MinWidth="300">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Margin="3" Text="{Binding Path=atr}"/>
                    <TextBlock Margin="3" Text="{Binding Path=numer}"/>
                    <TextBlock Margin="3" Text="{Binding Path=imię}"/>
                    <TextBlock Margin="3" Text="{Binding Path=nazwisko}"/>
                </StackPanel>
            </Border>
        </DataTemplate >
    </Window.Resources>
        <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>
</Window>

最后,以编程方式加载数据,例如在启动时:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        string xml = @"<plan xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:noNamespaceSchemaLocation=""schema.xsd"">
  <nagłówek>
    <autorzy>
      <nazwa>Autorzy:</nazwa>
      <autor atr=""one"">
        <numer>222</numer>
        <imię>Rust</imię>
        <nazwisko>Snow</nazwisko>
      </autor>

      <autor>
        <numer>111</numer>
        <imię>Ian</imię>
        <nazwisko>Nower</nazwisko>
      </autor>
    </autorzy>
  </nagłówek>
</plan>
";
        var plan = XmlSerializationHelper.LoadFromXML<plan>(xml);
        var xml2 = plan.GetXml();
        Debug.WriteLine(xml2); // For testing

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

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

这会生成如下所示的内容:

您会想要用更漂亮的东西替换每个模板。

老实说,在研究完所有这些之后,我现在相信 WinForms 树

更新 - 编辑

Re-reading 你的问题,我看到你的要求是允许用户 load, edit in tree, and 保存 XML。这比加载更复杂。以下是步骤:

首先,添加用于加载和保存的自定义路由 UI 命令 XML:

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

接下来,在您的 Window1 class 中为这些操作添加实际的 c# 逻辑:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
    }

    private void ExecutedLoadXML(object sender, ExecutedRoutedEventArgs e)
    {
        string xml = @"<plan xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:noNamespaceSchemaLocation=""schema.xsd"">
  <nagłówek>
    <autorzy>
      <nazwa>Autorzy:</nazwa>
      <autor atr=""one"">
        <numer>222</numer>
        <imię>Rust</imię>
        <nazwisko>Snow</nazwisko>
      </autor>

      <autor>
        <numer>111</numer>
        <imię>Ian</imię>
        <nazwisko>Nower</nazwisko>
      </autor>
    </autorzy>
  </nagłówek>
</plan>
";
        var plan = XmlSerializationHelper.LoadFromXML<plan>(xml);

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

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

    private void ExecutedSaveXML(object sender, ExecutedRoutedEventArgs e)
    {
        var planList = treeView1.ItemsSource as IList<plan>;
        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());
        }
    }
}

如您所见,我只是从硬编码字符串加载,并通过调试写入行进行保存。你会想用真正的逻辑替换它们。

接下来,在XAML中,将上面定义的命令添加到<Window.CommandBindings>

然后,在XAML中添加一个ToolBarTrayToolBar,带有加载和保存按钮XML,并将按钮绑定到您添加到CommandBindings 以上。

最后,在 XAML 中,对于每个包含数据字段的 DataTemplateHierarchicalDataTemplate,将该字段的 TextBlock 替换为适当的框架元素以进行编辑.根据需要添加任何标签作为额外的 TextBlocks,并将它们全部包装在一个容器中,例如 Grid.

这是有效的方法:

<Window x:Class="WpfTreeViewNew.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:w="clr-namespace:WpfTreeViewNew"
    Title="Window1" Height="300" Width="600">
    <Window.CommandBindings>
        <CommandBinding Command="w:CustomCommands.LoadXMLCommand" Executed="ExecutedLoadXML"/>
        <CommandBinding Command="w:CustomCommands.SaveXMLCommand" Executed="ExecutedSaveXML"/>
    </Window.CommandBindings>
    <Window.Resources>
        <HierarchicalDataTemplate  DataType="{x:Type w:plan}" ItemsSource="{Binding Path=Items}">
            <TextBlock Text="Plan">
            </TextBlock>
        </HierarchicalDataTemplate >
        <HierarchicalDataTemplate  DataType="{x:Type w:planNagłówek}" ItemsSource="{Binding Path=autorzy}">
            <TextBlock Text="Nagłówek">
            </TextBlock>
        </HierarchicalDataTemplate >
        <HierarchicalDataTemplate  DataType="{x:Type w:planNagłówekAutorzy}" ItemsSource="{Binding Path=autor}">
            <Grid Margin="3" MinWidth="300">
                <Grid.RowDefinitions>
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <TextBlock Text="nazwa:" Grid.Column="0" Grid.Row="0"/>
                <TextBox Text="{Binding Path=nazwa, Mode=TwoWay}" Grid.Column="1" Grid.Row="0"/>
            </Grid>
        </HierarchicalDataTemplate >
        <DataTemplate  DataType="{x:Type w:planNagłówekAutorzyAutor}">
            <Border BorderBrush="Gray" BorderThickness="1" MinWidth="300">
                <Grid Margin="3" >
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="atr:" Grid.Column="0" Grid.Row="0"/>
                    <TextBox Text="{Binding Path=atr, Mode=TwoWay}" Grid.Column="1" Grid.Row="0"/>
                    <TextBlock Text="numer:" Grid.Column="0" Grid.Row="1"/>
                    <TextBox Text="{Binding Path=numer, Mode=TwoWay}" Grid.Column="1" Grid.Row="1"/>
                    <TextBlock Text="imię:" Grid.Column="0" Grid.Row="2"/>
                    <TextBox Text="{Binding Path=imię, Mode=TwoWay}" Grid.Column="1" Grid.Row="2"/>
                    <TextBlock Text="nazwisko:" Grid.Column="0" Grid.Row="3"/>
                    <TextBox Text="{Binding Path=nazwisko, Mode=TwoWay}" Grid.Column="1" Grid.Row="3"/>
                </Grid>
            </Border>
        </DataTemplate >
    </Window.Resources>
    <DockPanel>
        <ToolBarTray DockPanel.Dock="Top">
            <ToolBar>
                <Button Command="w:CustomCommands.LoadXMLCommand" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
                <Button Command="w: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>

它产生的 UI 看起来像:

我不是 UI 设计师,所以你会想用这个来获得更漂亮的东西。

更新 2

GetXML()扩展方法:

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