ViewModel的设计时设置

Fra*_*kyB 13 wpf blend mvvm visual-studio-2013

我正在使用Visual Studio 2013的设计器在WPF中创建我的用户控件,而我正在使用MVVM方法.

我正在尝试找到设置我的viewmodel"Design-Time"的最佳方法,以便我立即看到设计师在更改属性值时的效果.我使用了不同的设计和技术来支持这一点,但没有什么是我想要的.我想知道是否有人有更好的想法......

情况(简化):所以我有一个"设备",我希望UserControl显示状态和操作.从上到下:

  • 我有一个IDeviceModel,它有一个字段bool IsConnected {get;}(和状态变化的适当通知)
  • 我有一个实现IDeviceModel的FakeDeviceModel,因此我可以不依赖于真实设备进行设计时和测试
  • DeviceViewModel,包含IDeviceModel,并封装模型的属性.(是的,它有正确的INotifyPropertyChanged通知)
  • 我的UserControl将具有类型为DeviceViewModel的DataContext,并且将具有自定义样式的CheckBox,它是 IsChecked={Binding IsConnected, Mode=OneWay
  • 我的目标:我想在设计时预览Model的IsConnected状态如何影响我的UserControl(所以它可能会影响除IsChecked之外的其他事情)

框架:

  • 我使用MVVM Light ViewModelLocator的想法,返回非静态字段(因此ViewModels的新实例).在运行时,真实的datacontext将由实现此UserControl的那个给出

d:DataContext="{Binding DeviceViewModelDesignTime, Source={StaticResource ViewModelLocator}}"

 public class ViewModelLocator
 {
    private static MainWindowViewModel _mainWindowViewModel;
    public MainWindowViewModel MainWindowViewModelMainInstance
    {
        get
        {
            if (_mainWindowViewModel == null)
            {
                _mainWindowViewModel = new MainWindowViewModel();
            }
            return _mainWindowViewModel;
        }
    }

    public DeviceViewModel DeviceViewModelDesignTime
    {
        get
        {
            //Custom initialization of the dependencies here
            //Could be to create a FakeDeviceModel and assign to constructor
            var deviceViewModel = new DeviceViewModel();

            //Custom setup of the ViewModel possible here 
            //Could be: deviceViewModel.Model = new FakeDeviceModel();

            return deviceViewModel;
        }
    }
Run Code Online (Sandbox Code Playgroud)

解决方案我试过:

编译时解决方案

只需在ViewModelLocator中编写ViewModel的设置.

var deviceViewModel = new DeviceViewModel(fakeDeviceModel);
var fakeDeviceModel = new FakeDeviceModel();
fakeDeviceModel.IsConnected = true;
deviceViewModel.AddDevice(fakeDeviceModel);
Run Code Online (Sandbox Code Playgroud)

优点:简单

缺点:总是要更改代码中的值,重新编译,返回设计器视图,等待结果的更长迭代

资源实例并在ViewModelLocator中保持静态

所以我在XAML中创建了一个实例,并尝试将其推送到设计器使用的当前ViewModel中.不是最干净的方式,但在简单的情况下工作了一段时间(是的,这个集合有一些奇怪的东西,但我的想法是我可以拥有多个设备和当前的设备)

XAML:

<UserControl x:Class="Views.StepExecuteView"
         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:DataContext="{Binding DeviceViewModelDesignTime, Source={StaticResource ViewModelLocator}}">
<UserControl.Resources>
    <viewModels:DesignTimeDeviceManager x:Key="DesignTimeDeviceManager">
        <viewModels:DesignTimeDeviceManager.DesignTimeDevices>
            <device:FakeDeviceModel IsConnected="True"
                                    IsBusy="False"
                                    IsTrayOpen="True"
                                    NumberOfChipSlots="4"
                                    />
        </viewModels:DesignTimeDeviceManager.DesignTimeDevices>

 [... CheckBox binding to datacontext and so on...]
Run Code Online (Sandbox Code Playgroud)

和ViewModelLocator.cs:

 public class ViewModelLocator
 {
    private static MainWindowViewModel _mainWindowViewModel;
    public MainWindowViewModel MainWindowViewModelMainInstance
    {
        get
        {
            if (_mainWindowViewModel == null)
            {
                _mainWindowViewModel = new MainWindowViewModel();
            }
            return _mainWindowViewModel;
        }
    }

    public static FakeDeviceModel DeviceModelToAddInDesignTime;
    public DeviceViewModel DeviceViewModelDesignTime
    {
        get
        {
            var deviceViewModel = new DeviceViewModel();
            if (DeviceModelToAddInDesignTime != null)
                deviceViewModel.AddDevice(DeviceModelToAddInDesignTime );

            return deviceViewModel;
        }
    }
}

public class DesignTimeDeviceManager
{
    private ObservableCollection<FakeDeviceModel> _DesignTimeDevices;
    public ObservableCollection<FakeDeviceModel> DesignTimeDevices
    {
        get { return _DesignTimeDevices; }
        set
        {
            if (_DesignTimeDevices != value)
            {
                _DesignTimeDevices = value;
                ViewModelLocator.DeviceModelToAddInDesignTime = value.FirstOrDefault();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • 在一个项目上工作得非常好.我在XAML中的实例,我可以修改布尔值,我会得到-immediate-反馈它如何影响我的UserControl.所以在简单的情况下,CheckBox的"Checked"状态会改变,我可以实时修改我的样式,而无需重新编译

缺点:

它停止在另一个项目中工作,这本身就找不到原因.但是在重新编译和更改内容之后,设计师会给我一些异常,看起来像"无法施放"FakeDeviceModel"到"FakeDeviceModel""!! 我的猜测是Designer内部编译并使用这些类型的缓存(C:\ Users\firstname.lastname\AppData\Local\Microsoft\VisualStudio\12.0\Designer\ShadowCache).在我的解决方案中,根据事物的顺序,我创建了一个分配给静态实例的"FakeDeviceModel",并且"稍后",下次ViewModelLocator被要求提供ViewModel时,它会使用它实例.但是,如果在此期间他"重新编译"或使用不同的缓存,那么它并不是"完全"相同的类型.所以我不得不杀死设计师(XDescProc)并重新编译它才能工作,然后几分钟后再次失败.如果有人可以纠正我这将是伟大的.

d:DataContext和自定义转换器的多重绑定

之前的解决方案的问题是指向ViewModel和FakeDeviceModel是在不同的时刻创建(给出类型/强制转换问题)并解决它的事实,我需要同时创建它们

XAML:

<UserControl x:Class="MeltingControl.Views.DeviceTabView"
         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:UserControl.DataContext>
    <MultiBinding Converter="{StaticResource DeviceDataContextConverter}">
        <Binding Path="DeviceViewModelDesignTime" Source="{StaticResource ViewModelLocator}" />
        <Binding>
            <Binding.Source>
                <device:FakeDeviceModel IsConnected="False"
                                    IsBusy="False"
                                    IsTrayOpen="False"
                                    SerialNumber="DesignTimeSerie"
                                    />
            </Binding.Source>
        </Binding>
    </MultiBinding>
</d:UserControl.DataContext>

public class DeviceDataContextConverter: IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values == null || values.Length == 0)
            return null;

        var vm = (DeviceViewModel)values[0];
        if (values.Length >= 2)
        {
            var device = (IDeviceModel)values[1];
            vm.AddDevice(device);
        }

        return vm;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Run Code Online (Sandbox Code Playgroud)

优点:-Works超级棒!当DataContext的绑定请求ViewModel时,我利用Converter来修改ViewModel并在返回之前注入我的设备

缺点:

我们失去了intelissense(使用ReSharper),因为他不知道转换器返回了什么类型

我可以用来解决这个问题的任何其他想法或修改?

小智 9

您可以创建一个IsConnected = true基于您的视图模式 ( FakeDeviceViewModel)返回的设计时 ViewModel,然后将其设置为设计时数据上下文:

d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel, 
IsDesignTimeCreatable=True}"
Run Code Online (Sandbox Code Playgroud)

viewModels:实际视图模型的 xaml 命名空间在哪里。