A.Pissicat
A.Pissicat

Reputation: 3265

Share DataContext between MainWindows and UserControl

I wonder if it is possible to shared a datacontext between a Windows and is UserControl in C#/WPF.

I have a main windows like this (not finished):

MainWindow.xaml:

<Window x:Class="MyProject.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:local="clr-namespace:MyProject"
        xmlns:v="clr-namespace:MyProject.Views"
        mc:Ignorable="d"
        Title="MyProject" >
    <Window.DataContext>
        <local:MainViewModel/>        
    </Window.DataContext>
    <Grid>
        <v:GenerateView/>
        <v:ReadView/>
    </Grid>
</Window>

MainViewModel.cs:

public class MainViewModel : ViewModelBase
{
    #region Properties
    #endregion

    #region Fields
    #endregion

    #region Constructor
    public MainViewModel()
        : base()
    {
    }
    #endregion

    #region Methods
    #endregion

    #region Commands
    #endregion
}

Depending on a future parameter, I will display my view GenerateView or ReadView. Actually I'm developing the UserControl GenerateView, but I wonder if I can use the same Datacontext.

According to that post, I started by this :

<UserControl x:Class="MyProject.Views.GenerateView"
         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" 
         xmlns:local="clr-namespace:MyProject.Views"
         xmlns:p="clr-namespace:MyProject.Properties"
         xmlns:MyProject="clr-namespace:MyProject" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyProject:MainWindow}}}">
    <Grid>
    </Grid>
</UserControl>

But It doesn't work, when I try to access Datacontext in GenerateView, It is null.

Edit:

I forgot a part of my code:

public partial class GenerateView : UserControl
{
    private MainViewModel Context
    {
        get
        {
            return DataContext as MainViewModel;
        }
    }

    public GenerateView()
    {
        InitializeComponent();
        Context.PropertyChanged += Context_PropertyChanged;
    }

    private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        //Action to perform
    }
}

The line Context.PropertyChanged += Context_PropertyChanged; throw an Exception because Datacontext is null.

Upvotes: 3

Views: 2332

Answers (4)

user2019716
user2019716

Reputation: 643

One can use the View first approach. You first define a design context for design purposes.

<UserControl x:Class="MyProject.Views.CustomView"
     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" 
     xmlns:local="clr-namespace:MyProject.Views"
     xmlns:p="clr-namespace:MyProject.Properties"
     xmlns:MyProject="clr-namespace:MyProject" 
     mc:Ignorable="d"
     d:DataContext="{d:DesignInstance Type=vm:MyViewModel}"
     d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
     .....
  </Grid>
</UserControl>

then you bind the usercontrol to the datacontext of the mainwindow

<Window x:Class="MyProject.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:local="clr-namespace:MyProject"
    xmlns:v="clr-namespace:MyProject.Views"
    mc:Ignorable="d"
    Title="MyProject" >
<Window.DataContext>
    <local:MyViewModel/>        
</Window.DataContext>
<Grid>
    <v:CustomView/>
</Grid>
</Window>

then the usercontrol will automatically inherit the datacontext of its parent.

Upvotes: 0

Felix D.
Felix D.

Reputation: 5083

I usually set the DataContext in my Window:

public class MainWindow : Window
{
   InitializeComponent();
   ViewModel vm = new ViewModel();
   this.DataContext = vm;
}

or sometimes more advanced:

I add a static property to my ViewModel:

public static ViewModel Instance {get; set;}

public class MainWindow : Window
{
   InitializeComponent();
   if(ViewModel.Instance == null)
   {
       ViewModel.Instance = new ViewModel();           
   }
   this.DataContext = ViewModel.Instance;
}

Upvotes: 1

ViVi
ViVi

Reputation: 4464

What is the main requirement behind reusing a view model for Window and it’s sub user control? it doesn’t make sense. Does both have everything in common?

In my opinion create a MainWindowViewModel and create SubViewModel for user control. Create the instances of sub viewmodels in your MainWindowViewModel and access them using DataContext.SubViewModel.

By doing so, you could maintain the code and application well as well as preserve coding standards and have a complication free viewmodel. If you mix up everything just for reusability, you may be violating the MVVM pattern. Let different views/windows have its own viewmodel since they are not at all alike.

If both are alike, then you can create reusable controls using Dependency Properties.

Upvotes: 3

A.Pissicat
A.Pissicat

Reputation: 3265

According to Andrew's comment I'have the solution to my problem:

public partial class GenerateView : UserControl { private MainViewModel Context { get { return DataContext as MainViewModel; } }

public GenerateView()
{
    InitializeComponent();
    DataContextChanged += GenerateView_DataContextChanged;
}

private void GenerateView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (Context != null)
        Context.PropertyChanged += Context_PropertyChanged;
}
private void Context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    //Action to perform
}

}

And I removed DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyProject:MainWindow}}}" from my UserControl.

Upvotes: 0

Related Questions