MVVM Light with UserControl library

Aug 5, 2014 at 7:40 PM
Hello,

I have a few questions regarding MVVM Light and UserControls.

I have created a UserControls Library as we use a few of our controls in different applications with identical functionality. I've started using MVVM Light recently, but I'm not entirely sure how to integrate it.

Right now, I have the MVVM Light Libraries in my UserControls library, because I use the DispatcherHelper, RelayCommands, and etc... but I don't have the ModelViewLocator. I'm getting my view models like below:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             ...
             x:Class="SubC_Controls.Views.CameraSerial" 
             xmlns:Converters="clr-namespace:SubCTools.Converters;assembly=SubCTools"
             xmlns:Extenders="clr-namespace:SubCTools.Controls.WPF.Extenders"
             xmlns:Helpers="clr-namespace:SubCTools.Controls.WPF.Helpers"
             xmlns:VM="clr-namespace:SubCTools.Controls.WPF.ViewModels">
    <UserControl.Resources>
        <Converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
        <Extenders:ListBoxExtenders x:Key="ScrollToEnd"/>
    </UserControl.Resources>
    UserControl.DataContext>
        <VM:SerialVM/>
    </UserControl.DataContext>
...
I have a lot of shared dependencies among view models, I'm only using one serial object for example, so when I input the UserContol in to an application, I set its DataContext by with the following:
<Views:CameraSerial DataContext="{Binding SerialVM, Source={StaticResource Locator}}"/>
I have two constructors in my View Model, one default so it shows up in UserControl Library, and one preferred so SimpleIoc can call the right one:
public SerialVM()
            :this(new SubCSerial(), 
            new MessageBoxDialogService(), 
            new SubCSave("test"))
        { }

        [PreferredConstructor]
        public SerialVM(ISubCSerial subCSerial, 
            IDialogService dialog,
            ISettingsService settings)
            : base(subCSerial, dialog, settings)
        {
...
So, it seems what's happening is my empty constructor is being called and creating my save file with the name "test", and then the second constructor is being called from my application and SimpleIoc is injecting all the correct dependencies.

Should I have a ViewModelLocater in my UserControls library, and get my View Models from the Ioc? Will take conflict with having another SimpleIoc in my application?

When I remove my default constructor, my UserControl designer complains: Error 4 "'SubCTools.Controls.WPF.ViewModels.SerialVM' does not contain a constructor that takes 0 arguments"

And when I remove my DataContext from my UserControl view, and try to set it from my application with SimpleIoc I get: "'The invocation of the constructor on type 'SubC_Controls.Views.CameraSerial' that matches the specified binding constraints threw an exception.'"

I would certainly appreciate some guidance, thank you.
Aug 7, 2014 at 12:15 AM
I ended up adding a view model locator to my UserControl library and setting up my Locator as a static reference. The Views show up in my library just fine, but whenever I add them to my application, I get a red box with a red X in the center. Error message is: Exception: Cannot create an instance of "DeviceControl".

When I run my application however, it seems to be working fine.
Aug 7, 2014 at 6:11 AM
Hi,
I think I had once the red X as well, but can't recall the reason any more. You might have to debug the design time to find out. So I am not really sure about the problem, but I also struggled a bit till I got it running as expected. Below a sample of what I have:

IoC
    public class ViewModelLocator
    {
        /// <summary>
        /// Initializes a new instance of the ViewModelLocator class.
        /// </summary>
        public ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            if (ViewModelBase.IsInDesignModeStatic)
            {
                //Create design time view services and models
                SimpleIoc.Default.Register<IIoService, IoServicesDesign>();
            }
            else
            {
                // Create run time view services and models
                SimpleIoc.Default.Register<IIoService, IoServices>();
            }

            SimpleIoc.Default.Register<MainViewModel>();
            SimpleIoc.Default.Register<ContentViewModel>();
        }

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

        public ContentViewModel ContentViewModel
        {
            get { return ServiceLocator.Current.GetInstance<ContentViewModel>(); }
        }
        //Add some more here
    }
and in App.xaml add this line
    <Application.Resources>
        <tool:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />

        <!-- Add this if you have a black themein VS in order to read black written text -->
        <Style TargetType="{x:Type UserControl}">
            <Style.Triggers>
                <Trigger Property="componentModel:DesignerProperties.IsInDesignMode" Value="true">
                    <Setter Property="Background" Value="WhiteSmoke" />
                </Trigger>
            </Style.Triggers>
        </Style>

    </Application.Resources>
View
<UserControl x:Class="Tool.View.ContentView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             DataContext="{Binding ContentViewModel,
                                   Mode=OneWay,
                                   Source={StaticResource Locator}}"
             d:DesignHeight="200"
             d:DesignWidth="300"
             mc:Ignorable="d">
...
and in Code (View), just to make sure all was init propertly
    public partial class ContentView
    {
        public ContentView()
        {
            InitializeComponent();

            ViewModel = DataContext as ContentViewModel;

            if (ViewModel == null)
            {
                System.Diagnostics.Debugger.Break();
            }
       }

        /// <summary>
        /// This gets or sets the ViewModel, more used for test purposes.
        /// </summary>
        public ContentViewModel ViewModel { get; set; }
    }
and finally the ViewModel
    public class ContentViewModel : ViewModelBase, IDataErrorInfo
    {
        private readonly IIoService _ioService;

        public ContentViewModel(IIoService ioService)
        {
            _ioService = ioService;
            MyProperty = _ioService.GetData();

            if (IsInDesignMode)
            {
                //Get stuff for DesignTime
            }
            else
            {
                //Get stuff for RunTime
            }
        }

        //Your code
    }
One thing to add: if you call the same instance twice you might get an error, so you need to unregister it first. This needs a fix first as discussed here:
https://mvvmlight.codeplex.com/discussions/483764

Hope this helps