[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 ViewModels 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.
