如何在 UserControl 中显示基于层次结构的对象并使用 MVVM 呈现命令?
How can I display hierarchical-based objects in an UserControl and present Commands using MVVM?
[由于这个问题是关于 MVVM 思想的,所以我在这个问题中使用了伪代码和 -XAML 以使其尽可能简短和准确。]
我最近 运行 遇到了一个 MVVM 问题,我无法使用最佳实践建议解决该问题。
想象一下,您正在尝试为某物创建一个编辑器程序。我们将使用图书馆模型(我想不出更好的模型):
- 一个图书馆包含一本或多本书
- 一本书包含一个或多个章节
- 一个章节包含一个或多个段落
UI可能是这样的可能设计的不好,毕竟这只是一个例子:
现在我想出了这个 MainWindow
定义:
<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>
虽然这看起来仍然很整洁,但我似乎无法在 UserControl
本身中实现所需的行为:
<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>
当我沿着本书的层次结构树走下去时,事情开始变得复杂起来。例如,删除一个段落时,我必须将三个命令参数传递给 MainWindow
(在哪本书中?在哪一章中?在哪一段中?)。
我可以通过摆脱 UserControl
的 DependencyProperties
、将 TabControl 直接放在 MainWindow 并添加单独的 ViewModel
s 到子控件。这样 EditBookControl
可以自行进行所需的更改:
(Everything in MainWindow)
public List<Control> EditControls;
<TabControl ItemsSource="{Binding EditControls}" />
SelectBookCommand_Executed { EditControls.Add(new EditBookControl(new BookVM(e.CommandParameter as Book))); }
据我所知,这不是要走的路;最佳做法是使用一个 ViewModel
每个 Window
如下所述:
- SO: MVVM + UserControl + Dependency Property
老实说,我无法想象每个 Window 只允许一个 ViewModel。 Visual Studio 也是使用 WPF 编写的 - 他们真的使用一个 ViewModel 来处理大量功能吗?
我想知道如何解决这个难题和编写干净漂亮的代码。
一旦你理解了 MVVM 就非常简单。
您需要知道的一件事是,在 DataTemplate 中,DataContext 是我们在其上应用 DataTemplate 的对象。
所以在LibraryV.xaml的ItemsControl中,我们将一个DataTemplate应用于BookVM的集合,所以BookV中的DataContext就是相关的BookVM。
它让你很容易获得你想要的信息。
你的问题的一个非常简单(不完整)的版本:
LibraryVM.cs:
public class LibraryVM{
public LibraryVM(LibraryModel model) {
_model = model
}
#region CmdRemove
private DelegateCommand _cmdRemove;
public DelegateCommand CmdRemove {
get { return _cmdRemove ?? (_cmdRemove = new DelegateCommand(Remove, CanRemove)); }
}
private void Remove(Object parameter) {
BookVM bookToRemove = (BookVM)parameter;
Books.Remove(bookToRemove);
}
private void CanRemove(Object parameter) {
BookVM bookToRemove = parameter as BookVM;
return bookToRemove != null && Books.Contains(bookToRemove);
}
#endregion
private readonly LibraryModel _model;
public List<BookVM> Books {get {return _model.Books.Select(b => new BookVM(b)).ToList();}}
}
BookVM.cs:
public class BookVM{
public LibraryVM(BookModel model) {
_model = model
}
private readonly BookModel _model;
public String Title {get {return _model.Title;}}
public List<ChapterVM> Chapters {get {return _model.Chapters.Select(c => new ChapterVM(c)).ToList();}}
}
BookV.xaml:
<UserControl ...>
<StackPanel Orientation="Vertical">
<Button><!-- Button to remove the book from the library -->
<TextBlock Text="Remove"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type view:LibraryV}}, Path=DataContext.CmdRemove}"
CommandParameter="{Binding Mode=OneTime}"/>
</Button>
<TextBlock Text="{Binding Title, Mode=OneWay}"/>
<ItemsControl ItemsSource="{Binding Chapters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ChapterVM}">
<view:ChapterV/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
LibraryV.xaml:
<UserControl ...>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Books, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:BookVM}">
<view:BookV/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
您唯一需要设置的 DataContext 是 LibraryV 的 DataContext。
[由于这个问题是关于 MVVM 思想的,所以我在这个问题中使用了伪代码和 -XAML 以使其尽可能简短和准确。]
我最近 运行 遇到了一个 MVVM 问题,我无法使用最佳实践建议解决该问题。
想象一下,您正在尝试为某物创建一个编辑器程序。我们将使用图书馆模型(我想不出更好的模型):
- 一个图书馆包含一本或多本书
- 一本书包含一个或多个章节
- 一个章节包含一个或多个段落
UI可能是这样的可能设计的不好,毕竟这只是一个例子:
现在我想出了这个 MainWindow
定义:
<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>
虽然这看起来仍然很整洁,但我似乎无法在 UserControl
本身中实现所需的行为:
<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>
当我沿着本书的层次结构树走下去时,事情开始变得复杂起来。例如,删除一个段落时,我必须将三个命令参数传递给 MainWindow
(在哪本书中?在哪一章中?在哪一段中?)。
我可以通过摆脱 UserControl
的 DependencyProperties
、将 TabControl 直接放在 MainWindow 并添加单独的 ViewModel
s 到子控件。这样 EditBookControl
可以自行进行所需的更改:
(Everything in MainWindow)
public List<Control> EditControls;
<TabControl ItemsSource="{Binding EditControls}" />
SelectBookCommand_Executed { EditControls.Add(new EditBookControl(new BookVM(e.CommandParameter as Book))); }
据我所知,这不是要走的路;最佳做法是使用一个 ViewModel
每个 Window
如下所述:
- SO: MVVM + UserControl + Dependency Property
老实说,我无法想象每个 Window 只允许一个 ViewModel。 Visual Studio 也是使用 WPF 编写的 - 他们真的使用一个 ViewModel 来处理大量功能吗?
我想知道如何解决这个难题和编写干净漂亮的代码。
一旦你理解了 MVVM 就非常简单。
您需要知道的一件事是,在 DataTemplate 中,DataContext 是我们在其上应用 DataTemplate 的对象。
所以在LibraryV.xaml的ItemsControl中,我们将一个DataTemplate应用于BookVM的集合,所以BookV中的DataContext就是相关的BookVM。
它让你很容易获得你想要的信息。
你的问题的一个非常简单(不完整)的版本:
LibraryVM.cs:
public class LibraryVM{
public LibraryVM(LibraryModel model) {
_model = model
}
#region CmdRemove
private DelegateCommand _cmdRemove;
public DelegateCommand CmdRemove {
get { return _cmdRemove ?? (_cmdRemove = new DelegateCommand(Remove, CanRemove)); }
}
private void Remove(Object parameter) {
BookVM bookToRemove = (BookVM)parameter;
Books.Remove(bookToRemove);
}
private void CanRemove(Object parameter) {
BookVM bookToRemove = parameter as BookVM;
return bookToRemove != null && Books.Contains(bookToRemove);
}
#endregion
private readonly LibraryModel _model;
public List<BookVM> Books {get {return _model.Books.Select(b => new BookVM(b)).ToList();}}
}
BookVM.cs:
public class BookVM{
public LibraryVM(BookModel model) {
_model = model
}
private readonly BookModel _model;
public String Title {get {return _model.Title;}}
public List<ChapterVM> Chapters {get {return _model.Chapters.Select(c => new ChapterVM(c)).ToList();}}
}
BookV.xaml:
<UserControl ...>
<StackPanel Orientation="Vertical">
<Button><!-- Button to remove the book from the library -->
<TextBlock Text="Remove"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type view:LibraryV}}, Path=DataContext.CmdRemove}"
CommandParameter="{Binding Mode=OneTime}"/>
</Button>
<TextBlock Text="{Binding Title, Mode=OneWay}"/>
<ItemsControl ItemsSource="{Binding Chapters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ChapterVM}">
<view:ChapterV/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
LibraryV.xaml:
<UserControl ...>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Books, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:BookVM}">
<view:BookV/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
您唯一需要设置的 DataContext 是 LibraryV 的 DataContext。