【问题标题】:How to bind specific, deserialized from XML class structure to Treeview如何将特定的、反序列化的 XML 类结构绑定到 Treeview
【发布时间】:2015-01-06 14:59:30
【问题描述】:

我想制作将数据从我的 xml 文件反序列化为类结构的应用程序。我通过“将 XML 粘贴为类”工具准备了类,但是所有内容都是在公共字段或表上完成的,当我尝试为 List 或 ObservableCollections 更改它时,序列化器停止正确加载 xml 文档。

接下来我想做的是可以从树视图中选择一些元素,编辑它,然后再次保存到 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>
...

这里是类的例子

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

【问题讨论】:

  • 您在该任务的哪一部分遇到问题?
  • 好吧。我想将这种类结构绑定到树视图——这就是我现在的目标。如果不更改类,我就无法做到这一点,这会给序列化程序带来麻烦。我现在正在尝试使用分层数据模板 -> codemag.com/Article/1401031
  • 为什么你认为你不能将树绑定到这些类?你找到了正确的方向,HierarchicalDataTemplate 是通往这里的路。
  • 但正如我所见,我需要一些包含其他元素的列表类。而且 - 一开始 - 我有问题如何处理“计划”课程。它包含另一个元素,但它们是单一的。在这里制作列表结构似乎毫无意义。我只是补充说我是初学者
  • XML 序列化 not 与 WPF 一起工作。序列化程序不喜欢必要的INotifyPropertyChanged 接口或ObservableCollection&lt;T&gt;s,因此您需要做一些手动工作来满足您的要求。我的意思是,您将无法将生成的类与 WPF 一起使用,也无法序列化在 WPF 中正常工作的类。因此,只需遍历反序列化生成的类并填充一些新的 WPF 友好类......然后你会有更多的运气。

标签: c# xml wpf treeview


【解决方案1】:

要在 WPF TreeView 中显示类的层次结构,在 XAML 中,您需要定义一个 HierarchicalDataTemplate 对应于层次结构中可能有子类的每种类型,并定义一个 DataTemplate 对应于每种类型的层次结构中没有子级的类。每个数据模板都应该定义一个框架元素(例如TextBlock,或者一个容器控件,例如带有任意数量的嵌入式框架元素作为子元素的DockPanel),它将在树中显示该类型类的数据,具有适当的绑定。

首先,使用xsd.exe 为您的 XML 自动生成类。要在树中简单地显示数据,您不需要实现 INotifyPropertyChanged 或为子级使用 ObservableCollection&lt;T&gt;

/// <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 中定义一个用户界面来显示这些类,为每个级别手动创建适当的数据模板:

<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 树 may be easier to work with

更新 - 编辑

重新阅读您的问题,我看到您的要求是允许用户加载在树中编辑保存 XML .这比加载更复杂。步骤如下:

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

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 类中为这些操作添加实际的 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 中,将上面定义的命令添加到 &lt;Window.CommandBindings&gt;

然后,在 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);
    }
}

【讨论】:

  • 哇,我永远也想不通。明天我会仔细研究,我肯定会有一些问题要问你,但现在谢谢你,我真的很感激。
  • 好的,我想我回到家并开始得到它,现在我正在做你的回答的第一个“部分”,并尝试使用我添加到我的第一篇文章的链接中的我的 xml 文件。你能告诉我你在“计划”类中的 GetXML 方法是什么样的吗?
  • @Tom - GetXML() 是一种将类序列化为 XML 并将其存储在字符串中的扩展方法。由于问题与序列化或反序列化无关,因此我没有包含它。
  • 这个方法比我的更“弹性”,感谢我完成了我的文档与类结构的匹配(一步一步),我不得不稍微改变它(结构),我花了虽然,但它并不像我想象的那么难。加载和保存工作正常 - 这里没有问题。明天我将添加编辑文档的功能 - 我必须阅读有关路由 UI 命令的内容,因为我不太了解它。接下来我会尝试给它一些更好的外观。我也想让节点可编辑,但我会把它留作将来的可选项。
  • 我将尝试使用DataContractSerializer 来实现它,如果我会失败(这很可能)提出一个新问题。感谢@dbc 的时间和耐心
猜你喜欢
  • 2014-01-20
  • 1970-01-01
  • 2018-01-09
  • 1970-01-01
  • 2011-07-06
  • 1970-01-01
  • 1970-01-01
  • 2020-12-07
  • 1970-01-01
相关资源
最近更新 更多