【发布时间】:2014-09-26 11:17:00
【问题描述】:
我正在使用 Visual Studio 2013 的设计器在 WPF 中创建我的用户控件,并且我正在使用 MVVM 方法。
我正在尝试找到对我的视图模型进行“设计时”设置的最佳方法,以便我立即看到设计器更改属性值的效果。我使用了不同的设计和技术来支持这一点,但没有什么是我想要的。我想知道是否有人有更好的想法......
情况(简体): 所以我有一个“设备”,我想要一个用户控件来显示状态和操作。从上到下:
- 我有一个 IDeviceModel,它有一个字段
bool IsConnected {get;}(以及适当的状态更改通知) - 我有一个实现 IDeviceModel 的 FakeDeviceModel,因此我可以不依赖真实设备进行设计和测试
- 一个DeviceViewModel,它包含一个IDeviceModel,并封装了模型的属性。 (是的,它有适当的 INotifyPropertyChanged 通知)
- 我的 UserControl 将有一个 DeviceViewModel 类型的 DataContext,并有一个自定义样式的 CheckBox,它是
IsChecked={Binding IsConnected, Mode=OneWay - 我的目标:我想在设计时预览模型的 IsConnected 状态如何影响我的 UserControl(因此它可能会影响 IsChecked 之外的其他内容)
框架:
- 我使用 MVVM Light ViewModelLocator 的想法,返回非静态字段(因此是 ViewModel 的新实例)。在运行时,真正的数据上下文将由实例化此 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;
}
}
我尝试过的解决方案:
编译时解决方案
只需在 ViewModelLocator 中编写 ViewModel 的设置代码。
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 中拥有的实例,我可以修改布尔值,我会得到关于它如何影响我的 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();
}
}
优点: - 效果超级好!当 DataContext 的绑定请求 ViewModel 时,我利用 Converter 修改该 ViewModel 并在返回之前注入我的设备
缺点:
我们失去了智能感知(使用 ReSharper),因为他不知道转换器返回什么类型
我还有什么其他想法或修改可以解决这个问题吗?
【问题讨论】:
-
你在 Blend 中试过吗?使用 Visual Studio 2010,在 Blend 中使用此模式比在 Visual Studio 中更容易,因为设计器更强大。我不确定 Blend 与 VS2013 相比如何。
-
你在 Blend 中是如何处理这个问题的?
-
VS 和 Blend 现在共享同一个设计器,但无论如何功能并不完全相同。尽管 VS 不再使用苹果酒设计器,但您现在仍然可以在 VS 中做很多您可以/可以做的事情。
标签: wpf mvvm visual-studio-2013 blend