在运行时和设计时将ViewModels分配给UserControls

时间:2012-12-11 19:12:57

标签: wpf mvvm wpf-controls mvvm-light

我正在使用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 Instantiation Approaches(选项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://stackoverflow.com/a/3334780/575530中,akjoshi建议主ViewModel保存UserControl的ViewModel实例;但是当我设计UserControl本身时,它是如何工作的?

  2. https://stackoverflow.com/a/9910298/575530 tam中指出“你想让你的datacontext保持打开并可用于绑定你正在使用这个控件的控件”,并在下面的注释中SoMoS补充说,需要“在ViewModel中为绑定属性创建属性,当有人想要更改控件的一个属性时(如启用了一些子控件),他必须通过View Model“。这是有希望的,但我不知道该怎么做来代替MainViewModel的可绑定的UserControlViewModel集合。

  3. https://stackoverflow.com/a/6340668/575530中,Ehsan Ershadi表示“对于UserControles使用MVVM Light ViewModelLocator并不是一个好主意,因为它是一个静态属性,当你要实例化用户控件的多个实例时,将拥有相同的常见ViewModel,因此它们都是相同的,如果您决定在整个项目中使用它一次,这不是我们想要的UserControl。“然后声明“要解决这个问题,你需要通过使所有属性非静态来修改ViewModelLocator”。我不确定这对我有什么帮助。

  4. https://stackoverflow.com/a/2637830/575530之后的评论中,Jon Mitchell提到“看起来MVVM看起来并不适合创建用户控件”。我希望这不对。

  5. 相比之下,在When should I use a UserControl instead of a Page?中,dthrasher提到“许多WPF MVVM框架似乎都避免使用NavigationWindow和Page控件来支持使用嵌套的UserControls组合页面”,即UserControls是普通的设备在MVVM中。

  6. https://stackoverflow.com/a/1798649/575530中,Reed Copsey提醒沙箱“UserControls总是可以通过暴露属性和使用DataBinding与其控件进行通信。这非常好,因为它在所有方面都保留了MVVM样式。”并且“包含控件可以使用属性将两个用户控件上的两个属性链接在一起,同样保留干净的边界”但是当我在Expression Blend中设计UserControl时,我再也看不到这有什么帮助。

    < / LI>
  7. Should I be using UserControls for my Views instead of DataTemplates? Rachel提到偶尔使用Expression Blend设计UserControl,然后将代码剪切并粘贴到DataTemplate中:“如果我想用它来设计DataTemplate,我通常会创建一个新的UserControl,按我想要的方式设计它,然后将内容复制/粘贴到DataTemplate“

  8. 对这篇文章长度问题感到抱歉!我很困惑如何在设计一个UserControl时使用MVVM Light,注定是MainWindow上集合中项目的可视化,特别是如何设置三个绑定:运行时视图模型,主要设计时间视图模型窗口及其用户控件的实例化,以及用户控件的设计时间视图模型。

2 个答案:

答案 0 :(得分:6)

我认为你的事情过于复杂:

出了什么问题:

<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,以使设计更容易。 Design-Time 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}"
    >

这可以让您到达可以构建用户控件并在其中包含设计时数据的位置。它很简单,并且不需要太多(如果有的话)脚手架。

答案 1 :(得分:4)

基于Faster Solutions' answer这是最简单的例子,我可以使用UserControl在MVVM Light中显示列表的内容。

为了完整起见,我将尽可能简短地包含我尝试制作的所有代码,同时仍然提供与用户控件的视图模型和主视图模型中的运行时数据不同的设计时数据。 p>

首先是定位器,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 /视图模型/ 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,以便在设计用户控件时我需要设计时绑定Expression Blend并不会阻止我需要的数据上下文在我在Expression Blend中设计主窗口时,在运行时和父级提供的数据上下文。我对此感到不舒服,这不是Paul Stovell的Option 6: A XAML View Model Locatorendorsed by @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 eggshell)。
  4. (NB还有另一个数据选项,用户控件的运行时间不是由主窗口设置的。在这种情况下,用户控件的视图模型选择#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>