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 在设计时也会出现同样的问题。如果我在 MainWindow 的外观和感觉上使用 Expression Blend,该绑定如何被设计时主 ViewModel 中的集合提供的 UserControl 的 ViewModel 类的实例覆盖?

有许多问题和答案已经涉及到这一点:

  1. https://stackoverflow.com/a/3334780/575530中,akjoshi 建议主 ViewModel 包含 UserControl 的 ViewModel 的实例;但是当我设计 UserControl 本身时,它是如何工作的?

  2. https://stackoverflow.com/a/9910298/575530中,tam 指出“您希望保持数据上下文打开并可用于绑定到您使用此控件的控件”,并且在以下评论中 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. 相反,在什么时候应该使用 UserControl 而不是 Page?dthrasher 提到“许多 WPF MVVM 框架似乎避免使用 NavigationWindow 和 Page 控件,而是使用嵌套的 UserControls 来编写页面”,即 UserControls 是 MVVM 中的常见设备。

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

  7. 我应该为我的视图使用 UserControls 而不是 DataTemplates 吗?Rachel 提到偶尔会在将代码剪切并粘贴到 DataTemplate 之前使用 Expression Blend 来设计 UserControl:“如果我确实想用它来设计 DataTemplate,我通常会创建一个新的 UserControl,按照我想要的方式设计它,然后将内容复制/粘贴到 DataTemplate"

对不起这个论文长度问题!在设计一个注定要成为 MainWindow 上集合中项目的视觉对象的 UserControl 时,我对如何使用 MVVM Light 感到困惑,尤其是如何设置三个绑定:运行时视图模型、主视图的设计时视图模型窗口及其用户控件的实例化,以及用于隔离用户控件的设计时视图模型。

4

2 回答 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}"
    >

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

于 2012-12-12T09:25:18.247 回答
4

基于Faster Solutions 的回答,这是我能想到的最简单的示例,它使用 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 中的用户控件,我看到一个带有柔和红色填充的简单矩形:

将屏幕截图与打开的用户控件混合以进行编辑

主视图模型位于文件 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 中编辑此视图,但视图模型代码设置设计时颜色数据的方式不同:

将屏幕截图与打开的主窗口混合以进行编辑

这是两个 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 Locator中描述为 MVVM Light并得到@LBugnion认可的方法

另一个视图文件是 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>

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

简单测试应用的屏幕截图

太简单了,我知道,但希望能展示三个正在使用的潜在数据集:

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

(注意还有另一个数据选项,主窗口未设置用户控件的运行时间。在这种情况下,用户控件的视图模型选择#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>
于 2012-12-12T11:53:01.997 回答