StaticResource inside MainWindow passing through a Datacontext inside an UserControl and this Datacontext passing through another UserControl

Michell 45 Reputation points
2023-08-07T17:40:32.9766667+00:00

Could you clarify a question about StaticResource and hierarchy?

I have MainWindow.xaml (Window) and inside it I call NewChart.xaml (UserControl) through the command <vm:NewChart DataContext="{StaticResource _instance}" /> .

I would like to know how inside NewChart.xaml I can call another UserControl called NewMenuRight.xaml passing the StaticResource _instance that he inherited from MainWindow.xaml.

E.g.

MainWindow.xaml.cs:

public partial class MainWindow : Window
    {
        public MainWindowViewModel _instance { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            
            _instance = new MainWindowViewModel();
            DataContext = _instance;
        }

    }

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
      ...
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:views="clr-namespace:WpfApp1.Views" 
        xmlns:vm="clr-namespace:WpfApp1.ViewModels"       
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <vm:MainWindowViewModel x:Key="_instance" />
    </Window.Resources>
    <StackPanel>
        <views:NewChart DataContext="{StaticResource _instance}" />
    </StackPanel>
</Window>

MewChart.xaml

<UserControl x:Class="WpfApp1.NewChart"
      ...
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:views="clr-namespace:WpfApp1.Views"
        xmlns:vm="clr-namespace:WpfApp1.ViewModels"                
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <UserControl.Resources>
        <vm:MainWindowViewModel x:Key="_instance" />
    </UserControl.Resources>
    <StackPanel>
        <views:NewMenuRight DataContext="{StaticResource _instance}" />
    </StackPanel>
</UserControl>


NewChart.xaml.cs

public partial class NewChart: UserControl
    {
        public MainWindowViewModel _instance { get; set; }

        public NewChart()
        {
            InitializeComponent();

            
            _instance = new MainWindowViewModel();
            DataContext = _instance;
        }

    }

MainWindowViewModel.cs:

namespace WpfApp1.ViewModels;
public partial class MainWindowViewModel 
{
    private static MainWindowViewModel _instance; 
    public event PropertyChangedEventHandler PropertyChanged;
    private bool _novoMenuIsOpen { get; set; } 
    public bool NovoMenuIsOpen
    {
        get { return _novoMenuIsOpen; } 
        set
        {
            _novoMenuIsOpen = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NovoMenuIsOpen)));
        }
    }
    public void OpenMenu() 
    {
        NovoMenuIsOpen = true;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NovoMenuIsOpen)));
    }
   
    public void ClickCancelar(object sender, RoutedEventArgs args)
    {
        NovoMenuIsOpen = false;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NovoMenuIsOpen)));
    }
   
    public MainWindowViewModel()
    {
        NovoMenuIsOpen = false;
        _instance = this;
    }
}

NewMenuRight.xaml.cs

public partial class NewMenuRight: UserControl      
{          
	public MainWindowViewModel _instance { get; set; }          
	public NewChart()          
	{              
		InitializeComponent();                            
		_instance = new MainWindowViewModel();              
		DataContext = _instance;          
	}      
}

NewMenuRight.xaml

<UserControl ...
             xmlns:views="using:QuantPro.Views"
             xmlns:vm="using:QuantPro.ViewModels"
             mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="330"
             x:Class="WpfApp1.Views.NewMenuRight">
  <UserControl.Resources>
    <vm:MainWindowViewModel x:Key="_instance" />
  </UserControl.Resources>
  <Grid RowDefinitions="Auto Auto" >    
        <Button Grid.Column="0" Margin="0,10,0,0" Width="70" CornerRadius="10" Click="ClickCancelar" DataContext="{StaticResource _instance}">Cancel</Button>
      </Grid>
    </Grid>
  </Border>
  </Grid>
</UserControl>

The problem is: the NewMenuRight.xaml is creating a new instance of MainWindowViewModel and it is not changing the properties of the MainWindowViewModel that was instantiated in the initialization of MainWindow.xaml

I would like to click on Cancel Button(NewMenuRight.xaml) and it run the method ClickCancelar inside MainWindowViewModel.cs from current instance, not create a new one. Is it possible?

In resume: StaticResource inside MainWindow passing through a Datacontext (MainWindowViewModel) inside an UserControl(NewChart) and this Datacontext passing through another UserControl(NewMenuRight).

Thank you so much!

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,385 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,264 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
765 questions
0 comments No comments
{count} votes

Accepted answer
  1. Hui Liu-MSFT 38,256 Reputation points Microsoft Vendor
    2023-08-08T09:58:51.7+00:00

    Hi,@Michell . Welcome Microsoft Q&A. For the problem of click on Cancel Button(NewMenuRight.xaml) and it run the method ClickCancelar inside MainWindowViewModel.cs from current instance, I modified the code according to your situation. You could try looking at the code below.

    You can check the code below to see if it is what you want. If you still have problems, please let me know.

    NewMenuRight.xaml:

    <UserControl x:Class="WpfApp2.Views.NewMenuRight"
                ...
                 xmlns:local="clr-namespace:WpfApp2.Views"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
     
        <Grid  >
            <CheckBox IsChecked="{Binding NovoMenuIsOpen}"/>
            <Button Grid.Column="0" Margin="0,10,0,0" Width="70"  Command="{Binding  ClickCancelarCommand}" >Cancel</Button>
        </Grid>
    </UserControl>
    
    
    

    NewMenuRight.xaml.cs:

     public partial class NewMenuRight : UserControl
        {
            public NewMenuRight()
            {
                InitializeComponent();
            }
        }
    
    

    NewChart.xaml:

    
    <UserControl x:Class="WpfApp2.Views.NewChart"
                 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:views="clr-namespace:WpfApp2.Views"
                 xmlns:vm="clr-namespace:WpfApp2.ViewModels"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <UserControl.Resources>
            <vm:MainWindowViewModel x:Key="_instance" />
        </UserControl.Resources>
        <StackPanel>
            <views:NewMenuRight DataContext="{StaticResource _instance}" />
        </StackPanel>
    </UserControl>
    
    

    NewChart.xaml.cs:

    
     public partial class NewChart : UserControl
        {
            public MainWindowViewModel _instance { get; set; }
    
            public NewChart()
            {
                InitializeComponent();
    
    
                _instance = new MainWindowViewModel();
                DataContext = _instance;
            }
        }
    
    

    RelayCommand:

    
     public class RelayCommand : ICommand
        {
            private readonly Action<object> _execute;
            private readonly Predicate<object> _canExecute;
    
            public RelayCommand(Action<object> execute)
                : this(execute, null)
            {
            }
    
            public RelayCommand(Action<object> execute, Predicate<object> canExecute)
            {
                if (execute == null)
                    throw new ArgumentNullException("execute");
                _execute = execute;
                _canExecute = canExecute;
            }
    
            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute(parameter);
            }
    
            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }
    
            public void Execute(object parameter)
            {
                _execute(parameter);
            }
        }
    
    

    MainWindowViewModel.cs :

    
    namespace WpfApp2.ViewModels
    {
        public class MainWindowViewModel : INotifyPropertyChanged
        {
            private static MainWindowViewModel _instance;
         
            private bool _novoMenuIsOpen { get; set; }
            public bool NovoMenuIsOpen
            {
                get { return _novoMenuIsOpen; }
                set
                {
                    _novoMenuIsOpen = value;
                    OnPropertyChanged("NovoMenuIsOpen");
                }
            }
            public RelayCommand ClickCancelarCommand { get; }
    
    
            public MainWindowViewModel()
            {
                NovoMenuIsOpen = false;
                _instance = this;
                ClickCancelarCommand = new RelayCommand(ClickCancelar);
            }
    
            public void OpenMenu()
            {
                NovoMenuIsOpen = true;
               
            }
    
            public void ClickCancelar(object obj)
            {
                NovoMenuIsOpen = false;
              
            }
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    
    
    

    MainWindow.xaml:

    
    <Window x:Class="WpfApp2.MainWindow"
          ...
            xmlns:local="clr-namespace:WpfApp2"
               xmlns:views="clr-namespace:WpfApp2.Views"
                 xmlns:vm="clr-namespace:WpfApp2.ViewModels"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Window.Resources>
            <vm:MainWindowViewModel x:Key="_instance"/>
        </Window.Resources>
        <StackPanel>
            <views:NewChart DataContext="{StaticResource _instance}" />
        </StackPanel>
    </Window>
    
    

    MainWindow.xaml.cs:

    
      public partial class MainWindow : Window
        {
            public MainWindowViewModel _instance { get; set; }
    
            public MainWindow()
            {
                InitializeComponent();
    
    
                _instance = new MainWindowViewModel();
                DataContext = _instance;
            }
        }
    
    

    The result:

    7


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


6 additional answers

Sort by: Most helpful
  1. Brian Zarb 1,675 Reputation points
    2023-08-07T18:16:58.8966667+00:00

    Hi Michell, i remember having this issue a while ago, here is why it happens and how to resolve it: The why: you're instantiating a new instance of MainWindowViewModel in every XAML where you use <vm:MainWindowViewModel x:Key="_instance" />. Each time this is used in XAML, a new instance is created and the _instance you've declared in the code-behind is not the same instance as the one being used in the XAML. The key here is to ensure that the DataContext for all related views is set to the same instance of the MainWindowViewModel.


    Here's how you can solve this:

    1. Remove the instantiation of MainWindowViewModel from XAML files. You don't need <vm:MainWindowViewModel x:Key="_instance" /> in every XAML.
    2. Instead of using StaticResource, just use data binding.

    steps:

    MainWindow.xaml

    <Window x:Class="WpfApp1.MainWindow"
           ...
           Title="MainWindow" Height="450" Width="800">
        <StackPanel>
            <views:NewChart DataContext="{Binding}" />
        </StackPanel>
    </Window>
    

    MainWindow.Xaml.cs

    public partial class MainWindow : Window
    {
        public MainWindowViewModel _instance { get; set; }
    
        public MainWindow()
        {
            InitializeComponent();
    
            _instance = new MainWindowViewModel();
            DataContext = _instance;
        }
    }
    

    NewChart.xaml

    <UserControl x:Class="WpfApp1.MainWindow"
           ...
           Title="MainWindow" Height="450" Width="800">
        <StackPanel>
            <views:NewMenuRight DataContext="{Binding}" />
        </StackPanel>
    </UserControl>
    

    NewChart.xaml.cs:

    public partial class NewChart: UserControl
    {
        public NewChart()
        {
            InitializeComponent();
        }
    }
    

    NewMenuRight.xaml: Here, you don't need to set the DataContext again since it's inherited from its parent, and thus, it will have the same DataContext as NewChart.

    <UserControl ...
                 xmlns:views="using:QuantPro.Views"
                 xmlns:vm="using:QuantPro.ViewModels"
                 mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="330"
                 x:Class="WpfApp1.Views.NewMenuRight">
      <Grid RowDefinitions="Auto Auto" >    
            <Button Grid.Column="0" Margin="0,10,0,0" Width="70" CornerRadius="10" Click="ClickCancelar">Cancel</Button>
      </Grid>
    </UserControl>
    

    Apologies for the incorrect font call above.

    By doing this, you're ensuring that the NewChart UserControl is using the DataContext of its parent (MainWindow) which is an instance of MainWindowViewModel. Similarly, NewMenuRight is using the DataContext of its parent (NewChart) which again is the same instance of MainWindowViewModel.

    With this method, when you click the Cancel button, it will call the ClickCancelar method from the same instance of the MainWindowViewModel that was instantiated in the MainWindow.

    Hope this help, if so accept it as an answer to help anyone else who is struggling with the same problem

    0 comments No comments

  2. Brian Zarb 1,675 Reputation points
    2023-08-07T19:25:47.4333333+00:00

    Yes, it is possible,

    To achieve this, you should avoid creating a new instance of MainWindowViewModel inside every UserControl. Instead, propagate the DataContext from MainWindow to its children.

    Here's how you can adjust your code to ensure that:

    MainWindow.xaml

    You've already set the DataContext of the NewChart UserControl to _instance which is your ViewModel.

    <views:NewChart DataContext="{StaticResource _instance}" />
    

    NewChart.xaml

    Remove the creation of another instance of MainWindowViewModel from the resources. Just bind the DataContext of NewMenuRight to the current DataContext.

    <UserControl x:Class="WpfApp1.NewChart"
        ...
        Title="NewChart" Height="450" Width="800">
        <StackPanel>
            <views:NewMenuRight DataContext="{Binding}" />
        </StackPanel>
    </UserControl>
    

    NewChart.xaml.cs

    You do not need to create another instance here. The DataContext will be inherited from MainWindow.

    public partial class NewChart: UserControl
    {
        public NewChart()
        {
            InitializeComponent();
        }
    }
    

    NewMenuRight.xaml

    Remove the creation of another instance of MainWindowViewModel from the resources. The DataContext will be inherited from NewChart.

    <UserControl ...
        xmlns:views="using:QuantPro.Views"
        xmlns:vm="using:QuantPro.ViewModels"
        mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="330"
        x:Class="WpfApp1.Views.NewMenuRight">
        <Grid RowDefinitions="Auto Auto" >    
            <Button Grid.Column="0" Margin="0,10,0,0" Width="70" CornerRadius="10" Click="ClickCancelar">Cancel</Button>
        </Grid>
    </UserControl>
    

    NewMenuRight.xaml.cs

    You do not need to create another instance here. The DataContext will be inherited from NewChart.

    public partial class NewMenuRight: UserControl      
    {          
        public NewMenuRight()          
        {              
            InitializeComponent();      
        }      
    }
    

    By doing this, when you click the Cancel button in NewMenuRight, it will call the ClickCancelar method from the MainWindowViewModel instance created in MainWindow, ensuring that there's a single instance of the ViewModel being shared across all three controls (MainWindow, NewChart, and NewMenuRight).

    0 comments No comments

  3. Michell 45 Reputation points
    2023-08-08T00:27:09.2533333+00:00

    Hi Brian Zarb,

    Firstly I would like to thank you for your response.

    I made the changes exactly as you suggested but unfortunately the NewMenuRight.xaml does not find the ClickCancelar method that is inside the MainWindowViewModel.

    If I put the ClickCancelar method inside the NewMenuRight.xaml.cs file it compiles successfully but I can't access the methods and properties of MainWindowViewModel.cs because there is nothing declared inside NewMenuRight.xaml.cs to access MainWindowViewModel.cs.

    MainWindow.xaml

    <Window.Resources>
        <vm:MainWindowViewModel x:Key="_instance" />
    </Window.Resources>
    <local:NewChart  DataContext="{StaticResource _instance}"/>
    

    MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        public MainWindowViewModel _instance { get; set; }
        public MainWindow()
        {
            InitializeComponent();
            _instance = new MainWindowViewModel();
            DataContext = _instance;
        }
    }
    

    NewChart.xaml

    <views:NewMenuRight DataContext="{Binding}" />
    

    NewChart.xaml.cs

    public partial class NewChart : UserControl
    {
        public NewChart()
        {
            InitializeComponent();
        }
    }
    

    NewMenuRight.xaml

    <Button Grid.Column="1" Click="ClickCancelar">Cancelar"</Button>
    

    NewMenuRight.xaml

    public partial class NewMenuRight : UserControl
    {
        public NewMenuRight()
        {
            InitializeComponent();
        }
    }
    

    MainWindowViewModel.cs

    public void ClickCancelar(object sender, RoutedEventArgs args)
    

  4. Michell 45 Reputation points
    2023-08-08T11:28:49.6666667+00:00

    Brian Zarb, thank you for reply. I will try do that.

    0 comments No comments