Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning ViewModels to UserControls at runtime and at design time

I am writing some data visualization code in WPF with MVVM Light. Here's a fragment:

    <Window x:Class="EventBlockVisualization.MainWindow"
            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:ignore="http://www.ignore.com"
            Title="MainWindow"
            mc:Ignorable="d ignore"
            DataContext="{Binding Main, Source={StaticResource Locator}}">
        <Window.Resources>
            <ItemsPanelTemplate x:Key="GraphRowItemsPanelTemplate">
                <StackPanel IsItemsHost="True" Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </Window.Resources>
        <Grid IsSharedSizeScope="True">
            <ScrollViewer Margin="8" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True">
                <ItemsControl x:Name="GraphItemsControl" Margin="8"  ItemsSource="{Binding VibeEvents, Mode=OneTime}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition SharedSizeGroup="NameWidthSizeGroup" Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock x:Name="NameTextBlock" Text="{Binding Name}" Grid.Column="0" Margin="4,0"/>
                                <ItemsControl x:Name="GraphRowItemsControl" ItemsSource="{Binding VibeEventViewModels, Mode=OneTime}" ItemsPanel="{DynamicResource GraphRowItemsPanelTemplate}"  Grid.Column="1" Margin="4,0">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Grid HorizontalAlignment="Left" VerticalAlignment="Center" Height="10">
                                                <TextBlock x:Name="FGTitleTextBox" Text="{Binding FGTitle}" Visibility="Collapsed"/>
                                                <Button Margin="1,0,0,0" Width="{Binding LengthInSeconds}" HorizontalAlignment="Left" Background="{Binding BackgroundColor}" BorderBrush="#FF2186A1">
                                                    <Button.ToolTip>
                                                        <ToolTip>
                                                            <StackPanel>
                                                                <TextBlock FontWeight="Bold" Text="{Binding FGTitle}"/>
                                                                <TextBlock Text="{Binding LengthText}"/>
                                                            </StackPanel>
                                                        </ToolTip>
                                                    </Button.ToolTip>
                                                </Button>
                                            </Grid>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </Grid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </ScrollViewer>
        </Grid>
    </Window>

I'd like to swap out the central ItemsControl.ItemTemplate DataTemplate and make it a User Control so that I can design it more easily in Expression Blend.

I cannot find a simple sample that includes a User Control in MVVM Light but there are some tutorial articles. For example in MVVM Instantiation Approaches (Option 6) Paul Stovell suggests binding in the UserControl's ViewModel in MVVM Light thus:

<UserControl ...>
    <UserControl.Resources>
        <ViewModelLocator x:Key="ViewModelLocator"/>
    </UserControl.Resources>
    <TextBox Text="{Binding Source={DynamicResource ViewModelLocator}, Path=CalculatorViewModel...}" />

That would work great when I'm designing the UserControl in Expression Blend as the locator can supply a ViewModel replete with dummy data. But what happens at runtime; how does that binding get overwritten with instances of the UserControl's ViewModel class supplied by collections in the main ViewModel? The same problem happens for the MainWindow at design time. If I am working in Expression Blend on the look and feel of the MainWindow how does that binding get overwritten with instances of the UserControl's ViewModel class supplied by collections in the design time main ViewModel?

There are a number of questions and answers that already touch on this:

  1. In https://stackoverflow.com/a/3334780/575530 akjoshi suggests that the main ViewModel holds the instance of the UserControl's ViewModel; but how does that work when I am designing the UserControl itself?

  2. In https://stackoverflow.com/a/9910298/575530 tam points out that "you want to keep your datacontext open and available for binding to in controls where you are using this control" and in the following comment SoMoS adds that one needs to "create properties in the ViewModel for the binded properties and when someone wants to change one property of the control (like some subcontrol enabled) he will have to go thru the View Model". That's promissing but I am not sure what to do in place of the MainViewModel's bindable collection of UserControlViewModels.

  3. In https://stackoverflow.com/a/6340668/575530 Ehsan Ershadi suggests that "it's not a good idea to use MVVM Light ViewModelLocator for UserControles because it is a static property and when you are going to instantiate multiple instances of your user control there are going to have the same common ViewModel so they all act same and this is not what we want for a UserControl in case you decide to use it once in your entire project." And then states that "to get around this problem you need to modify the ViewModelLocator by making all the properties Non static for instance". I'm not sure how that would help me.

  4. In the comments after https://stackoverflow.com/a/2637830/575530 Jon Mitchell mentions that "It does look like MVVM isn't ideal for creating user controls". I hope that's not right.

  5. In contrast, in When should I use a UserControl instead of a Page? dthrasher mentions that "many of the WPF MVVM frameworks seem to avoid using the NavigationWindow and Page controls in favor of composing pages using nested UserControls", i.e. that UserControls are commonplace devices in MVVM.

  6. In https://stackoverflow.com/a/1798649/575530 Reed Copsey reminds sandbox that "UserControls can always talk to their containing control via exposing properties and using DataBinding. This is very nice, since it preserves the MVVM style in all aspects." and that "The containing control can use properties to link two properties on two user controls together, again, preserving clean boundaries" But again I don't see how this helps when I am in Expression Blend designing the UserControl.

  7. In Should I be using UserControls for my Views instead of DataTemplates? Rachel mentions occasionally using Expression Blend to design the UserControl before cutting and pasting the code into a DataTemplate: "in the event I do want to use it to design a DataTemplate, I usually create a new UserControl, design it the way I want it, then copy/paste the contents into a DataTemplate"

Sorry about this essay length question! I am confused about how to use MVVM Light when designing a UserControl destined to be the visual for the items in a collection on the MainWindow, especially how to set up the three bindings: run-time view models, design time view models for the main window and its instantiations of the user control, and a design time view model for the user control in isolation.

like image 966
dumbledad Avatar asked Dec 11 '12 19:12

dumbledad


People also ask

Should a UserControl have a ViewModel?

Your UserControls should NOT have ViewModels designed specifically for them. This is, in fact, a code smell. It doesn't break your application immediately, but it will cause you pain as you work with it. A UserControl is simply an easy way to create a Control using composition.

Can one ViewModel have multiple views?

If the view models truly are equivalent then you might want to ask yourself why you have two separate views in the first place. You may consider merging them into one view. It's possible that having two separate views is what you want, but it's just something to consider.

What is c# MVVM?

MVVM is an architectural pattern that is represented by three distinct components, the Model, View and ViewModel. In order to understand these three layers, it is necessary to briefly define each, followed by an explanation of how they work together. Model is the layer that drives the business logic.

Does WPF use MVVM?

MVVM is the lingua franca of WPF developers because it is well suited to the WPF platform, and WPF was designed to make it easy to build applications using the MVVM pattern (amongst others).

How to add datagridview controls at run time?

Users can add controls at Runtime from the Toolbar, design their Form, write their code for controls to perform some Action. Now, for example, user can add a DataGridView and a Button Control at Run time. In Code Part Text Box, users can add their code to Bind data from Database to the Selected Data Grid. Everything is done at Runtime.

How do I debug a custom control in Visual Studio Code?

To debug your custom control's design-time behavior, you will debug a separate instance of Visual Studio that is running your custom control's code. Right-click on the DebugControlLibrary project in the Solution Explorer and select Properties. In the DebugControlLibrary property sheet, select the Debug tab.

What is design-time error list in Visual Studio?

Explains the meaning and use of the Design-Time Error List that appears in Microsoft Visual Studio when the Windows Forms designer fails to load. Shows how to diagnose and fix common issues that can occur when you author a custom component or control. Discusses how to create your own custom controls with the .NET Framework.

How to run code at runtime in Visual Studio Code?

Users can enter their own C# code at textbox at the bottom to run their code at Runtime, for example, Add a Button and in the Textbox, add your code and click the Button to see the Runtime Button Click event. Here in this program, I have added a simple MessageBox display while clicking the Button.


2 Answers

I think you're overcomplicating things:

What's wrong with:

<Grid IsSharedSizeScope="True">
   <ScrollViewer Margin="8" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True">
      <ItemsControl x:Name="GraphItemsControl" Margin="8"  ItemsSource="{Binding VibeEvents, Mode=OneTime}">
         <ItemsControl.ItemTemplate>
            <DataTemplate>
               <MyShinyUserControl DataContext={Binding}/>
            </DataTemplate>
         </ItemsControl.ItemTemplate>
      </ItemsControl>
   </ScrollViewer>
</Grid>

Bind each VibeEvent to the DataContext of the user control. In the user control itself I'd suggest creating a design-time DataContext to make design easier. Design-Time DataContext looks like this:

<UserControl x:Class="EMC.Windows.AlarmsModule.UserControl1"
             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"
    mc:Ignorable="d"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:AlarmsModule="clr-namespace:EMC.Windows.AlarmsModule" d:DesignHeight="300"
    d:DesignWidth="300"
             d:DataContext="{d:DesignInstance Type=AlarmsModule:Alarm}"
    >

This gets you to a place where you can build your user control and have design-time data in it. And it's simple and doesn't require much, if any, scaffolding.

like image 183
Faster Solutions Avatar answered Nov 02 '22 23:11

Faster Solutions


Based on Faster Solutions' answer here's the simplest example I can come up with of using a UserControl to display the contents of a list within MVVM Light.

For completeness sake I'll include all the code which I have tried to make as short as possible while still providing design time data that differs from run time data in both the user control's view model and the main view model.

Firstly the locator, VMUCExample/ViewModel/ViewModelLocator.cs:

using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;

namespace VMUCExample.ViewModel
{
    public class ViewModelLocator
    {
        static ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
            SimpleIoc.Default.Register<MainViewModel>();
            SimpleIoc.Default.Register<ASquareViewModel>();
        }

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

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

        public static void Cleanup() {}
    }
}

I've not used the data service side of MVVM Light, partly to keep things simple. The visible distinction between live data and design time data is handled in the two view model classes.

VMUCExample/ViewModel/ASquareViewModel.cs:

using System.Windows.Media;
using GalaSoft.MvvmLight;

namespace VMUCExample.ViewModel
{
    public class ASquareViewModel : ViewModelBase
    {
        private Brush _SquareColour;
        public Brush SquareColour
        {
            get
            {
                return _SquareColour ?? (_SquareColour = IsInDesignModeStatic ?
                    new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0x78, 0x78)) : // FF7878 (pastel red)
                    new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xBB, 0x78))); // FFBB78 (nectarine)
            }
            set { _SquareColour = value; }
        }
    }
}

So looking at the user control in Expression Blend I see a simple rectangle with pastel red fill:

Blend screenshot with the user control open for editing

The main view model lives in the file VMUCExample/ViewModel/MainViewModel.cs:

using System.Collections.Generic;
using System.Windows.Media;
using GalaSoft.MvvmLight;

namespace VMUCExample.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        private List<ASquareViewModel> _Squares;
        public List<ASquareViewModel> Squares
        {
            get
            {
                if (_Squares == null)
                {
                    _Squares = new List<ASquareViewModel>();
                    var colour = IsInDesignModeStatic ?
                        new SolidColorBrush(Color.FromArgb(0xFF, 0x78, 0xB2, 0xFF)) : // 78B2FF (pastel blue)
                        new SolidColorBrush(Color.FromArgb(0xFF, 0xF9, 0xFF, 0xC7)); // F9FFC7 (eggshell)
                    for (var i = 0; i < 10; i++)
                    {
                        _Squares.Add(new ASquareViewModel {SquareColour = colour});
                    }
                }
                return _Squares;
            }
            set { _Squares = value; }
        }
        public MainViewModel() {}
    }
}

The view for this can also be edited in Expression Blend, but the view model code sets the design time colour data differently:

Blend screenshot with the main window open for editing

These are the two XAML files, firstly VMUCExample/ASquareUC.xaml:

<UserControl x:Class="VMUCExample.ASquareUC"
             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:DesignHeight="15" d:DesignWidth="60"
             d:DataContext="{Binding ASquare, Mode=OneWay, Source={StaticResource Locator}}">
    <Grid>
        <Rectangle Fill="{Binding SquareColour}" Margin="2" Width="50" Height="10"/>
    </Grid>
</UserControl>

You can see I've used Faster Solutions' suggestion of putting d:DataContext so that the design time binding I need for Expression Blend when I am designing the user control does not block the data context I need at run-time nor the data context supplied by the parent when I am designing the main window in Expression Blend. I am uncomfortable about this though, it is not the approach characterised as MVVM Light in Paul Stovell's Option 6: A XAML View Model Locator and endorsed by @LBugnion

The other view file is VMUCExample\MainWindow.xaml:

<Window x:Class="VMUCExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vmucExample="clr-namespace:VMUCExample"
        Height="200" Width="100"
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Grid x:Name="LayoutRoot">
        <ScrollViewer ScrollViewer.CanContentScroll="True">
            <ItemsControl ItemsSource="{Binding Squares}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <vmucExample:ASquareUC/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl> 
        </ScrollViewer>
    </Grid>
</Window>

The example application itself just draws ten rectangles in a column:

Screenshot of the simple test app

Too simple, I know, but hopefully shows the three potential data sets in use:

  1. Design time for the user control (#FF7878 pastel red),
  2. Design time for the user control set by the main window (#78B2FF pastel blue), and
  3. Run time for the user control set by the main window (#F9FFC7 eggshell).

(N.B. There is another data option, run time for the user control not set by the main window. In this case the user control's view model chooses #FFBB78/nectarine but I didn't need that to explore these bindings.)

For completeness here is the VMUCExample\App.xaml file:

<Application x:Class="VMUCExample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:VMUCExample.ViewModel"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             StartupUri="MainWindow.xaml"
             mc:Ignorable="d">
    <Application.Resources>
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
    </Application.Resources>
</Application>
like image 42
dumbledad Avatar answered Nov 02 '22 23:11

dumbledad