Я использую конструктор 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), так как он не знает, какой тип возвращается конвертером
Любые другие идеи или модификации, которые я мог бы решить, чтобы решить эту проблему?