Настройка времени создания ViewModel

Я использую конструктор Visual Studio 2013 для создания моего User Control в WPF, и я использую подход MVVM.

Я пытаюсь найти лучший способ настройки "Design-Time" моей модели просмотра, чтобы сразу увидеть эффект в дизайнере, изменяя значение свойства, например. Я использовал разные конструкции и методы для поддержки этого, но ничего не хочу, чего я хочу. Мне интересно, есть ли у кого-то лучшие идеи...

Ситуация (упрощенная): Поэтому у меня есть "Устройство", которое я хочу, чтобы UserControl отображал состояния и операции. Сверху вниз:

  • У меня есть IDeviceModel, у которого есть поле bool IsConnected {get;} (и правильное уведомление о изменениях состояния)
  • У меня есть FakeDeviceModel, который реализует IDeviceModel и, таким образом, позволяет мне не полагаться на реальное устройство для разработки и тестирования
  • DeviceViewModel, который содержит IDeviceModel и инкапсулирует свойства модели. (да, в нем есть соответствующие уведомления INotifyPropertyChanged)
  • My UserControl, который будет иметь DataContext типа DeviceViewModel и будет иметь настраиваемый CheckBox, который является IsChecked={Binding IsConnected, Mode=OneWay
  • Моя цель: я хочу просмотреть время разработки, как состояние модели 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;
        }
    }

Решения, которые я пробовал:

Решение Compile-Time

Просто скопируйте настройку ViewModel в ViewModelLocator.

var deviceViewModel = new DeviceViewModel(fakeDeviceModel);
var fakeDeviceModel = new FakeDeviceModel();
fakeDeviceModel.IsConnected = true;
deviceViewModel.AddDevice(fakeDeviceModel);

Плюсы: Простой

Минусы: длительные итерации всегда меняют значение в коде, перекомпилируют, возвращаются к представлению дизайнера, ждут результата

Экземпляры в ресурсах и сохраняются в 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...]

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

Плюсы:

  • Работал отлично по одному проекту. экземпляр, который я имел в XAML, я мог бы изменить логические значения, и я бы получил -immediate-feedback о том, как это влияет на мой UserControl. Поэтому в простой ситуации состояние CheckBox "Checked" изменилось бы, и я мог бы изменить свой стиль в режиме реального времени, не перекомпилируя

Минусы:

Он перестал работать в другом проекте, и это само по себе я не мог найти причину. Но после перекомпиляции и изменения материала дизайнер дал бы мне исключения, похожие на "Can not cast" FakeDeviceModel "на" FakeDeviceModel "! Я предполагаю, что дизайнер внутренне компилирует и использует кеши для этих типов (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();
    }
}

Плюсы: -Очень классно! Когда привязка для DataContext запрашивает ViewModel, я использую конвертер для изменения этого ViewModel и вставляю свое устройство, прежде чем возвращать его

Минусы:

Мы теряем intelissense (с ReSharper), так как он не знает, какой тип возвращается конвертером

Любые другие идеи или модификации, которые я мог бы решить, чтобы решить эту проблему?

Ответ 1

Вы можете создать ViewModel времени разработки, который возвращает IsConnected = true (FakeDeviceViewModel) и установить его как контекст данных времени разработки:

d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel, 
IsDesignTimeCreatable=True}"

Ответ 3

  • Я бы создал экземпляр модели Fake View в отдельном xaml, например. DeviceViewModelDataSample.xaml(см. Пример ниже)

  • Установите действие сборки DesignData​​strong >

  • Ссылка на файл как таковой

       <UserControl x:Class="YourNameSpace.YourControl"
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                    mc:Ignorable="d" 
                    d:DataContext="{d:DesignData Source=/DataSample/DeviceViewModelDataSample.xaml}">
    <!-- Skiped details for brevity -->   
    </UserControl>
    

DeviceViewModelDataSample.xaml

<vm:DeviceViewModel xmlns:dm="clr-namespace:YourNameSpace.DataModel" 
                xmlns:vm="clr-namespace:YourNameSpace.ViewModel">
   <vm:DeviceViewModel.DeviceManager> <!-- Assuming this is a collection -->
        <dm:DeviceModel DeviceName="Fake Device" IsConnected ="true" /> <!-- This creates an instance at design time -->
   </vm:DeviceViewModel.DeviceManager>    
</vm:DeviceViewModel>

Ответ 4

Я хотел бы предложить альтернативное решение.

Вы можете использовать ту же модель представления для данных времени разработки и обычной среды выполнения и проверить свою (единую) модель представления, активен ли конструктор, а затем загружать там данные времени разработки.

В вашей модели просмотра вы сделаете что-то вроде этого:

public class ExampleViewModel : ViewModelBase
{
    public ExampleViewModel()
    {
        if (IsInDesignMode == true)
        {
            LoadDesignTimeData();
        }
    }

    private void LoadDesignTimeData()
    {
        // Load design time data here
    }       
}

Свойство IsInDesignMode может быть помещено в ваш базовый класс модели просмотра - если он у вас есть - и выглядит так:

DesignerProperties.GetIsInDesignMode(new DependencyObject());

Пожалуйста, посмотрите мой ответ здесь