locked
How to Bind a Viewmodel to a Nested User Control when Using the MVVM Pattern

    Question

  • Using Unity in an MVVM pattern, as part of a Windows 8 store app, I cannot seem to bind a nested user control's viewmodel to the user control in an MVVM way. Here's some code:

    <Grid>...

    <ItemsControl>...

    <uc:MyUserControl DataContext="{Binding}" />

    ---------------------------------------------------

    In the user control's code behind, I at first thought that this would work

    private MyUserControlViewModel ViewModel { get { return DataContext as MyUserControlViewModel; } }

    It does not. The datacontext by this point is set to an item within a class collection. I tried passing the user control's viewmodel from the parent page to the user control after instantiating the user control's viewmodel like this

    publicParentPageViewModel(IDataServicedataService, INavigationServicenavigationService, ISessionStateService _sessionStateService, IMyUserControlViewModel myUserControlViewModel) {

    _myUserControlViewModel = myUserControlViewModel

    ... }

    This approach does apparently instantiate the user control's viewmodel, but I cannot see how to pass it from the parent page's viewmodel to the user control. If you are able to follow my descriptions, please advise how one should instantiate a user control's viewmodel using Unity when that user control is nested instead a parent page's itemcontrol.itemtemplate.

    Friday, July 18, 2014 10:00 PM

All replies

  • Do you need a separate ViewModel for your UserControl? With MVVM a UserControl is part of the View like any other visual element. It will participate in data-binding in the same way as other controls. If you want, you can add properties to it too (dp), and bind to these. If you want to bind to a different part of the DataContext and the page ViewModel from your UserControl you can. See binding mark-up extensions here.

    See http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh758283.aspx

    This is a point of implementation style, but does enable you to bind a ViewModel to a nested user control when using the MVVM pattern.

    Saturday, July 19, 2014 9:05 AM
  • I think there is some confusion, when you are using some type of ItemsControl (FlipView, GridView, ListView etc.) then its ItemsSource should point to a collection or the DataContext itself should be a collection for that control.

    Any thing that sits inside ItemsControl as a DataTemplate (uc:MyUserControl in  your case) is repeated for number of times equal to count of items in collection. Then all those repeated controls created from DataTemplate get respective item set as DataContext automatically. I am not sure why are you writing binding expression there.

    Further if you post some working code that can be used to see specifically what your issue is then I can help you much better. Please upload a sample on OneDrive and share link here.


    -- Vishal Kaushik --

    Please 'Mark as Answer' if my post answers your question and 'Vote as Helpful' if it helps you. Happy Coding!!!

    Saturday, July 19, 2014 6:05 PM
  • Within the user control I need to access the DataContext and to run methods from the user control's viewmodel. While I can certainly access the DataContext, I cannot see how to also instantiate the user control's viewmodel--without explicitly newing it up from the user control's code behind. Such a newing up would violate the MVVM pattern I'm trying to maintain. So the issue remains: how does one access both the DataContext and the viewmodel from a user control when that user control lies within an ItemsControl?
    Sunday, July 20, 2014 6:17 PM
  • It is certainly possible to compose ViewModels though DI using IOC container like Unity, as you are doing. To your user control, you can add properties. These properties can bind to the page view model. The page ViewModel can expose a property to access your injected ViewModel. MyUserControlViewModel would be assigned in your page ViewModel constructor, when it is injected.

    <uc:MyUserControl IsFlashing="{Binding MyUserControlViewModel.IsCritical}" .. other properties, even relative binding to  />

    This is not passing in the DataContect directly, rather passing in the required values and using data binding with it's associated view model, that was injected when the page ViewModel was constructed. (You may want to think about the lifetime of the injected ViewModel). This offers better encapsulation than passing an arbitrary DataContext to a user control.

    The fact that it is a list or not the same principles apply.
    • Edited by Ben - GGT Sunday, July 20, 2014 7:11 PM
    Sunday, July 20, 2014 6:59 PM
  • Here are two options that you can do when you want to have a separate ViewModel for a user control to be used.

    <Page
        x:Class="MsdnSample21072014.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:MsdnSample21072014"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        <Page.DataContext>
            <local:MainPageViewModel/>
        </Page.DataContext>
        <Page.Resources>
            <local:MyUserControlViewModel x:Key="sameMyUserControlViewModel"/>
        </Page.Resources>
        <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <!-- Option 1 if same instance of MyUserControlViewModel is to be used -->
            <GridView ItemsSource="{Binding DataItems}">
                <GridView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Name}"/>
                            <local:MyUserControl1 DataContext="{StaticResource sameMyUserControlViewModel}"/>
                        </StackPanel>
                    </DataTemplate>
                </GridView.ItemTemplate>
            </GridView>
            <!-- Option 2 if every time a new instance of MyUserControlViewModel is to be used -->
            <GridView ItemsSource="{Binding DataItems}">
                <GridView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Name}"/>
                            <local:MyUserControl1>
                                <local:MyUserControl1.DataContext>
                                    <local:MyUserControlViewModel/>
                                </local:MyUserControl1.DataContext>
                            </local:MyUserControl1>
                        </StackPanel>
                    </DataTemplate>
                </GridView.ItemTemplate>
            </GridView>
        </StackPanel>
    </Page>
    

    Option 3:

    In above XAML I have assumed that page is bound to MainPageViewModel what has a DataItems collection of type say ABC. In ABC I can add a property of Type MyUserControlViewModel that can then be simply bound to your User Control. In this case if you want same instance to be shared, then you use a Singleton View Model else you can always create a new one and return. By creating property you will make sure that your ViewModel is instantiated only when some UI control is bound to it else it simply stays like that.

    Option 4:

    What I have explained in Option 3 can be implemented via any IoC where in property rather than creating/getting instance directly you will ask IOC to resolve it for you. In config of IOC you can say if it needs to be singleton.

    Does this help? If not then please post some code I will be glad to help!


    -- Vishal Kaushik --

    Please 'Mark as Answer' if my post answers your question and 'Vote as Helpful' if it helps you. Happy Coding!!!

    Monday, July 21, 2014 7:38 PM
  • The code below reveals how I would like things to work.

    xmlns:uc="using:MySolution.Controls"

    <Grid >
     <Grid.ChildrenTransitions>
      <TransitionCollection>
       <EntranceThemeTransition />
      </TransitionCollection>
     </Grid.ChildrenTransitions>
     <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
     </Grid.RowDefinitions>
     <Grid.ColumnDefinitions>
      <ColumnDefinition Width="120" />
      <ColumnDefinition Width="Auto" MinWidth="400" />
      <ColumnDefinition Width="Auto" />
     </Grid.ColumnDefinitions>

     <ItemsControl x:Name="gv1"
           Margin="0 62 80 20"
           Grid.Column="1"
           Grid.Row="1">

      <ItemsControl.ItemsPanel>
       <ItemsPanelTemplate>
        <VariableSizedWrapGrid  Orientation="Vertical" MaximumRowsOrColumns="1" />                           
       </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <ItemsControl.ItemTemplate>
      <DataTemplate>
       <uc:MyUserControl DataContext="{Binding}" />
      </DataTemplate>
     </ItemsControl.ItemTemplate>
     
     </ItemsControl>
    </Grid>
    --------------------------------------------------
    public sealed partial class MyUserControl : UserControl
    {
     private MyUserControlViewModel ViewModel { get { return DataContext as QuestionControlViewModel; } }

     public MyUserControl()
     {
      this.InitializeComponent();
      this.Loaded += MyUserControl_Loaded;
     }

     private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
     {
      ViewModel.InitializeViewModelCommand.Execute(null);
     }
    }  

    After the dashed lines, I would like to instantiate my user control as shown. However, the DataContext is set above and cannot be cast to the user control's viewmodel. Creating a property in the page, something like

    public OuterPageViewModel(IDataService dataService, INavigationService navigationService,
      ISessionStateService sessionStateService, ImyUserControlViewModel myUserControlViewModel)
     {
      _dataService = dataService;
      _navigationService = navigationService;
      _sessionStateService = sessionStateService;
      _myUserControlViewModel = myUserControlViewModel;
     }

    public IMyUserControlViewModel MyUserControlViewModel
     {
      get { return _myUserControlViewModel; }
      set { SetProperty(ref _myUserControlViewModel, value);}
     }

    This does cause the user control's viewmodel to be instantiated, but then I cannot access it from the user control itself. It's as if the user control's viewmodel in this case is "tied up" in the outer page's viewmodel. Again, I want to be able to use the viewmodel and the DataContext all in an IOC way using Unity. Do you have any additional thoughts?

    Monday, July 21, 2014 8:45 PM
  • I am sure that you are looking for Option 4 in my post then. I have started from Option 1 to Option 4 so that you can have some transition and understanding of what you want.

    For further discussion please provide me something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out incomplete snippets with undefined objects and unknown namespaces.


    -- Vishal Kaushik --

    Please 'Mark as Answer' if my post answers your question and 'Vote as Helpful' if it helps you. Happy Coding!!!

    Tuesday, July 22, 2014 2:19 AM