I am trying to accomplish this scenario with MVVM.
I want to have a DockLayoutManager with some dynamic documents and each document will show different view.
So let's say that I have the the DockLayoutManager with its ViewModel and I have 3 other views (usercontrols) with their own viewmodels
My code for the MainWIndow.xaml:
<UserControl x:Class="BrowserTabManager"
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:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:local="clr-namespace:WPFiRecsTest1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="600"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.DataContext>
<local:BrowserTabManagerViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ResultsViewModel}">
<local:ResultsView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:WorksheetViewModel}">
<local:WorksheetView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CrystalReportViewModel}">
<local:CrystalReportView />
</DataTemplate>
</UserControl.Resources>
<dxd:DockLayoutManager FloatingMode="Desktop">
<dxd:LayoutGroup>
<dxd:LayoutGroup x:Name="panelHost" />
<dxd:DocumentGroup x:Name="documentHost" ItemsSource="{Binding TabPages}" />
</dxd:LayoutGroup>
</dxd:DockLayoutManager>
</UserControl>
The good thing is that is working. Each document has its own view. But I cannot figure how and where I should bind the header caption:
The code for BrowserTabManagerViewModel:
Imports System.Collections.ObjectModel
Imports DevExpress.Mvvm
Public Class BrowserTabManagerViewModel
Inherits ViewModelBase
Private mTabPages As New ObservableCollection(Of ViewModelBase)()
Public Property TabPages() As ObservableCollection(Of ViewModelBase)
Get
Return mTabPages
End Get
Set(value As ObservableCollection(Of ViewModelBase))
RaisePropertyChanged("TabPages")
End Set
End Property
Private miSelectedTabIndex As Integer
Public Property SelectedTabIndex() As Integer
Get
Return miSelectedTabIndex
End Get
Set(value As Integer)
If value <> miSelectedTabIndex Then
miSelectedTabIndex = value
RaisePropertyChanged("SelectedTabIndex")
End If
End Set
End Property
Public Sub New()
mTabPages.Add(New ResultsViewModel())
mTabPages.Add(New WorksheetViewModel())
mTabPages.Add(New CrystalReportViewModel())
End Sub
So I found a way to do it, (maybe not the best one)
So the xaml is:
<UserControl x:Class="BrowserTabManager"
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:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:local="clr-namespace:WPFiRecsTest1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="600"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.DataContext>
<local:BrowserTabManagerViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<Style TargetType="{x:Type dxd:DocumentPanel}">
<Setter Property="Caption" Value="{Binding DisplayName}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding Content}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<dxd:DockLayoutManager x:Name="dockLayoutManager"
ClosedPanelsBarVisibility="Never"
ClosingBehavior="ImmediatelyRemove"
FloatingMode="Desktop"
ItemsSource="{Binding TabPages}">
<dxd:LayoutGroup x:Name="panelHost">
<dxd:DocumentGroup x:Name="documentHost" />
</dxd:LayoutGroup>
</dxd:DockLayoutManager>
</UserControl>
I have a viewmodel for the documents (BrowserTabViewModel) which has a property for the Caption (displayName) and one for the Content of the Document(TabPage is the Model):
Public Property Content() As UserControl
Get
Return TabPage.Content
End Get
Set(ByVal value As UserControl)
TabPage.Content = value
RaisePropertyChanged("Content")
End Set
End Property
Now the main ViewModel (BrowserTabManagerViewModel) is like this:
Imports System.Collections.ObjectModel
Imports DevExpress.Mvvm
Public Class BrowserTabManagerViewModel
Inherits ViewModelBase
Private mTabPages As New ObservableCollection(Of BrowserTabViewModel)()
Public Property TabPages() As ObservableCollection(Of BrowserTabViewModel)
Get
Return mTabPages
End Get
Set(value As ObservableCollection(Of BrowserTabViewModel))
RaisePropertyChanged("TabPages")
End Set
End Property
Public Sub New()
mTabPages.Add(New BrowserTabViewModel() With {.Type = "RES", .DisplayName = "Result1", .Content = New ResultsView})
mTabPages.Add(New BrowserTabViewModel() With {.Type = "WOR", .DisplayName = "worksheet", .Content = New WorksheetView})
mTabPages.Add(New BrowserTabViewModel() With {.Type = "CRR", .DisplayName = "Crystal", .Content = New CrystalReportView})
mTabPages.Add(New BrowserTabViewModel() With {.Type = "DXR", .DisplayName = "DX REport", .Content = New DXReportView})
mTabPages.Add(New BrowserTabViewModel() With {.Type = "DAR", .DisplayName = "Data Report", .Content = New DataReportView})
mTabPages.Add(New BrowserTabViewModel() With {.Type = "ADD", .DisplayName = "+", .Content = New BrowserAddMenuView})
End Sub
End Class
If anyone has any suggestions for improvement please let me know.
But I cannot figure how and where I should bind the header caption
You should declare that binding via the DocumentPanel's default style:
<UserControl.Resources>
<Style TargetType="dxd:DocumentPanel">
<Setter Property="Caption" Value="{Binding ViewModelPropertyForCaption}" />
</Style>
...
<DataTemplate DataType="{x:Type local:ResultsViewModel}">
...
</UserControl.Resources>
The best solution for the situation, described in the original question, is creating the base type for all of these ViewModels and separating the Header property into this class:
public class BaseDocumentViewModel {
public virtual string ViewModelPropertyForCaption { get; set; }
}
...
public class ResultsViewModel : BaseDocumentViewModel {
//...
}
Related help-article: MVVM Support - Building Dock UI.
A complete example is available online at: How to Build a dock UI using the MVVM pattern.
Related
I have a UserControl that hosts a TreeView that has a custom collection which inherits from ObservableCollection of a custom class. I am trying to bind to a property using a dependency property from a ViewModel, whilst I have proven the process works for another property it does not work here.
I'm sure it is something silly I have missed but I've been going round in circles, can anyone spot my mistake?
The UserControl XAML:
<UserControl
x:Class="FileExplorer.TreeViewUserControl"
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:fe="clr-namespace:WpfControlLibrary.FileExplorer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="FileExplorer"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
<TreeView
BorderThickness="0"
ItemsSource="{Binding FileSystem, RelativeSource={RelativeSource AncestorType=UserControl}}"
TreeViewItem.Collapsed="TreeView_Collapsed"
TreeViewItem.Expanded="TreeView_Expanded"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type fe:FileSystemObject}" ItemsSource="{Binding Items}">
<StackPanel Margin="0,2" Orientation="Horizontal">
<Image
Width="14"
Margin="2"
Source="{Binding Image}"
Stretch="Fill" />
<TextBlock
VerticalAlignment="Center"
FontSize="{StaticResource FontSizeSmall}"
Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The code behind for the UserControl:
Namespace FileExplorer
Public Class TreeViewUserControl
Inherits UserControl
#Region "Constructors"
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.DataContext = Me
End Sub
#End Region
#Region "Properties"
Public Shared ReadOnly FileSystemProperty As DependencyProperty = DependencyProperty.Register("FileSystem", GetType(FileSystemObjectCollection), GetType(TreeViewUserControl))
Public Property FileSystem As FileSystemObjectCollection
Get
Return CType(GetValue(FileSystemProperty), FileSystemObjectCollection)
End Get
Set(ByVal value As FileSystemObjectCollection)
SetValue(FileSystemProperty, value)
FileExplorer.UpdateLayout()
End Set
End Property
#End Region
Private Function GetFileSystemInfo(ByVal root As String) As FileSystemObjectCollection
Dim items As New FileSystemObjectCollection
' Parse all the directories at the path
For Each dir As String In Directory.GetDirectories(root)
items.Add(New FileSystemObject(dir, root))
Next
' Parse all the file at the path
For Each file As String In Directory.GetFiles(root)
items.Add(New FileSystemObject(file, root))
Next
Return items
End Function
Private Sub TreeView_Collapsed(sender As Object, e As RoutedEventArgs)
Dim node As TreeViewItem = CType(e.OriginalSource, TreeViewItem)
Dim fs As FileSystemObject = CType(node.DataContext, FileSystemObject)
fs.Clear()
End Sub
Private Sub TreeView_Expanded(sender As Object, e As RoutedEventArgs)
Dim node As TreeViewItem = CType(e.OriginalSource, TreeViewItem)
Dim fs As FileSystemObject = CType(node.DataContext, FileSystemObject)
If Not fs.HasChildren Then Exit Sub
fs.Items = GetFileSystemInfo(fs.FullName)
End Sub
End Class
End Namespace
The MainWindow XAML:
<Window
x:Class="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:fe="clr-namespace:WpfControlLibrary.FileExplorer;assembly=WpfControlLibrary"
xmlns:local="clr-namespace:ModStudio.Client"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:ModStudio.Client.ViewModels"
Title="MainWindow"
d:DesignHeight="600"
d:DesignWidth="800"
WindowStartupLocation="CenterOwner"
WindowState="Maximized"
mc:Ignorable="d">
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainWindowViewModel" />
</Window.Resources>
<Grid>
<fe:TreeViewUserControl DataContext="{StaticResource MainWindowViewModel}" FileSystem="{Binding ApplicationExplorer}" />
</Grid>
</Window>
The MainWindow has its DataContext set to a new instance of the ViewModel in code behind. The MainWindowViewModel has a property called ApplicationExplorer which is an instance of FileSystemObjectCollection. As mentioned FileSystemObjectCollection inherits from ObservableCollection(Of FileSystemObject). A FileSystemObject implements INotifyPropertyChanged. If I change ApplicationExplorer property, the control remains blank.
I have intentionally omitted some code here, but can add them if necessary.
Don't set the DataContext in the UserControl, i.e. remove this line:
Me.DataContext = Me
When you explcitly set the DataContext like this, you break the inheritance chain which means that the binding to the ApplicationExplorer property in your window no longer works:
<fe:TreeViewUserControl DataContext="{StaticResource MainWindowViewModel}"
FileSystem="{Binding ApplicationExplorer}" />
I made 3 changes and got this working!
Added OnPropertyChanged to ApplicationExplorer:
Public Property ApplicationExplorer As FileSystemObjectCollection
Get
Return _applicationExplorer
End Get
Set(value As FileSystemObjectCollection)
_applicationExplorer = value
OnPropertyChanged(NameOf(ApplicationExplorer))
End Set
End Property
Updated the binding in the UserControl:
<UserControl
x:Class="FileExplorer.TreeViewUserControl"
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:fe="clr-namespace:WpfControlLibrary.FileExplorer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="FileExplorer"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
<TreeView
BorderThickness="0"
ItemsSource="{Binding FileSystem, RelativeSource={RelativeSource AncestorType=fe:TreeViewUserControl}}"
TreeViewItem.Collapsed="TreeView_Collapsed"
TreeViewItem.Expanded="TreeView_Expanded"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type fe:FileSystemObject}" ItemsSource="{Binding Items}">
<StackPanel Margin="0,2" Orientation="Horizontal">
<Image
Width="14"
Margin="2"
Source="{Binding Image}"
Stretch="Fill" />
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>
Removed the Me.DataContext = Me from the UserControl code behind as you suggested
I'd like to bind the ItemsSource of a ContextMenu to a collection in my view model and I'd like the ContextMenu to show Separators as well.
Usually a Separator in a ContextMenu is rendered as a horizontal line. But this doesn't seem to work in my case. Maybe you can shed some light into this?
I know that view models should implement INotifyPropertyChanged but for the sake of simplicity I stripped my example of all unnecessary stuff.
MenuItemViewModel.vb:
Public Class MenuItemViewModel
Public Property IsSeparator As Boolean
Public Property Caption As String
End Class
MainViewModel.vb:
Public Class MainViewModel
Private ReadOnly _items As List(Of MenuItemViewModel)
Public Sub New()
_items = New List(Of MenuItemViewModel)
_items.Add(New MenuItemViewModel With {.Caption = "Item 1"})
_items.Add(New MenuItemViewModel With {.IsSeparator = True, .Caption = "Sep 1"})
_items.Add(New MenuItemViewModel With {.Caption = "Item 2"})
_items.Add(New MenuItemViewModel With {.Caption = "Item 3"})
_items.Add(New MenuItemViewModel With {.IsSeparator = True, .Caption = "Sep 2"})
_items.Add(New MenuItemViewModel With {.Caption = "Item 4"})
End Sub
Public ReadOnly Property Items As List(Of MenuItemViewModel)
Get
Return _items
End Get
End Property
End Class
MenuItemTemplateSelector.vb:
Public Class MenuItemTemplateSelector
Inherits DataTemplateSelector
Public Property ItemTemplate As DataTemplate
Public Property SeparatorTemplate As DataTemplate
Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate
Dim menuItem As MenuItemViewModel
menuItem = TryCast(item, MenuItemViewModel)
If (menuItem IsNot Nothing) AndAlso menuItem.IsSeparator Then
Return Me.SeparatorTemplate
Else
Return Me.ItemTemplate
End If
End Function
End Class
MainWindow.xaml:
<Window x:Class="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:WpfApp3"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="mit">
<TextBlock Text="{Binding Caption}" />
</DataTemplate>
<DataTemplate x:Key="mst">
<Separator />
<!--<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />-->
</DataTemplate>
<local:MenuItemTemplateSelector x:Key="mits"
ItemTemplate="{StaticResource mit}"
SeparatorTemplate="{StaticResource mst}" />
</Window.Resources>
<Grid>
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Text="Right click me">
<TextBox.ContextMenu>
<ContextMenu ItemsSource="{Binding Items}" ItemTemplateSelector="{StaticResource mits}" />
</TextBox.ContextMenu>
</TextBox>
</Grid>
</Window>
If you right click on the TextBox the ContextMenu pops up, but the Separators are not rendered as horizontal lines, they instead look like common menu items but without caption, They even blue up when the mouse hovers over them.
Since they have a caption in the view model and this caption doesn't show, it seems that it's really not using the defined ItemTemplate, but what template does it use? Or does a simple <Separator /> does not create a horizontal line anymore)?
How can I get a default separator to show up?
Edit: It seems, that my Separator gets wrapped inside a MenuItem, but how can I avoid that?
Following Ed Plunkett's comment I got rid of the "MenuItemTemplateSelector.vb" file and all the corresponding window resources ("mit", "mst" and "mits").
Then I added the following new window resources...
<ControlTemplate x:Key="mist" TargetType="{x:Type MenuItem}">
<Separator />
</ControlTemplate>
<ControlTemplate x:Key="mict" TargetType="{x:Type MenuItem}">
<MenuItem Header="{Binding Caption}" />
</ControlTemplate>
<Style x:Key="cmics" TargetType="{x:Type MenuItem}">
<Setter Property="Template" Value="{StaticResource mict}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSeparator}" Value="True">
<Setter Property="Template" Value="{StaticResource mist}" />
</DataTrigger>
</Style.Triggers>
</Style>
...and changed my TextBox to...
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Text="Right click me">
<TextBox.ContextMenu>
<ContextMenu ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource cmics}" />
</TextBox.ContextMenu>
</TextBox>
...and everything works fine.
Thank you Ed Plunkett.
I'd like to be able to bind the ItemsSource of a ContextMenu to a Collection in my view model, show Separators in the ContextMenu, and the ItemsSource has to be hierarchical (every level of the hierarchy will look the same).
In one of my other questions I managed to be able to show menu items and separators in a data bound ContextMenu, but now I struggle with making the ItemsSource hierarchical.
Right now I don't know what's going on, maybe you can enlighten me?
Here's my code again (simplified to be short, but working):
MenuItemViewModel.vb
Public Class MenuItemViewModel
Implements ICommand
Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
Public Property IsSeparator As Boolean
Public Property Caption As String
Private ReadOnly _subItems As List(Of MenuItemViewModel)
Public Sub New(createItems As Boolean, level As Byte)
_subItems = New List(Of MenuItemViewModel)
If createItems Then
_subItems.Add(New MenuItemViewModel(level < 4, level + 1) With {.Caption = "SubItem 1"})
_subItems.Add(New MenuItemViewModel(False, level + 1) With {.IsSeparator = True, .Caption = "SubSep 1"})
_subItems.Add(New MenuItemViewModel(level < 4, level + 1) With {.Caption = "SubItem 2"})
End If
End Sub
Public ReadOnly Property SubItems As List(Of MenuItemViewModel)
Get
Return _subItems
End Get
End Property
Public ReadOnly Property Command As ICommand
Get
Return Me
End Get
End Property
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
MessageBox.Show(Me.Caption)
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return True
End Function
End Class
The view model for each menu item on each level has a Caption to show in the context menu, an IsSeparator flag to indicate whether it's a separator or a functional menu item, a Command to be bound to when being a functional menu item and of course a SubItems collection containing functional menu items and separators down to a certain hierarchy level.
MainViewModel.vb
Public Class MainViewModel
Private ReadOnly _items As List(Of MenuItemViewModel)
Public Sub New()
_items = New List(Of MenuItemViewModel)
_items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 1"})
_items.Add(New MenuItemViewModel(False, 0) With {.IsSeparator = True, .Caption = "Sep 1"})
_items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 2"})
_items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 3"})
_items.Add(New MenuItemViewModel(False, 0) With {.IsSeparator = True, .Caption = "Sep 2"})
_items.Add(New MenuItemViewModel(True, 0) With {.Caption = "Item 4"})
End Sub
Public ReadOnly Property Items As List(Of MenuItemViewModel)
Get
Return _items
End Get
End Property
End Class
The main view model does only have an Items collection containing functional menu items as well as separators.
MainWindow.xaml
<Window x:Class="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:WpfApp3"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window.Resources>
<ControlTemplate x:Key="mist" TargetType="{x:Type MenuItem}">
<Separator />
</ControlTemplate>
<ControlTemplate x:Key="mict" TargetType="{x:Type MenuItem}">
<MenuItem Header="{Binding Caption}" Command="{Binding Command}" ItemsSource="{Binding SubItems}" />
</ControlTemplate>
<Style x:Key="cmics" TargetType="{x:Type MenuItem}">
<Setter Property="Template" Value="{StaticResource mict}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSeparator}" Value="True">
<Setter Property="Template" Value="{StaticResource mist}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Text="Right click me">
<TextBox.ContextMenu>
<ContextMenu ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource cmics}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding SubItems}" />
</ContextMenu.ItemTemplate>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</Grid>
</Window>
The window resources contain two ControlTemplates "mist" and "mict" and a Style "cmics" that switches between the two ControlTemplates depending on the value of the IsSeparator flag. This works fine as long as the ItemsSource is not hierarchical (see my other question).
If my Style "cmics" is attached to the ItemContainerStyle of the ContextMenu only (as in my example code) then it looks like this:
The first level works but the others don't. This doesn't change when attaching my Style "cmics" to the ItemContainerStyle of the HierarchicalDataTemplate as well.
If I only attach my Style "cmics" to the HierarchicalDataTemplate then it looks like this:
The first level doesn't show captions and separators, the second level works and the other levels don't work.
So, how can I persuade the ContextMenu to use my Style "cmics" as the ItemContainerStyle for every hierarchy level?
I just did some changes to your (TextBox) in Xaml portion. Have a look at this,
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Text="Right click me">
<TextBox.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding SubItems}">
<Button Content="{Binding Caption}" Command="{Binding Command}" Background="Red"/>
</HierarchicalDataTemplate>
</TextBox.Resources>
<TextBox.ContextMenu>
<ContextMenu ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource cmics}"/>
</TextBox.ContextMenu>
</TextBox>
Basically I have removed your ContexttMenu ItemTemplate and added under TextBox.Resources a hierarchical data template.
Inside the data template, I just now added a radio button. You can change the content as per your needs.
Let me know if this solves your problem or else needed any other help.
I found an answer here.
I had to create an empty view model just for the separators and a class that derives from ItemContainerTemplateSelector to return the DataTemplate that belongs to the type of the menu item ("MenuItemViewModel" or "SeparatorViewModel").
The linked article should be self explanatory.
Im very new to the WPF and MVVM (this is my 1st project in WPF). The project I'm working on is supposed to accept some search criteria, and display the results in the grid. To construct the query I'm using Dynamic LINQ queries. I seem to have issues managing instances of my ProjectSearchViewModel which corresponds to the view that's responsible for collecting the search criteria and executing the query. One instance is created when I create MainWindowViewModel. This creates all other viewmodel instances. This is what I expect. But when time comes to Show the MainWindow, I get another ProjectSearchViewModel, I guess from the binding.
The general idea is this:
The search criteria are filled in the ProjectSearchView.
When Load Command is pressed, I send SearchResultMessage using Reactive Extensions method.
The message is picked up by MainWindowViewModel
MainWindowViewModel is querying the ProjectSearchViewModel.SearchResult and assigning the IObservable List to AllProjectsViewModel.AllProjects which is bound to a datagrid to show the results (AllProjectView is responsible to show the grid with resulting projects list)
The problem is that the parameter filling and sending of the SearchResultMessage happens in one instance of ProjectSearchViewModel and the actual querying of SearchResult from MainWindowViewModel happens in another instance, where all the search criteria are empty.
I guess I have no choice but to post my code: Here it the abridged version of it, omitting some iDisposable plumbing and such. For my model I use Entity Framework 4.
As I mentioned, I'm a total newbie so If anyone sees any blatant disregard for common sense, please set me straight.
Imports Cheminator.ViewModel
Partial Public Class App
Inherits Application
Private viewModel As MainWindowViewModel
Private window As MainWindow
Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
MyBase.OnStartup(e)
window = New MainWindow
viewModel = New MainWindowViewModel '1st instance of ProjectSearchViewModel created Here
window.DataContext = viewModel
window.Show() '2nd instance of ProjectSearchViewModel created Here
End Sub
End Class
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cheminator"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
xmlns:vw="clr-namespace:Cheminator.Views"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxnb="http://schemas.devexpress.com/winfx/2008/xaml/navbar"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
Title="DXWpfApplication" Height="600" Width="800"
dx:ThemeManager.ThemeName="Office2007Blue"
>
<Window.Resources>
<ResourceDictionary Source="MainWindowResources.xaml" />
</Window.Resources>
<dxd:DockLayoutManager>
<dxd:LayoutGroup>
<dxd:LayoutGroup Orientation="Vertical" Width="3*">
<dxd:DocumentGroup Height="3*" SelectedTabIndex="0">
<dxd:DocumentPanel Caption="Document1" Height="3*" >
<ContentControl
Content="{Binding Path=ProjectsVM}"
/>
</dxd:DocumentPanel>
</dxd:DocumentGroup>
<dxd:LayoutPanel Caption="Search Criteria" Height="*" CaptionImage="Images/Icons/DetailView.png">
<ContentControl
Content="{Binding Path=ProjectsSearchVM}"
/>
</dxd:LayoutPanel>
</dxd:LayoutGroup>
</dxd:LayoutGroup>
</dxd:DockLayoutManager>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cheminator"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
xmlns:vw="clr-namespace:Cheminator.Views" >
<DataTemplate DataType="{x:Type vm:AllProjectsViewModel}">
<vw:AllProjectsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ProjectSearchViewModel}">
<vw:ProjectSearchView />
</DataTemplate>
<UserControl x:Class="Cheminator.Views.AllProjectsView"
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:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:Cheminator.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<dxg:GridControl AutoPopulateColumns="True" ShowBorder="False" >
<dxg:GridControl.DataSource>
<Binding Path="AllProjects"/>
</dxg:GridControl.DataSource>
<dxg:GridControl.View>
<dxg:TableView>
</dxg:TableView>
</dxg:GridControl.View>
</dxg:GridControl>
</UserControl>
<UserControl x:Class="Cheminator.Views.ProjectSearchView"
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:vm="clr-namespace:Cheminator.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="470">
<Grid Height="160" Width="470">
<Grid.DataContext>
<vm:ProjectSearchViewModel />
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="175*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="Cotation ID:" Height="28" Margin="49,12,32,0" Name="Label1" VerticalAlignment="Top" />
<TextBox Grid.Column="1" Height="23" Margin="0,14,159,0" Name="CotationIDTextBox" VerticalAlignment="Top" Text="{Binding Path=CotationID, UpdateSourceTrigger=LostFocus}"/>
<Label Grid.Row="1" Content="Cotation Name:" Height="28" Margin="49,6,6,0" Name="Label2" VerticalAlignment="Top" />
<TextBox Grid.Row="1" Grid.Column="1" Height="23" Margin="0,8,159,0" Name="CotationNameTextBox" VerticalAlignment="Top" Text="{Binding Path=ProjectSummary, UpdateSourceTrigger=PropertyChanged}"/>
<Label Grid.Row="2" Content="User:" Height="28" Margin="49,6,32,0" Name="Label3" VerticalAlignment="Top" />
<TextBox Grid.Row="2" Grid.Column="1" Height="23" Margin="0,8,159,0" Name="UserTextBox" VerticalAlignment="Top" Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged}"/>
<Button
Command="{Binding Path=LoadCommand}"
Content="_Load"
HorizontalAlignment="Right"
Margin="0,10,51,12"
MinWidth="60" Grid.Row="3" Width="72" Grid.Column="1" />
</Grid>
</UserControl>
Public Class MainWindowViewModel
Inherits ViewModelBase
Private _commands As ReadOnlyCollection(Of CommandViewModel)
Private _ProjectsVM As AllProjectsViewModel
Private _ProjectsSearchVM As ProjectSearchViewModel
Public Sub New()
MyBase.DisplayName = "Cheminator"
_ProjectsSearchVM = New ProjectSearchViewModel
Messenger.[Default].OfType(Of SearchResultMessage) _
.Subscribe(Sub(param As SearchResultMessage)
ProjectsVM.AllProjects = ProjectsSearchVM.SearchResult
End Sub)
_ProjectsVM = New AllProjectsViewModel
End Sub
Public ReadOnly Property ProjectsVM As AllProjectsViewModel
Get
If (_ProjectsVM IsNot Nothing) Then
Return _ProjectsVM
End If
Return Nothing
End Get
End Property
Public ReadOnly Property ProjectsSearchVM As ProjectSearchViewModel
Get
If (_ProjectsSearchVM IsNot Nothing) Then
Return _ProjectsSearchVM
End If
Return Nothing
End Get
End Property
End Class
Public Class AllProjectsViewModel
Inherits ViewModelBase
Private m_ProjectsList As ObservableCollection(Of xGMV_Cotation)
Public Sub New()
MyBase.DisplayName = "Temp AllProjectsViewModel Name" 'Strings.AllProjectsViewModel_DisplayName
End Sub
Public Property AllProjects() As ObservableCollection(Of xGMV_Cotation)
Get
Return m_ProjectsList
End Get
Set(ByVal value As ObservableCollection(Of xGMV_Cotation))
m_ProjectsList = value
OnPropertyChanged("AllProjects")
End Set
End Property
End Class
Public Class ProjectSearchViewModel
Inherits ViewModelBase
Private m_ProjectsList As ObservableCollection(Of xGMV_Cotation)
Public Sub New()
MyBase.DisplayName = "Cheminator.ProjectSearchViewModel"
End Sub
Dim _CotationID As Integer
Public Property CotationID As Integer
Get
Return _CotationID
End Get
Set(ByVal value As Integer)
_CotationID = value
MyBase.OnPropertyChanged("CotationID")
End Set
End Property
Public Property ProjectSummary As String
Public Property UserName As String
Private m_LoadCommand As RelayCommand
Public ReadOnly Property LoadCommand As ICommand
Get
If m_LoadCommand Is Nothing Then
Dim LoadAction As New Action(Of Object)(AddressOf Me.Load)
m_LoadCommand = New RelayCommand(LoadAction)
End If
Return m_LoadCommand
End Get
End Property
Public ReadOnly Property SearchResult() As ObservableCollection(Of xGMV_Cotation)
Get
Dim xWhere As String = ""
Dim i As Integer = 0
Dim parameterList As New ArrayList
If Not String.IsNullOrEmpty(CotationID) Then
xWhere = String.Format("CotationID = #{0}", i)
parameterList.Add(CotationID)
i += 1
End If
If Not String.IsNullOrEmpty(ProjectSummary) Then
If i > 0 Then
xWhere = xWhere & " AND "
End If
xWhere = xWhere & String.Format("ProjectSummary = '#{0}'", i)
i += 1
parameterList.Add(ProjectSummary)
End If
If Not String.IsNullOrEmpty(UserName) Then
If i > 0 Then
xWhere = xWhere & " AND "
End If
xWhere = xWhere & String.Format("UserName = '#{0}'", i)
i += 1
parameterList.Add(UserName)
End If
Return New ObservableCollection(Of xGMV_Cotation)(DataContext.DBEntities.xGMV_Cotations.Where(xWhere, parameterList.ToArray))
End Get
End Property
Private Sub Load()
OnPropertyChanged("SearchResult")
Messenger.Default.Send(New SearchResultMessage())
End Sub
End Class
everytime you create a ProjectSearchView usercontrol, you also create a ProjectSearchViewModel too. you should remove the following from your usercontrl xaml.
<Grid.DataContext>
<vm:ProjectSearchViewModel />
</Grid.DataContext>
because of your datatemplate you do not need to set the datacontext for ProjectSearchView, it already has the right datacontext.
I have a custom control with a ContentTemplate to display child controls. The data context isn't passing through my DataTemplate and so when I bind my child control, I'm not able to retrieve that value. I'm pretty sure that I'm not implementing this correctly specifically with respect to the DataTemplate, so I would appreciate any help. I've broken the problem down into as small a scenario as I can.
First, the Page:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<local:MainWindowViewModel x:Key="ViewModel" />
</ResourceDictionary>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<local:MyControl>
<local:MyControl.MainContent>
<DataTemplate>
<TextBlock Text="{Binding TextValue}" />
</DataTemplate>
</local:MyControl.MainContent>
</local:MyControl>
</Grid>
Next, the ViewModel:
Public Class MainWindowViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _textValue As String
Public Property TextValue() As String
Get
If String.IsNullOrEmpty(_textValue) Then
_textValue = "A default value"
End If
Return _textValue
End Get
Set(ByVal value As String)
_textValue = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("TextValue"))
End Set
End Property
End Class
And now my custom control code-behind:
Public Class MyControl
Inherits System.Windows.Controls.Control
Shared Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(MyControl), New FrameworkPropertyMetadata(GetType(MyControl)))
End Sub
Public Property MainContent As DataTemplate
Get
Return GetValue(MainContentProperty)
End Get
Set(ByVal value As DataTemplate)
SetValue(MainContentProperty, value)
End Set
End Property
Public Shared ReadOnly MainContentProperty As DependencyProperty = _
DependencyProperty.Register("MainContent", _
GetType(DataTemplate), GetType(MyControl), _
New FrameworkPropertyMetadata(Nothing))
End Class
And finally, my custom control definition:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyControl}">
<StackPanel>
<TextBlock Text="Hello, World!" />
<ContentControl x:Name="MainContentArea" ContentTemplate="{TemplateBinding MainContent}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
When this is run, the value from the ViewModel (TextValue) is not bound.
ContentControl is a little special in regards to DataContext. The DataContext within the DataTemplate is the Content of the ContentControl and not its DataContext. I didn't try your code but after a quick look I have a feelings that's your problem
You could try to bind the Content of the ContentControl to its DataContext with
<ContentControl x:Name="MainContentArea" Content="{Binding}" ...