[As this question is about MVVM thoughts, I'm using pseudo-code and -XAML in this question to get it as short and precise as possible.]
I've recently ran into a MVVM problem that I wasn't able to solve yet using best practice recommendations.
Imagine you're trying to create an editor program for something. We're going to use a library model (I couldn't come up with a better one):
- A library contains one or many books
- A book contains one or more chapters
- A chapter contains one or more paragraphs
The UI might look like this. It might be designed badly, but this is just an example after all:
Now I came up with this MainWindow
definition:
<Window DataContext="{PseudoResource EditorVM}">
<DockPanel>
<controls:AllBooksControl Books="{Binding Books}"
AddCommand="{Binding AddBookCommand}"
SelectCommand="{Binding SelectBookCommand}"
DockPanel.Dock="Left" />
<controls:EditBooksControl Books="{Binding SelectedBooks}"
DeleteBookCommand="{Binding DeleteBookCommand}"
AddChapterCommand="{Binding AddChapterCommand}"
DeleteChapterCommand="{Binding DeleteChapterCommand}"
AddParagraphCommand="{Binding AddParagraphCommand}"
DeleteParagraphCommand="{Binding DeleteParagraphCommand}" />
</DockPanel>
</Window>
While this still looks neat, I can't seem to implement the required behaviour in the UserControl
itself:
<UserControl x:Class="EditBooksControl" x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="Book">
<StackPanel Orientation="Vertical">
<Button Content="REMOVE"
Command="{Binding DeleteBookCommand, ElementName=root}"
CommandParameter="{Binding}" />
<TextBox Content="{Binding Title}" />
<WrapPanel ItemsSource="{Binding Chapters}" (Ignoring the additional Add tile here) />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="Chapter" (Template for the chapter tiles)>
<StackPanel Orientation="Vertical">
<Button Content="REMOVE"
Command="{Binding DeleteChapterCommand, ElementName=root}"
CommandParameter="{Binding}"
CommandParameter2="... I need to pass the chapter's parent book here, but there's no such a second command parameter, too ..." />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<TabControl ItemsSource="{Binding Books, ElementName=root}" />
</UserControl>
Things start to get complicated as I walk down the Book's hierarchical tree. For example I'd have to pass three command parameters to the MainWindow
when deleting a paragraph (in which book?, in which chapter?, which paragraph?).
I was able to solve all of this by getting rid of the UserControl
's DependencyProperties
, placing the TabControl directly in the MainWindow and adding separate ViewModel
s to the child controls. This way the EditBookControl
can make the required changes by itself:
(Everything in MainWindow)
public List<Control> EditControls;
<TabControl ItemsSource="{Binding EditControls}" />
SelectBookCommand_Executed { EditControls.Add(new EditBookControl(new BookVM(e.CommandParameter as Book))); }
As I read, this is not the way to go; best practice is using one ViewModel
per Window
as described here:
- SO: How do I Bind WPF Commands between a UserControl and a parent Window
- SO: MVVM + UserControl + Dependency Property
I honestly can't imagine that only one ViewModel per Window is allowed. Visual Studio is written by using WPF as well - did they really used one ViewModel for the tons and tons of features?
I'd like to know how I can solve this dilemma and writing clean and nice code.