14

I'm using Visual Studio 2013's designer to create my User Control in WPF, and I'm using a MVVM approach.

I'm trying to find the best way to have "Design-Time" setup of my viewmodel so that I immediatly see the effect in the designer of changing a value of a property for instance. I've used different designs and techniques to support this, but nothing is exactly what I want. I'm wondering if someone has better ideas...

Situation (simplified): So I have a "Device" which I want a UserControl to show states and operations. From top to bottom:

  • I have a IDeviceModel which has a field bool IsConnected {get;} (and proper notification of state changes)
  • I have a FakeDeviceModel which implements IDeviceModel, and thus enables me to not rely on a real device for design-time and testing
  • A DeviceViewModel, which contains a IDeviceModel, and encapsulate the model's properties. (yes it has proper INotifyPropertyChanged notifications in it)
  • My UserControl which will have a DataContext of type DeviceViewModel, and would have a custom-styled CheckBox which is IsChecked={Binding IsConnected, Mode=OneWay
  • My Goal: I want to preview on design time how does the Model's IsConnected state affect my UserControl (So it could affect other things than just IsChecked)

Framework:

  • I use the idea of the MVVM Light ViewModelLocator, returning non-static fields (so new instances of ViewModels). At runtime, the real datacontext will be given by the one instanciating this 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;
        }
    }

Solutions I tried:

Compile-Time solution

Simply code the setup of the ViewModel in the ViewModelLocator.

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

Pros: Simple

Cons: That's longer iterations of always going to change the value in code, recompile, go back to designer view, wait for result

Instance in resources and kept static in ViewModelLocator

So I create an instance in XAML and I try to push it in the current ViewModel used by the designer. Not the cleanest way, but worked for a while in simple situation (yes there's some wierdness with the collection, but was with the idea that I could have multiple devices and a current one)

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...]

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

Pros:

  • Worked super great on one project. the instance that I had in XAML, I could modify the booleans and I would get -immediate- feedback on how it affects my UserControl. So in the simple situation, the CheckBox's "Checked" state would change and I could modify my styling in real-time, without needing to recompile

Cons:

It stopped working in another project, and this by itself I couldn't find the reason why. But after recompiling and changing stuff, the designer would give me exceptions looking like "Cannot cast "FakeDeviceModel" to "FakeDeviceModel""!! My guess is that the Designer internally compiles and uses caches for those types (C:\Users\firstname.lastname\AppData\Local\Microsoft\VisualStudio\12.0\Designer\ShadowCache). And that in my solution, depending on the ordering of things, I was creating a "FakeDeviceModel" which was assigned to a static instances, and "later on", the next time the ViewModelLocator would be asked for a ViewModel, it would use that instance. However, if in the meantime he "recompiles" or uses a different cache, then it's not "exactly" the same type. So I had to kill the designer (XDescProc) and recompile for it to work, and then fail again a few minutes after. If someone can correct me on this it would be great.

Multi-Binding for d:DataContext and custom converter

The previous solution's problem was pointing me to the fact that the ViewModel and the FakeDeviceModel were created at different moment in time (giving the type/cast problem) and to solve it, I would need to create them at the same time

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

Pros: -Works super nice! When the binding for the DataContext asks for the ViewModel, I take advantage of the Converter to modify that ViewModel and inject my device before returning it

Cons:

We lose intelissense (with ReSharper), since he doesn't know what type is returned by the converter

Any other ideas or modifications I could make to solve this issue?

FrankyB
  • 697
  • 6
  • 16
  • Have you tried this in Blend? With Visual Studio 2010, it was easier to work with this pattern in Blend than Visual Studio because the designer was more robust. I am not sure how Blend compares to VS2013. – Anthony Brien Sep 30 '14 at 19:15
  • How were you handling this in Blend? – FrankyB Oct 01 '14 at 08:23
  • VS and Blend share the same designer now, though not all the same functionality by any means. Though VS no longer uses the cider designer you can still do a lot of what you can/could in blend in VS now. – Chris W. Oct 07 '14 at 20:00
  • Possible duplicate of [How to see design-time data-binding in XAML editor (it works in runtime)?](https://stackoverflow.com/questions/16401885/how-to-see-design-time-data-binding-in-xaml-editor-it-works-in-runtime) – StayOnTarget Apr 09 '19 at 12:04

5 Answers5

9

You may create a design time ViewModel that returns IsConnected = true based on your view mode (FakeDeviceViewModel) and then set it as a design-time data context:

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

Where viewModels: is the xaml namespace to the actual view model.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
IUnknown
  • 335
  • 1
  • 11
  • The `d` namespace may not be added automatically. It is added when looking at Visual Studio's Document Viewer and turning off controls in design time, but also when working in Blend. Hence the full namespace is `xmlns:d="http://schemas.microsoft.com/expression/blend/2008"` – ΩmegaMan Dec 11 '19 at 15:28
1

I've documented exactly how I managed to get the perfect setup to see live Design Time data in Visual Studio.

Look at Hint 9 - Design Time DataContext on this page:

ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"

Community
  • 1
  • 1
Contango
  • 76,540
  • 58
  • 260
  • 305
1

I would like to propose an alternative solution.

You could use that same view model for design time data and normal runtime, and check in your (single) viewmodel whether the designer is active and then load the design time data there.

In your view model you would do something like this:

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

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

The IsInDesignMode property could be placed in your view model base class - if you have one - and looks like this:

DesignerProperties.GetIsInDesignMode(new DependencyObject());

Please take a look at my answer here

Community
  • 1
  • 1
Martin
  • 5,165
  • 1
  • 37
  • 50
1

This is how I am doing it one of my projects using MVVMLight.

  1. Create interface for every viewmodel for which you need separate design time and run time properties and behavior.
  2. Make separate viewmodels for every view - one for run time and another for design time. Derive both viewmodels from the same interface defined above.
  3. Create a static class that has two static methods - one for registering services for run time in the IOC container and another for registering services for design time in the IOC container. I use the same SimpleIOC.Default container. Register appropriate viewmodels in both the methods bound to their interfaces.

    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>();
        }
    
  4. In constructor of ViewModelLocator, check if the app is in DesignMode and accordingly call the static method to register services.

    public ViewModelLocator()
    {
        if (ViewModelBase.IsInDesignModeStatic)
        {
            MyServiceLocator.RegisterDesignTimeServices();
        }
        else MyServiceLocator.RegisterRunTimeServices();
    }
    
  5. Now, your Views just have to set datacontext as corresponding viewmodel interface instead of viewmodel object. To do that, instead of exposing viewmodel objects to each view from the ViewModelLocator, expose viewmodel interface.

    In ViewModelLocator.cs

    public IAboutViewModel About
    {
        get
        {
            return ServiceLocator.Current.GetInstance<IAboutViewModel>();
        }
    }
    

    In AboutView.xaml

    DataContext="{Binding Source={StaticResource Locator}, Path=About}"
    
  6. Wherever needed in code, cast the interface to ViewModelBase type to convert it to ViewModel object and use.

    In MainViewModel.cs

    public class MainViewModel : ViewModelBase
    {
        private readonly IAboutViewModel _aboutViewModel;
    
        public MainViewModel()
        {
            _aboutViewModel = ServiceLocator.Current.GetInstance<IAboutViewModel>();
            CurrentViewModel = (ViewModelBase) _aboutViewModel;
        }
    }
    

So, basically I use DI to inject appropriate viewmodels to each view depending on whether the code is in run time or design time. The ViewModelLocator simply registers either design time or run time viewmodels in the SimpleIOC container. The benefit of this is that there is no mixing of code in viewmodel files and one can also setup the code for multiple design time data without much interference. If you want designtime data to show while the application runs, then also its possible with one line change in code.

bedunadain
  • 73
  • 1
  • 7
0
  1. I would create an instance of Fake View Model in a separate xaml e.g. DeviceViewModelDataSample.xaml (see example below)

  2. Set Build Action to DesignData

  3. Reference the file as such

       <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>