关于“可混合”的建议ViewModelLocator 与 Unity 2.0

发布于 2024-12-02 18:31:33 字数 547 浏览 0 评论 0原文

我有一套现有的 Silverlight 应用程序,使用 MVVM 模式来分离视图和视图模型。我们使用 Unity 2.0 作为 IoC 容器,将依赖项注入 ViewModel 类(和支持类型)。我有一个现有的 ViewModelLocator 类,它使用 Unity 容器来解析 ViewModel。

所有这些在运行时都运行良好;但是,由于 ViewModelLocator 依赖于由 Bootstrapper 类创建和配置的 Unity 容器,该类是从 App.xaml.cs 中的 Application_Start 方法“运行”的,因此我无法在设计器或 Blend 中打开视图。

我正在寻找如何重新设计 ViewModelLocator 以支持“可混合性”的建议。

请注意,我不愿意仅仅为了可混合性而强制我们的 ViewModel 类实现默认的无参数构造函数。我们还让 ViewModel 检查 IsInDesignMode 属性(来自 MVVM Light ViewModelBase 类)以提供设计时数据而不是进行服务调用,因此我们在设计时和运行时没有不同的 ViewModel 实现。

让我知道你的想法。

I have an suite of existing Silverlight applications using the MVVM pattern to separate Views and ViewModels. We use Unity 2.0 for an IoC container to inject dependencies into the ViewModel classes (and supporting types). I have an existing ViewModelLocator class that uses the Unity Container to resolve the ViewModel.

All of this is working great at runtime; however, because the ViewModelLocator relies on the Unity Container being created and configured by a Bootstrapper class that is "Run" from the Application_Start method in App.xaml.cs, I've lost the ability to open the views in the designer or in Blend.

I am looking for suggestions how I can rework the ViewModelLocator to support "Blendability".

Note that I'm not willing to force our ViewModel classes to implement default parameterless constructors just for the sake of Blendability. We also have our ViewModels check the IsInDesignMode property (from the MVVM Light ViewModelBase class) to supply design-time data versus making service calls so we don't have different ViewModel implementations for design-time and run-time.

Let me know what you think.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

誰ツ都不明白 2024-12-09 18:31:33

您希望 UI 元素具有良好的设计时体验(包括在 Blend 中)。这听起来是一个合理的目标。

让我们看看什么是 UI 元素。对于这个答案的其余部分,我将其称为“控件”。控件的职责是呈现 UI 并响应用户事件。只要它应该有行为,它就应该只具有与 UI 渲染和用户事件相关的行为。

除此之外,框架本身(Silverlight 和 WPF)强加了一条规则:所有控件都必须有一个默认构造函数。此外,由于 DataContext 是一个属性,因此可以选择分配它。

我们应该牢记封装。我们开发的任何控件都应该在上面给出的限制内很好地工作。这意味着,除了具有默认构造函数之外,它在没有 DataContext 的情况下也应该正常工作。换句话说,Blendability 体验应该由 Control 本身提供,而不是任何需要引导来分配 DataContext 的外部容器。

当控件必须响应用户事件时,我总是发现 ICommand 接口就足够了。将 ICommands 附加到控件中任何适用的事件处理程序。 ICommands 由视图模型定义,但 ICommand 的优点在于它基本上是一个 void 方法,这意味着提供(无操作)本地默认值很简单在 DataContext 为 null 的情况下。然而,这很少有必要,因为命令不是由设计者调用的。


这是我的书中的示例:

<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();
    }
}

You want UI elements to have a good design-time experience (including in Blend). That sounds like a reasonable goal.

Let’s look at what a UI element is. For the rest of this answer I’m going to call it a Control. The responsibility of a Control is to render UI and respond to user events. Insofar as it should have behavior, it should only have behavior that relates to UI rendering and user events.

In addition to that, the framework itself (Silverlight as well as WPF) imposes a rule: all Controls must have a default constructor. Furthermore, since the DataContext is a property, it’s optional to assign it.

We should keep encapsulation in mind. Any Control we develop should work nicely within the constraints given above. This means that, apart from having a default constructor, it should also work correctly without a DataContext. In other words, the Blendability experience should be supplied by the Control itself, not any external container that needs to be bootstrapped to assign a DataContext.

When a Control must respond to user events I’ve always found the ICommand interface more than sufficient. Attach ICommands to any applicable event handler in the Control. The ICommands are defined by the View Model, but the beauty of ICommand is that it’s basically a void method which means that it’s trivial to supply a (no-op) Local Default in the case where the DataContext is null. However, that’s rarely necessary, as the Commands aren’t invoked by designers.


Here's an example from my book:

<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>

and the code-behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文