我正在使用Visual Studio 2013的设计器在WPF中创建用户控件,而我正在使用MVVM方法。
我正在尝试找到设置我的viewmodel的“Design-Time”的最佳方法,以便我立即看到设计师在更改属性值时的效果。我使用了不同的设计和技术来支持这一点,但没有什么是我想要的。我想知道是否有人有更好的想法...
情况(简化): 所以我有一个“设备”,我希望UserControl显示状态和操作。从上到下:
bool IsConnected {get;}
(以及状态变化的正确通知)IsChecked={Binding IsConnected, Mode=OneWay
框架:
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();
}
}
}
}
优点:
缺点:
它停止了在另一个项目中工作,而这本身就找不到原因。但是在重新编译和更改内容之后,设计师会给我一些异常,看起来像“无法施放”FakeDeviceModel“到”FakeDeviceModel“”!!我的猜测是Designer内部编译并使用这些类型的缓存(C:\ Users \ firstname.lastname \ AppData \ Local \ Microsoft \ VisualStudio \ 12.0 \ Designer \ ShadowCache)。在我的解决方案中,根据事物的顺序,我创建了一个分配给静态实例的“FakeDeviceModel”,并且“稍后”,下次ViewModelLocator被要求提供ViewModel时,它会使用它实例。但是,如果在此期间他“重新编译”或使用不同的缓存,那么它不是“完全”相同的类型。所以我不得不杀死设计师(XDescProc)并重新编译它才能工作,然后几分钟后再次失败。如果有人可以纠正我,那就太好了。
d:DataContext和自定义转换器的多重绑定
之前的解决方案的问题是指向ViewModel和FakeDeviceModel是在不同的时刻创建(给出类型/强制转换问题)并解决它的事实,我需要同时创建它们< / p>
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();
}
}
优点: -Works超级棒!当DataContext的绑定请求ViewModel时,我利用Converter修改ViewModel并在返回之前注入我的设备
缺点:
我们失去了intelissense(使用ReSharper),因为他不知道转换器返回的是什么类型
我可以为解决这个问题做出任何其他想法或修改吗?
答案 0 :(得分:4)
您可以创建一个设计时ViewModel,它返回IsConnected = true(FakeDeviceViewModel)并将其设置为设计时数据上下文:
d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel,
IsDesignTimeCreatable=True}"
答案 1 :(得分:1)
我想提出一个替代解决方案。
您可以将相同的视图模型用于设计时数据和正常运行时,并检查您的(单个)视图模型是否设计器处于活动状态,然后在那里加载设计时数据。
在您的视图模型中,您可以执行以下操作:
public class ExampleViewModel : ViewModelBase
{
public ExampleViewModel()
{
if (IsInDesignMode == true)
{
LoadDesignTimeData();
}
}
private void LoadDesignTimeData()
{
// Load design time data here
}
}
IsInDesignMode
属性可以放在视图模型基类中 - 如果有的话 - 看起来像这样:
DesignerProperties.GetIsInDesignMode(new DependencyObject());
请查看我的回答here
答案 2 :(得分:1)
这是我使用MVVMLight进行的项目之一。
创建一个具有两个静态方法的静态类-一个用于在IOC容器中注册运行时服务,另一个用于在IOC容器中注册设计时服务。我使用相同的SimpleIOC.Default容器。在绑定到其接口的两种方法中注册适当的视图模型。
public static class MyServiceLocator()
{
public static void RegisterRunTimeServices()
{
ServiceLocator.SetLocatorProvider(() => SimpleIOC.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<IAboutViewModel, AboutViewModel>();
}
public static void RegisterDesignTimeServices()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<IAboutViewModel, DesignTimeAboutViewModel>();
}
在ViewModelLocator的构造函数中,检查应用程序是否处于DesignMode并相应地调用静态方法以注册服务。
public ViewModelLocator()
{
if (ViewModelBase.IsInDesignModeStatic)
{
MyServiceLocator.RegisterDesignTimeServices();
}
else MyServiceLocator.RegisterRunTimeServices();
}
现在,您的View只需将datacontext设置为对应的viewmodel接口,而不是viewmodel对象。为此,不要公开ViewModel对象到ViewModelLocator的每个视图中,而是公开viewmodel接口。
在ViewModelLocator.cs
public IAboutViewModel About
{
get
{
return ServiceLocator.Current.GetInstance<IAboutViewModel>();
}
}
在AboutView.xaml中
DataContext="{Binding Source={StaticResource Locator}, Path=About}"
在代码中需要的地方,请将接口强制转换为ViewModelBase类型,以将其转换为ViewModel对象并使用。
在MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private readonly IAboutViewModel _aboutViewModel;
public MainViewModel()
{
_aboutViewModel = ServiceLocator.Current.GetInstance<IAboutViewModel>();
CurrentViewModel = (ViewModelBase) _aboutViewModel;
}
}
因此,基本上,我使用DI向每个视图注入适当的视图模型,具体取决于代码是在运行时还是在设计时。 ViewModelLocator只是在SimpleIOC容器中注册设计时或运行时视图模型。这样做的好处是,视图模型文件中不会混合任何代码,而且还可以为多个设计时数据设置代码而不会产生太多干扰。如果您希望在应用程序运行时显示设计时数据,那么只需一行代码更改即可。
答案 3 :(得分:0)
我已经详细记录了我是如何设法在Visual Studio中查看实时设计时数据的完美设置。
在此页面上查看Hint 9 - Design Time DataContext
:
ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"