要在 WPF TreeView 中显示类的层次结构,在 XAML 中,您需要定义一个 HierarchicalDataTemplate 对应于层次结构中可能有子类的每种类型,并定义一个 DataTemplate 对应于每种类型的层次结构中没有子级的类。每个数据模板都应该定义一个框架元素(例如TextBlock,或者一个容器控件,例如带有任意数量的嵌入式框架元素作为子元素的DockPanel),它将在树中显示该类型类的数据,具有适当的绑定。
首先,使用xsd.exe 为您的 XML 自动生成类。要在树中简单地显示数据,您不需要实现 INotifyPropertyChanged 或为子级使用 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 中定义一个用户界面来显示这些类,为每个级别手动创建适当的数据模板:
<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 中,将上面定义的命令添加到 <Window.CommandBindings>
然后,在 XAML 中添加带有按钮的 ToolBarTray 和 ToolBar 以加载和保存 XML,并将按钮绑定到您在上面添加到 CommandBindings 的命令。
最后,在 XAML 中,对于每个包含数据字段的 DataTemplate 或 HierarchicalDataTemplate,将该字段的 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);
}
}