Привязка XML-данных к WPF TreeView Control
Я потратил много времени, пытаясь выяснить, как связать данные в моем XML-файле с элементом управления TreeView, но я не знаю, с чего начать. Я даже пытался выполнить двустороннее связывание данных Xml с WPF TreeView и пример кода Джоша Смита для проекта кода, но все еще не могу понять, с чего начать!!!
У меня есть XML в файле "C:\SPDependencies.xml" (я могу изменить формат, если требуется)!!!:
<node type="SPDependencies" Name="SPDependencies">
<node type="StoredProc" Name="SP1">
<node type="OperationType" Name="Type1">
<node type="TableName" Name="Table1"/>
<node type="TableName" Name="Table2"/>
</node>
<node type="OperationType" Name="Type2">
<node type="TableName" Name="Table1"/>
<node type="TableName" Name="Table2"/>
</node>
.....
</node>
<node type="StoredProc" Name="SP2">
<node type="OperationType" Name="Type1">
...
...
</node>
</node>
Мне нужно отобразить это в элементе управления Treeview в следующем формате:
<SP1>
<Type1>
<Table1>
<Table2>
<Table3>
<Type2>
<Table1>
<Table2>
<Table3>
<SP2>
<Type1>
........
Спасибо, Абхи.
3 ответа
Учитывая следующий XML-файл:
<node type="SPDependencies" Name="SPDependencies">
<node type="StoredProc" Name="SP1">
<node type="OperationType" Name="Type1">
<node type="TableName" Name="Table1"/>
</node>
<node type="OperationType" Name="Type2">
<node type="TableName" Name="Table1"/>
</node>
</node>
<node type="StoredProc" Name="SP2">
<node type="OperationType" Name="Type1">
</node>
</node>
</node>
Посмотреть:
<Window x:Class="Tree.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Tree"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<HierarchicalDataTemplate x:Key="template">
<TextBlock Text="{Binding XPath=@Name}" />
<HierarchicalDataTemplate.ItemsSource>
<Binding XPath="node" />
</HierarchicalDataTemplate.ItemsSource>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid DataContext="{Binding Path=XmlData}">
<TreeView ItemsSource="{Binding}" ItemTemplate="{StaticResource template}">
</TreeView>
</Grid>
</Window>
Посмотреть модель:
public class ViewModel
{
public XmlDataProvider XmlData { get; set; }
public ViewModel()
{
XmlData = new XmlDataProvider();
XmlData.Source = new Uri(@"C:\input.xml");
XmlData.XPath = "node";
}
}
Выход:
Если вы хотите показать только узлы ниже корня, просто измените XPath на:
XmlData.XPath = "/node/node";
Вот дерево:
<Window.Resources>
<HierarchicalDataTemplate DataType="node"
ItemsSource="{Binding XPath=node}">
<TextBox Width="Auto"
Text="{Binding XPath=@Name, UpdateSourceTrigger=PropertyChanged}" />
</HierarchicalDataTemplate>
<XmlDataProvider
x:Key="xmlDataProvider"
XPath="node" Source="C:\Data.XML">
</XmlDataProvider>
</Window.Resources>
<Grid>
<StackPanel>
<Button Click="Button_Click">Save</Button>
<TreeView
Width="Auto"
Height="Auto"
Name="treeview"
ItemsSource="{Binding Source={StaticResource xmlDataProvider}, XPath=.}"/>
</StackPanel>
</Grid>
Я добавил простую кнопку, чтобы сохранить изменения. Итак, для вашего метода Button_Click в коде:
XmlDataProvider dataProvider = this.FindResource("xmlDataProvider") as XmlDataProvider;
dataProvider.Document.Save(dataProvider.Source.LocalPath);
Смотрите здесь статью о привязке данных и WPF.
Я понял, как это сделать, не наступая на TreeView.DataContext
, Связывание легко. Codebehind почти так же легко, но с одной маленькой ошибкой.
Вы ничего не получите, если вы связываете или назначаете XmlDataProvider
в ItemsSource
, Это не IEnumerable
(хотя это INotifyPropertyChanged
) и нет неявного преобразования. Что вы хотите XmlDataProvider.Data
, который объявлен как Object
но я вижу тип во время выполнения XmlDataCollection
(который наследует от ReadOnlyObservableCollection<XmlNode>
).
MVVM
переплет Data
это просто. Я не знаю, если это чистый MVVM поставить XmlDataProvider
по вашему мнению, модель, возможно, нет.
ViewModel:
public XmlDataProvider ViewModelXMLDataProp { ... }
XAML
<TreeView
ItemsSource="{Binding ViewModelXMLDataProp.Data}"
...
/>
Готово - то есть, если вам не нужно использовать XPath
собственность Binding
, Если вы делаете, вы должны использовать DataContext
ляп. Вы не можете установить оба Path
а также XPath
на той же привязке.
Свойство XPath XmlDataProvider
делает то же самое. Если вы можете работать с этим, у вас все хорошо.
Вы бы подумали Binding.Source
будет работать, потому что это работает, когда ваш XmlDataProvider
статический ресурс когда Binding.Source
это DataSourceProvider
а также Path
не указано, Path
по умолчанию Data
:
<TreeView
ItemsSource="{Binding Source={StaticResource MyXmlDataProviderResource}}"
...
/>
... но это работает, потому что вы даете ему статический ресурс. Следующее фактически связывается со строкой "ViewModelXMLDataProp"
вместо того, чтобы смотреть на DataContext
для собственности с этим именем. Это не хорошо.
<TreeView
ItemsSource="{Binding Source=ViewModelXMLDataProp}"
...
/>
Может быть, вы могли бы написать MarkupExtension
это сделало бы эту работу, но в этом нет необходимости.
Codebehind
Вы должны изучать и использовать MVVM, но это происходит по многим причинам, и вы не пришли сюда для проповеди.
Codebehind немного сложнее. TreeView.ItemsSource
не требует ничего, кроме того, что объект, который вы даете ему, должен реализовать System.Collections.IEnumerable
так что снимали provider.Data
в System.Collections.IEnumerable
и не беспокойтесь о том, что является точным типом времени выполнения.
Теперь вот что надо: XmlDataProvider.Data
заполняется асинхронно.
protected void LoadXML(String path)
{
var provider =
new XmlDataProvider()
{
Source = new Uri(path, UriKind.Absolute),
XPath = "./*"
};
// FAIL: provider.Data is still null
treeView.ItemsSource = (IEnumerable)provider.Data;
}
Я обнаружил, что это проблема, даже когда я создаю XmlDocument
, вызов XmlDocument.Load()
и назначьте документ XmlDataProvider.Document
, Binding
все еще будет торчать, когда Data
свойство наконец устанавливается, и оно будет обновляться ItemsSource
затем. Но назначение ItemsSource
в вашем коде файла нет такой вещи.
Вопреки вездесущему народному убеждению в Stack Overflow, в следующем коде не происходит связывания, и ни в каком смысле ничего не было связано:
// NOT A BINDING
treeView.ItemsSource = someRandomCollectionOfStuff;
Если никто не создает экземпляр System.Windows.Data.Binding
или же x:Bind
Это не является обязательным. Это различие имеет значение: "Используйте текущую стоимость x
"не то же самое понятие, что и" неопределенное обновление с будущими значениями y.x
каждый раз y
повышения PropertyChanged
".
Вы можете программно создать Binding
или даже справиться PropertyChanged
, но они пошли дальше и дали вам гораздо более простой вариант. Просто справиться с XmlDataProvider.DataChanged
событие.
protected void LoadXML(String path)
{
var provider =
new XmlDataProvider()
{
Source = new Uri(path, UriKind.Absolute),
XPath = "./*"
};
provider.DataChanged += (s,e)
=> treeView.ItemsSource = (IEnumerable)provider.Data;
}
И это делает это. Вы даже можете держать этого провайдера рядом, загружать в него новый XML и иметь DataChanged
событие держать дерево в курсе. Похоже, трата усилий, хотя.