在运行时和设计时将ViewModel分配给UserControl

8

我正在使用MVVM Light和WPF编写一些数据可视化代码。以下是一个片段:

    <Window x:Class="EventBlockVisualization.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:ignore="http://www.ignore.com"
            Title="MainWindow"
            mc:Ignorable="d ignore"
            DataContext="{Binding Main, Source={StaticResource Locator}}">
        <Window.Resources>
            <ItemsPanelTemplate x:Key="GraphRowItemsPanelTemplate">
                <StackPanel IsItemsHost="True" Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </Window.Resources>
        <Grid IsSharedSizeScope="True">
            <ScrollViewer Margin="8" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True">
                <ItemsControl x:Name="GraphItemsControl" Margin="8"  ItemsSource="{Binding VibeEvents, Mode=OneTime}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition SharedSizeGroup="NameWidthSizeGroup" Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock x:Name="NameTextBlock" Text="{Binding Name}" Grid.Column="0" Margin="4,0"/>
                                <ItemsControl x:Name="GraphRowItemsControl" ItemsSource="{Binding VibeEventViewModels, Mode=OneTime}" ItemsPanel="{DynamicResource GraphRowItemsPanelTemplate}"  Grid.Column="1" Margin="4,0">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Grid HorizontalAlignment="Left" VerticalAlignment="Center" Height="10">
                                                <TextBlock x:Name="FGTitleTextBox" Text="{Binding FGTitle}" Visibility="Collapsed"/>
                                                <Button Margin="1,0,0,0" Width="{Binding LengthInSeconds}" HorizontalAlignment="Left" Background="{Binding BackgroundColor}" BorderBrush="#FF2186A1">
                                                    <Button.ToolTip>
                                                        <ToolTip>
                                                            <StackPanel>
                                                                <TextBlock FontWeight="Bold" Text="{Binding FGTitle}"/>
                                                                <TextBlock Text="{Binding LengthText}"/>
                                                            </StackPanel>
                                                        </ToolTip>
                                                    </Button.ToolTip>
                                                </Button>
                                            </Grid>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </Grid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </ScrollViewer>
        </Grid>
    </Window>

我想替换中央的ItemsControl.ItemTemplate DataTemplate,并将其更改为用户控件,以便我可以在Expression Blend中更轻松地设计它。
我找不到包含MVVM Light中用户控件的简单示例,但有一些教程文章。例如,在MVVM实例化方法(选项6)中,Paul Stovell建议在MVVM Light中绑定UserControl的ViewModel,如下所示:
<UserControl ...>
    <UserControl.Resources>
        <ViewModelLocator x:Key="ViewModelLocator"/>
    </UserControl.Resources>
    <TextBox Text="{Binding Source={DynamicResource ViewModelLocator}, Path=CalculatorViewModel...}" />

当我在Expression Blend中设计UserControl时,这将非常有效,因为定位器可以提供一个带有虚拟数据的ViewModel。但是,在运行时会发生什么呢?该绑定如何被覆盖为由主ViewModel中的集合提供的UserControl的ViewModel类的实例?在设计时间的MainWindow上也会出现同样的问题。如果我正在Expression Blend中处理MainWindow的外观和感觉,那么该绑定如何被覆盖为由设计时主ViewModel中的集合提供的UserControl的ViewModel类的实例?已经有一些关于这个问题的问题和答案。
  1. https://dev59.com/qXA75IYBdhLWcg3wW3y8#3334780中,akjoshi建议主ViewModel持有UserControl的ViewModel实例;但是当我设计UserControl本身时,这该如何实现呢?

  2. https://dev59.com/dmLVa4cB1Zd3GeqPsw8K#9910298中,tam指出,“您希望保持数据上下文开放并可用于绑定到使用此控件的控件中”,并在以下评论中,SoMoS补充说,需要“在ViewModel中为绑定属性创建属性,并且当有人想要更改控件的一个属性(例如某个子控件启用)时,他将不得不通过View Model”。这很有前途,但我不确定在MainViewModel的可绑定集合中应该做什么。

  3. https://dev59.com/2ljUa4cB1Zd3GeqPOy74#6340668中,Ehsan Ershadi建议,“不建议使用MVVM Light ViewModelLocator来创建UserControles,因为它是静态属性,当您要实例化多个用户控件的实例时,它们都将具有相同的公共ViewModel,因此它们都会以相同的方式运作,而这不是我们希望在整个项目中使用一次的UserControl中看到的情况。”然后指出,“要解决这个问题,您需要通过使所有属性非静态来修改ViewModelLocator,例如”。我不确定这对我有什么帮助。

  4. https://dev59.com/f3E85IYBdhLWcg3wvGER#2637830的评论中,Jon Mitchell提到,“MVVM似乎并不是创建用户控件的理想选择”。我希望这不是正确的。

  5. 相反,在何时应该使用UserControl而不是Page?中,dthrasher提到,“许多WPF MVVM框架似乎避免使用NavigationWindow和Page控件,而是使用嵌套UserControls组合页面”,即UserControls是MVVM中常见的设备。

  6. https://dev59.com/R3I-5IYBdhLWcg3wipBk#1798649中,Reed Copsey提醒sandbox,“UserControls始终可以通过公开属性并使用DataBinding与其包含的控件交流。这非常好,因为它在所有方面都保留了MVVM风格。”并且“包含的控件可以使用属性将两个用户控件上的两个属性链接在一起,从而保留了清晰的边界”。但是当我在Expression Blend设计UserControl时,我仍然不知道如何实现这一点。

  7. 我应该使用UserControls来代替DataTemplates作为我的视图吗?中,Rachel提到偶尔使用Expression Blend来设计UserControl,然后将代码复制粘贴到DataTemplate中:“如果我确实想要将其用于设计DataTemplate,我通常会创建一个新的UserControl,按照自己的方式进行设计,然后将内容复制/粘贴到DataTemplate中”

抱歉这个问题有点长!我对如何在设计一个UserControl以成为MainWindow中集合项的可视化方式使用MVVM Light感到困惑,特别是如何设置三个绑定:运行时视图模型,主窗口和其实例的设计时视图模型以及UserControl独立的设计时视图模型。

2个回答

7
我认为你过于复杂化了事情:
这样做有什么问题吗:
<Grid IsSharedSizeScope="True">
   <ScrollViewer Margin="8" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True">
      <ItemsControl x:Name="GraphItemsControl" Margin="8"  ItemsSource="{Binding VibeEvents, Mode=OneTime}">
         <ItemsControl.ItemTemplate>
            <DataTemplate>
               <MyShinyUserControl DataContext={Binding}/>
            </DataTemplate>
         </ItemsControl.ItemTemplate>
      </ItemsControl>
   </ScrollViewer>
</Grid>

将每个VibeEvent绑定到用户控件的DataContext中。我建议在用户控件本身中创建一个设计时的DataContext,以便更容易地进行设计。设计时的DataContext如下:

<UserControl x:Class="EMC.Windows.AlarmsModule.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:AlarmsModule="clr-namespace:EMC.Windows.AlarmsModule" d:DesignHeight="300"
    d:DesignWidth="300"
             d:DataContext="{d:DesignInstance Type=AlarmsModule:Alarm}"
    >

这将让您能够构建用户控件并在其中使用设计时数据。而且这很简单,不需要太多的脚手架。


太好了,谢谢Faster Solutions。我希望答案比问题简单!不过,我想要一个符合MVVM Light的做事方式的答案。d:DataContext并不是它(我认为),尽管看起来很直观。 - dumbledad
你所说的遵循MVVM Light是指自动使用它的设计时数据机制吗?这样做会面临一个问题,即你的UserControl要从ViewModels中的集合属性中的对象获取数据。而UserControl的DataContext不会绑定到由SimpleIoc生成的对象。 - Faster Solutions
我将把这个标记为答案,并基于此发布一个完整的示例作为单独的答案,我们将看看是否有人能够阐明在使用MVVM Light在Blend中设计控件时,d:DataContext是将视图模型绑定到用户控件的数据上下文的预期方式。再次感谢。 - dumbledad

4

根据Faster Solutions' answer,这是我能想到的使用UserControl在MVVM Light中显示列表内容的最简单示例。

为了完整起见,我将包括所有代码,尽可能缩短代码长度,同时仍提供与用户控件视图模型和主视图模型中运行时数据不同的设计时数据。

首先是定位器,VMUCExample/ViewModel/ViewModelLocator.cs:

using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;

namespace VMUCExample.ViewModel
{
    public class ViewModelLocator
    {
        static ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
            SimpleIoc.Default.Register<MainViewModel>();
            SimpleIoc.Default.Register<ASquareViewModel>();
        }

        public ASquareViewModel ASquare
        {
            get
            {
                return ServiceLocator.Current.GetInstance<ASquareViewModel>();
            }
        }

        public MainViewModel Main
        {
            get
            {
                return ServiceLocator.Current.GetInstance<MainViewModel>();
            }
        }

        public static void Cleanup() {}
    }
}

我没有使用MVVM Light的数据服务端,部分原因是为了保持简单。实时数据和设计时间数据之间的可见区别由两个视图模型类处理。

VMUCExample/ViewModel/ASquareViewModel.cs:

using System.Windows.Media;
using GalaSoft.MvvmLight;

namespace VMUCExample.ViewModel
{
    public class ASquareViewModel : ViewModelBase
    {
        private Brush _SquareColour;
        public Brush SquareColour
        {
            get
            {
                return _SquareColour ?? (_SquareColour = IsInDesignModeStatic ?
                    new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0x78, 0x78)) : // FF7878 (pastel red)
                    new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xBB, 0x78))); // FFBB78 (nectarine)
            }
            set { _SquareColour = value; }
        }
    }
}

在Expression Blend中查看用户控件时,我看到一个简单的矩形,填充色为柔和的红色:

Blend screenshot with the user control open for editing

主视图模型位于文件VMUCExample/ViewModel/MainViewModel.cs中:
using System.Collections.Generic;
using System.Windows.Media;
using GalaSoft.MvvmLight;

namespace VMUCExample.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        private List<ASquareViewModel> _Squares;
        public List<ASquareViewModel> Squares
        {
            get
            {
                if (_Squares == null)
                {
                    _Squares = new List<ASquareViewModel>();
                    var colour = IsInDesignModeStatic ?
                        new SolidColorBrush(Color.FromArgb(0xFF, 0x78, 0xB2, 0xFF)) : // 78B2FF (pastel blue)
                        new SolidColorBrush(Color.FromArgb(0xFF, 0xF9, 0xFF, 0xC7)); // F9FFC7 (eggshell)
                    for (var i = 0; i < 10; i++)
                    {
                        _Squares.Add(new ASquareViewModel {SquareColour = colour});
                    }
                }
                return _Squares;
            }
            set { _Squares = value; }
        }
        public MainViewModel() {}
    }
}

这个视图也可以在Expression Blend中进行编辑,但是视图模型代码会以不同的方式设置设计时颜色数据:

Blend screenshot with the main window open for editing

这是两个XAML文件,首先是VMUCExample/ASquareUC.xaml:
<UserControl x:Class="VMUCExample.ASquareUC"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" d:DesignHeight="15" d:DesignWidth="60"
             d:DataContext="{Binding ASquare, Mode=OneWay, Source={StaticResource Locator}}">
    <Grid>
        <Rectangle Fill="{Binding SquareColour}" Margin="2" Width="50" Height="10"/>
    </Grid>
</UserControl>

您可以看到,我使用了Faster Solutions的建议,将d:DataContext放置在那里,这样当我设计用户控件时需要表达式混合所需的设计时绑定不会阻塞运行时所需的数据上下文,也不会阻塞在表达式混合中设计主窗口时所需的父级提供的数据上下文。但我对此感到不舒服,因为这不是Paul Stovell的选项6:XAML视图模型定位器被@LBugnion支持的MVVM Light所表征的方法。
另一个视图文件是VMUCExample\ MainWindow.xaml:
<Window x:Class="VMUCExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vmucExample="clr-namespace:VMUCExample"
        Height="200" Width="100"
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Grid x:Name="LayoutRoot">
        <ScrollViewer ScrollViewer.CanContentScroll="True">
            <ItemsControl ItemsSource="{Binding Squares}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <vmucExample:ASquareUC/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl> 
        </ScrollViewer>
    </Grid>
</Window>

示例应用程序本身只是在一列中绘制了十个矩形:

Screenshot of the simple test app

虽然很简单,但希望能展示三个潜在数据集的使用:

  1. 用户控件的设计时间(#FF7878淡红色),
  2. 主窗口设置的用户控件的设计时间(#78B2FF淡蓝色),以及
  3. 主窗口设置的用户控件运行时(#F9FFC7蛋壳色)。

(注意:还有另一种数据选项,即运行时未由主窗口设置的用户控件。在这种情况下,用户控件的视图模型选择 #FFBB78/nectarine,但我不需要它来探索这些绑定。)

为了完整起见,这是VMUCExample\App.xaml文件:

<Application x:Class="VMUCExample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:VMUCExample.ViewModel"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             StartupUri="MainWindow.xaml"
             mc:Ignorable="d">
    <Application.Resources>
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
    </Application.Resources>
</Application>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接