Reputation: 41
I'm new to WPF. I'm moving a a Win Forms application over to WPF, which requires a lot of learning on my part.
In WinForms, I would create a subclass of common controls (MyLabel : Label, MyDataGrid : DataGrid, etc) This would allow me to set all my required attributes and styles, and these would be consistent for any control I then used. In addition, I could implement functionality on these controls that would be available for all my controls.
I'm failing miserably at being able to do this with a TreeView/TreeViewItem in WPF. I created a working example with a normal TreeView, and then simply substituted MyTreeView in the MainPage. I get: System.InvalidOperationException: 'Items collection must be empty before using ItemsSource.'
Hopefully you can download a project from this link: https://app.box.com/s/9ofyuwfgk2cobqhokezgkknvtnmzmeq7
Part of the design change is to implement the menu as a treeview on the left of the screen. This will have approximately 60 options.
I've spent a bit of time, and have a populated TreeView. However, I believe it is possible to use a menu here, and style it as a tree view. Is this correct? If so, can anyone point me to an example, as I haven't been able to find one on Google. Google seems to think I want a context menu on a TreeView.
Edited to add code:
TreeView xaml (TreeViewItem is basically identical)
<TreeView x:Class="WpfApp1.Controls.MyTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
</Grid>
</TreeView>
TreeView cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1.Controls
{
/// <summary>
/// Interaction logic for MyTreeView.xaml
/// </summary>
public partial class MyTreeView : TreeView
{
public MyTreeView()
{
InitializeComponent();
}
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTreeView();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyTreeView;
}
}
MainWindow
<Window x:Class="WpfApp1.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:local="clr-namespace:WpfApp1"
xmlns:Controls="clr-namespace:WpfApp1.Controls"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<XmlDataProvider x:Key="treeViewStructure" Source="Data/treeViewStructure.xml" XPath="./*/Category"/>
<HierarchicalDataTemplate DataType="Category" ItemsSource="{Binding XPath=*}">
<TextBlock Text="{Binding XPath=@Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="SubCategory" ItemsSource="{Binding XPath=*}">
<TextBlock Text="{Binding XPath=@Name}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<Controls:MyTreeView x:Name="mainTreeViewMenu"
Grid.Column="0"
BorderThickness="0"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource treeViewStructure}}" DataContext="{DynamicResource treeViewStructure}"/>
</Grid>
</Window>
Changing Controls:MyTreeView to simply TreeView will enable the app to run normally
ETA2: XML for Bradley:
<?xml version="1.0" encoding="utf-8" ?>
<Categories>
<Category Name="1">
<SubCategory Name="A"/>
<SubCategory Name="B"/>
</Category>
<Category Name="2">
<SubCategory Name="C"/>
<SubCategory Name="D"/>
<SubCategory Name="E"/>
<SubCategory Name="F"/>
<SubCategory Name="G"/>
<SubCategory Name="H"/>
</Category>
<Category Name="3">
<SubCategory Name="I"/>
<SubCategory Name="J"/>
</Category>
<Category Name="4">
<SubCategory Name="K"/>
<SubCategory Name="L"/>
</Category>
</Categories>
Upvotes: 0
Views: 673
Reputation: 16148
A few mistakes in your code, but you're close. The thing to keep in mind is that things like DataType
are used when you've serialized your XML data into a specific type of class. You don't get the benefits of this when you use XmlDataProvider, the data all gets serialized into generic XML data classes so you have to do things a bit differently to most of the example TreeView code you'll find on the net.
In your case you need to specifically bind all the root elements to a HierarchicalDataTemplate
resource that you declare with a key, and then have that bind to the list of children via XPath again. This should do this trick:
<Window.Resources>
<XmlDataProvider x:Key="treeViewStructure" Source="Data/treeViewStructure.xml" />
<HierarchicalDataTemplate x:Key="SubCategory">
<TextBlock Text="{Binding XPath=@Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="Category" ItemsSource="{Binding XPath=SubCategory}" ItemTemplate="{StaticResource SubCategory}">
<TextBlock Text="{Binding XPath=@Name}" />
</HierarchicalDataTemplate>
<!-- force all treeviewitems to be initially expanded -->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
</Style>
</Window.Resources>
<TreeView
ItemsSource="{Binding Source={StaticResource treeViewStructure}, XPath=/Categories/Category}"
ItemTemplate="{StaticResource Category}" />
Result:
Also if you don't explicitly need different templates for each node then you can just use the one, WPF will figure out that your children don't have children themselves:
<HierarchicalDataTemplate x:Key="Category" ItemsSource="{Binding XPath=SubCategory}" >
<TextBlock Text="{Binding XPath=@Name}" />
</HierarchicalDataTemplate>
Upvotes: 0