WPF MVVM: How to bind DevComponents NavigationPane? - wpf

I want to bind a DevComponents NavigationPane with PaneItems (main menu items) with ButtonDropDowns (menu items) to a property of type List<MainMenuItems> in the viewmodel but I dont know how to integrate the menus in the main menus.
This is how to build the menu statically:
<WpfRibbon:NavigationPane Width="200" LargeItemsCount="10">
<WpfRibbon:PaneItem Header="MainMenu1">
<StackPanel>
<WpfRibbon:ButtonDropDown Header="Menu1"/>
<WpfRibbon:ButtonDropDown Header="Menu2"/>
</StackPanel>
</WpfRibbon:PaneItem>
<WpfRibbon:PaneItem Header="MainMenu2">
<StackPanel>
<WpfRibbon:ButtonDropDown Header="Menu3"/>
<WpfRibbon:ButtonDropDown Header="Menu4"/>
</StackPanel>
</WpfRibbon:PaneItem>
</WpfRibbon:NavigationPane>
Here in the data bound version I get the PaneItems rendered, but then I am stuck, I don't see how to implement the menu items (ButtonDropDowns):
<WpfRibbon:NavigationPane Width="200" LargeItemsCount="10"
ItemsSource="{Binding MainMenuItems}">
<WpfRibbon:NavigationPane.ItemContainerStyle>
<Style TargetType="{x:Type WpfRibbon:PaneItem}">
<Setter Property="Header" Value="{Binding Title}" />
</Style>
</WpfRibbon:NavigationPane.ItemContainerStyle>
</WpfRibbon:NavigationPane>
The model looks like follows:
MainMenuItems = new List<KisMainMenuItem>
{
new KisMainMenuItem(title: "MainMenu1", image: #"img/books.png", menuItems: new List<KisMenuItem>
{
new KisMenuItem(innerTitle: "Menu1", image: #"img/books.png"),
new KisMenuItem(innerTitle: "Menu2", image: #"img/books.png")
}),
new KisMainMenuItem("MainMenu2", #"img/books.png", new List<KisMenuItem>
{
new KisMenuItem("Menu3", #"img/books.png"),
new KisMenuItem("Menu4", #"img/books.png")
})
};

The missing part was a DataTemplate with an ItemsControl.
<DataTemplate DataType="{x:Type local:MainMenuItem}">
<ItemsControl ItemsSource="{Binding MenuItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WpfRibbon:ButtonDropDown Header="{Binding InnerTitle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>

Related

Dynamic treeview expansion not working with HeirarchicalDataTemplate

I have a treeview control that picks item controls dynamically through a template selector. I have bound the property IsExpanded on my model to IsExpanded on TreeViewItem using a Setter. (I know this is hooked up, because if I set IsExpanded to true in the model constructor, the entire tree is expanded, as expected.)
Here's the problem. After the I load the tree, just the root node is visible (as expected), but if I set IsExpanded on a node to true, the tree is supposed to expand to the node that changed, but it does not. (I've put debug markers in to make sure the property is actually changing. )
Here is my xaml:
<Window.Resources>
<HierarchicalDataTemplate x:Key="RegularNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<StackPanel Orientation="Horizontal">
<Border Width="8" Height="15" >
<Label Content="*" Padding="0" HorizontalAlignment="Right" Visibility="{Binding ModifiedCueVisibility}" />
</Border>
<TextBlock Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="RootNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
<local:ManifestNodeTemplateSelector x:Key="manifestNodeTemplateSelector"/>
</Window.Resources>
<Grid>
<TreeView Name="TheManifestTreeView" Grid.Row="0" ItemsSource="{Binding ManifestRoot}"
ItemTemplateSelector="{StaticResource manifestNodeTemplateSelector}"
SelectedItemChanged="TreeView_SelectedItemChanged" >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
Here is The code for IsExpanded:
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
My problem is that I was expecting the tree to automatically expand to a leaf node if that node expanded. If I want the tree to expand to a node, I need to walk up the parentage and expand those.
It seems like ItemContainerStyle only applies for first level of TreeViewItem.Try to put the style inside TreeView.Resources instead
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.Resources>

WPF TreeView not displaying arrow

I'm having a weird problem that I can't figure out where it's coming from.
I'm binding a TreeView in WPF to an object structure. At the beginning, the TreeView is completely empty, then I create the root with empty children (from a user action) and then the children are added (also from a user action).
For some reason that I don't understand, when I add the children of the root (Parents in my example), the arrow is not added to the root element. The parents are there, because when I double-click the root, it displays the parents, but the arrow is not displayed.
At the same time, the parents have "Children" and for those there is no issue, the arrow is displayed and everything is fine.
Here's the (simplified) XAML I used to display the TreeView:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ChildTemplate"
DataType="dom:Child">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Child: " />
<TextBlock Text="{Binding Date}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ParentTemplate"
ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource ChildTemplate}"
DataType="dom:Parent">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Parent: " />
<TextBlock Text="{Binding Point, StringFormat='point: {0:N2}'}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="RootTemplate"
ItemsSource="{Binding Path=Parents}"
ItemTemplate="{StaticResource ParentTemplate}"
DataType="vm:RootViewModel">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Root: " />
<TextBlock Text="{Binding Name}" Margin="5 0" />
</StackPanel>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView ItemsSource="{Binding Roots}" ItemTemplate="{StaticResource RootTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource MetroTreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
In terms of ViewModel, there is nothing particular. The ViewModel has a collection of Root elements (called Roots, which is bound to the ItemSource). And this Roots class has a collection of Parent called Parents which have a collection of Child called Children. Parents implement INotifyPropertyChanged which notifies when I add a child, same thing for Root.
This should really be closed, but just in case someone else looks at this:
Joel Lucsy comment solved the issue.
In addition: read only observable collection on msdn.

WPF TreeView IsSelected Binding Throwing Null Reference Exception

I've got a tabcontrol that is bound to a tab collection in my view model. Right now there are 2 types of tabs and one of them has a treeview in it. When the tab is first created, the treeview selection works. When I switch tabs and come back to the tab with the treeview in it, the treeview items seem to become unbound. When I try to select one I get the following error:
System.Windows.Data Error: 8 : Cannot save value from target back to source.
BindingExpression:Path=IsSelected; DataItem='NavigationItem' (HashCode=50956576);
target element is 'TreeViewItem' (Name=''); target property is 'IsSelected' (type
'Boolean') NullReferenceException:'System.NullReferenceException:
Object reference not set to an instance of an object.
at System.ComponentModel.ReflectPropertyDescriptor.SetValue(
Object component, Object value)
at MS.Internal.Data.PropertyPathWorker.SetValue(Object item, Object value)
at MS.Internal.Data.ClrBindingWorker.UpdateValue(Object value)
at System.Windows.Data.BindingExpression.UpdateSource(Object value)'
I'm not sure why this is occurring. When debugging through the application my model still has all of the correct data, and the treeview even displays the information from the model meaning it has to have been bound correctly when the tab was initially changed. It's just when I try to select an item after having left the tab and come back. Here's the source for my tab control and the tab in question with a treeview in it:
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
...
</TabControl.ItemTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type tabs:ArchitectureTabViewModel}">
<views:ArchitectureTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type tabs:TestOrderTabViewModel}">
<views:TestOrderTab/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
And the tab in question:
<Grid DataContext="{Binding GraphNavigationModel}">
...
<TreeView ItemsSource="{Binding NavigationTree}" Background="#00ffffff" Margin="5" BorderThickness="0" MinWidth="200">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Text}">
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Grid>

How to show Master-Detail Informations in a collapsable ListView

I have a class that describes a certain task. This "task"-Class has a list of substeps.
What I wanted to do is to show these Informations in a ListView where the task-description should be used as the Groupheader and the substeps as the details
Also I wanted to be able to collapse and expand these groups.
Here is what I tried so far (simplified, but you'll get the idea hopefully):
public class Task : INotifyPropertyChanged
{
public string Description;
public ObservableCollection<String> substeps;
...
}
in the Viewmodel :
Task t = new Task();
t.Description = "Task1";
t.substeps.Add("substep 1");
t.substeps.Add("substep 2");
...
Tasks = new CollectionViewSource { Source = TaskList }; //TaskList is just a ObservableCollection<Task>
Tasks.GroupDescriptions.Add(new PropertyGroupDescription("Description"));
in xaml:
<s:SurfaceListBox Width="500" Height="1000" ItemsSource="{Binding TaskList.View}">
<s:SurfaceListBox.Resources>
<Style x:Key="ContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Description}" IsExpanded="True">
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</s:SurfaceListBox.Resources>
<s:SurfaceListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ContainerStyle}"/>
</s:SurfaceListBox.GroupStyle>
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding substeps}"/>
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
</s:SurfaceListBox>
The Result is a Listbox with my collapsed item. If I expand it, instead of each step I only see "(Listing)"
Do I have to build a class with the substeps and the description in it, and group by the description?
Try this:
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource={Binding substeps} />
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
Your problem is that the string binding to the ObservableCollection will just do a ToString() on that collection. You need iterate through the collection and display each item. By using the ItemsControl as I have done, you can also DataTemplate each subtask as you see fit.
ControlTemplate Binding
The Expander header binding will not work because it is inside a ControlTemplate which is inside the Style. The DataContext for the control template will not be your ViewModel but the control (i.e. SurfaceListBox) itself. Similar question is here.
There are two ways you can fix this.
1.Use DataTemplate
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Description}"
IsExpanded="True" >
<ItemsControl ItemsSource={Binding substeps} />
</Expander >
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
2.Use TemplatedParent binding
<Expander Header="{Binding Content.Description, , RelativeSource={RelativeSource TemplatedParent}}"
IsExpanded="True">
I personally recommend option 1.

WPF TabControl and DataTemplates

I've got a set of ViewModels that I'm binding to the ItemsSource property of a TabControl. Let's call those ViewModels AViewModel, BViewModel, and CViewModel. Each one of those needs to have a different ItemTemplate (for the header; because they each need to show a different icon) and a different ContentTemplate (because they have very different interaction models).
What I'd like is something like this:
Defined in Resource.xaml files somewhere:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
Defined separately:
<TabControl ItemTemplate="[ Some way to select "ItemTemplate" based on the type ]"
ContentTemplate="[ Some way to select "ContentTemplate" based on the type ]"/>
Now, I know that realistically, each time I define a DataTemplate with the same key the system is just going to complain. But, is there something I can do that's similar to this that will let me put a DataTemplate into a TabControl based on a name and a DataType?
The easiest way would be to use the automatic template system, by including the DataTemplates in the resources of a ContentControl. The scope of the templates are limited to the element they reside within!
<TabControl ItemsSource="{Binding TabViewModels}">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</TabControl.Resources>
</TabControl>
You can remove the x:Key :) This will automatically apply the template when the given type is encountered (probably one of the most powerful and underused features of WPF, imo.
This Dr. WPF article goes over DataTemplates pretty well. The section you'll want to pay attention to is "Defining a Default Template for a Given CLR Data Type".
http://www.drwpf.com/blog/Home/tabid/36/EntryID/24/Default.aspx
If this doesn't help your situation, you might be able to do something close to what you are looking for using a Style (ItemContainerStyle) and setting the content and header based on the type using a data trigger.
The sample below hinges on your ViewModel having a property called "Type" defined pretty much like this (easily put in a base ViewModel if you have one):
public Type Type
{
get { return this.GetType(); }
}
So as long as you have that, this should allow you to do anything you want. Note I have "A Header!" in a textblock here, but that could easily be anything (icon, etc).
I've got it in here two ways... one style applies templates (if you have a significant investment in these already) and the other just uses setters to move the content to the right places.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:WpfApplication1">
<Window.Resources>
<CompositeCollection x:Key="MyCollection">
<local:AViewModel Header="A Viewmodel" Content="A Content" />
<local:BViewModel Header="B ViewModel" Content="B Content" />
</CompositeCollection>
<DataTemplate x:Key="ATypeHeader" DataType="{x:Type local:AViewModel}">
<WrapPanel>
<TextBlock>A Header!</TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</DataTemplate>
<DataTemplate x:Key="ATypeContent" DataType="{x:Type local:AViewModel}">
<StackPanel>
<TextBlock>Begin "A" Content</TextBlock>
<TextBlock Text="{Binding Content}" />
</StackPanel>
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="TabItem">
<Style.Triggers>
<!-- Template Application Approach-->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:AViewModel}">
<Setter Property="HeaderTemplate" Value="{StaticResource ATypeHeader}" />
<Setter Property="ContentTemplate" Value="{StaticResource ATypeContent}" />
</DataTrigger>
<!-- Just Use Setters Approach -->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:BViewModel}">
<Setter Property="Header">
<Setter.Value>
<WrapPanel>
<TextBlock Text="B Header!"></TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="{Binding Content}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemContainerStyle="{StaticResource TabItemStyle}" ItemsSource="{StaticResource MyCollection}" />
</Grid>
HTH, Anderson
One way would be to use DataTemplateSelectors and have each one resolve the resource from a separate ResourceDictionary.
In this example I use DataTemplates in the resources section of my TabControl for each view model I want to display in the tab items.
In this case I map ViewModelType1 to View1 and ViewModelType2 to View2.
The view models will be set as DataContext object of the views automatically.
For displaying the tab item header, I use an ItemTemplate.
The view models I bind to are of different types, but derive from a common base class ChildViewModel that has a Title property. So I can set up a binding to pick up the title to display it in the tab item header.
In addition I display a "Close" Button in the tab item header. If you do not need that, just remove the button from the example code so you just have the header text.
The contents of the tab items are rendered with a simple ItemTemplate which displays the view in a content control with Content="{Binding}".
<UserControl ...>
<UserControl.DataContext>
<ContainerViewModel></ContainerViewModel>
</UserControl.DataContext>
<TabControl ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding SelectedViewModel}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type ViewModelType1}">
<View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelType2}">
<View2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Title}" />
<Button DockPanel.Dock="Right" Margin="5,0,0,0"
Visibility="{Binding RemoveButtonVisibility}"
Command="{Binding DataContext.CloseItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfContainingView}}}"
>
<Image Source="/Common/Images/ActiveClose.gif"></Image>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</UserControl>
The user control which contains the tab control has a container view model of type ContainerViewModel as DataContext. Here I have a collection of all the view models displayed in the tab control. I also have a property for the currently selected view model (tab item).
This is a shortened version of my container view model (I skipped the change notification part).
public class ContainerViewModel
{
/// <summary>
/// The child view models.
/// </summary>
public ObservableCollection<ChildViewModel> ViewModels {get; set;}
/// <summary>
/// The currently selected child view model.
/// </summary>
public ChildViewModel SelectedViewModel {get; set;}
}
Josh Smith uses exactly this technique (of driving a tab control with a view model collection) in his excellent article and sample project WPF Apps With The Model-View-ViewModel Design Pattern. In this approach, because each item in the VM collection has a corresponding DataTemplate linking the View to the VM Type (by omitting the x:Key as Anderson Imes correctly notes), each tab can have a completely different UI. See the full article and source code for details.
The key parts of the XAML are:
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<vw:CustomerView />
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
There is one downside - driving a WPF TabControl from an ItemsSource has performance issues if the UI in the tabs is big/complex and therefore slow to draw (e.g., datagrids with lots of data). For more on this issue, search SO for "WPF VirtualizingStackPanel for increased performance".

Resources