我有一套现有的Silverlight应用程序,使用MVVM模式来分离视图和视图模型。我们使用Unity 2.0作为IoC容器,将依赖注入ViewModel类(和支持类型)。我有一个现有的ViewModelLocator类,它使用Unity容器来解析ViewModel。
所有这些在运行时都工作得很好;然而,因为ViewModelLocator依赖于Unity容器的创建和配置,由一个Bootstrapper类从App.xaml.cs中的Application_Start方法"运行",我失去了在设计器或Blend中打开视图的能力。
我正在寻找如何重新工作ViewModelLocator以支持"可混合性"的建议。
请注意,我不愿意仅仅为了可混合性而强制我们的ViewModel类实现默认的无参数构造函数。我们也有我们的ViewModels检查IsInDesignMode属性(来自MVVM Light ViewModelBase类)来提供设计时数据而不是进行服务调用,所以我们没有不同的ViewModel实现用于设计时和运行时。
您希望UI元素具有良好的设计时体验(包括在Blend中)。这听起来是个合理的目标。
让我们看看什么是UI元素。对于这个答案的其余部分,我将称之为控制。控件的职责是呈现UI并响应用户事件。只要它应该有行为,它应该只具有与UI呈现和用户事件相关的行为。
除此之外,框架本身(Silverlight和WPF)强加了一个规则:所有控件必须有一个默认构造函数。此外,由于DataContext
是一个属性,因此可以选择对其进行赋值。
我们应该牢记封装。我们开发的任何控件都应该在上述约束条件下很好地工作。这意味着,除了具有默认构造函数外,它也应该在没有DataContext的情况下正常工作。换句话说,Blendability体验应该由Control本身提供,而不是任何需要引导来分配DataContext
的外部容器。
当控件必须响应用户事件时,我总是发现ICommand
接口绰绰有余。将ICommands
附加到控件中任何适用的事件处理程序。ICommands
是由视图模型定义的,但ICommand
的美妙之处在于它基本上是一个void方法,这意味着在DataContext
为空的情况下提供(no-op) Local Default是微不足道的。但是,这很少是必要的,因为设计人员不会调用command。
这是我书中的一个例子:
<Window x:Class="Ploeh.Samples.ProductManagement.WpfClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Product Management"
Height="300"
Width="300"
MinHeight="300"
MinWidth="300">
<Window.Resources>
<Style x:Key="ProductStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</Window.Resources>
<DockPanel FocusManager.FocusedElement="{Binding ElementName=productsListView}">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<Separator />
<MenuItem Header="E_xit" Command="{Binding Path=CloseCommand}" />
</MenuItem>
<MenuItem Header="_Actions">
<MenuItem Header="_Refresh" InputGestureText="F5" Command="{Binding Path=RefreshCommand}" />
<MenuItem Header="_Add Product" InputGestureText="Ins" Command="{Binding Path=InsertProductCommand}" />
<MenuItem Header="_Edit Product" InputGestureText="Enter" Command="{Binding Path=EditProductCommand}" />
<MenuItem Header="_Delete Product" InputGestureText="Del" Command="{Binding Path=DeleteProductCommand}" />
</MenuItem>
</Menu>
<ToolBarTray DockPanel.Dock="Top" HorizontalAlignment="Stretch">
<ToolBar HorizontalAlignment="Stretch" HorizontalContentAlignment="Left">
<Button Command="{Binding Path=RefreshCommand}">Refresh</Button>
<Button Command="{Binding Path=InsertProductCommand}">Add</Button>
<Button Command="{Binding Path=EditProductCommand}">Edit</Button>
<Button Command="{Binding Path=DeleteProductCommand}">Delete</Button>
</ToolBar>
</ToolBarTray>
<ListView x:Name="productsListView" ItemContainerStyle="{StaticResource ProductStyle}" ItemsSource="{Binding Path=Products}" SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Path=Id}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=UnitPrice}" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
和后面的代码:
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
}