WPF Usercontrol Bindings with MVVM ViewModel not working - c#

I've spent some time trying to solve this problem but couldn't find a solution.
I am trying to bind commands and data inside an user control to my view model. The user control is located inside a window for navigation purposes.
For simplicity I don't want to work with Code-Behind (unless it is unavoidable) and pass all events of the buttons via the ViewModel directly to the controller. Therefore code-behind is unchanged everywhere.
The problem is that any binding I do in the UserControl is ignored.
So the corresponding controller method is never called for the command binding and the data is not displayed in the view for the data binding. And this although the DataContext is set in the controllers.
Interestingly, if I make the view a Window instead of a UserControl and call it initially, everything works.
Does anyone have an idea what the problem could be?
Window.xaml (shortened)
<Window x:Class="Client.Views.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:Client.Views"
mc:Ignorable="d">
<Window.Resources>
<local:SubmoduleSelector x:Key="TemplateSelector" />
</Window.Resources>
<Grid>
<StackPanel>
<Button Command="{Binding OpenUserControlCommand}"/>
</StackPanel>
<ContentControl Content="{Binding ActiveViewModel}" ContentTemplateSelector="{StaticResource TemplateSelector}">
<ContentControl.Resources>
<DataTemplate x:Key="userControlTemplate">
<local:UserControl />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
MainWindowViewModel (shortened)
namespace Client.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase mActiveViewModel;
public ICommand OpenUserControlCommand { get; set; }
public ViewModelBase ActiveViewModel
{
get { return mActiveViewModel; }
set
{
if (mActiveViewModel == value)
return;
mActiveViewModel = value;
OnPropertyChanged("ActiveViewModel");
}
}
}
}
MainWindowController (shortened)
namespace Client.Controllers
{
public class MainWindowController
{
private readonly MainWindow mView;
private readonly MainWindowViewModel mViewModel;
public MainWindowController(MainWindowViewModel mViewModel, MainWindow mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.OpenUserControlCommand = new RelayCommand(ExecuteOpenUserControlCommand);
}
private void OpenUserControlCommand(object obj)
{
var userControlController = Container.Resolve<UserControlController>(); // Get Controller instance with dependency injection
mViewModel.ActiveViewModel = userControlController.Initialize();
}
}
}
UserControlSub.xaml (shortened)
<UserControl x:Class="Client.Views.UserControlSub"
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:Client.Views"
xmlns:viewModels="clr-namespace:Client.ViewModels"
mc:Ignorable="d">
<Grid>
<ListBox ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Attr}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding Add}">Kategorie hinzufügen</Button>
</StackPanel>
</Grid>
</UserControl>
UserControlViewModel (shortened)
namespace Client.ViewModels
{
public class UserControlViewModel : ViewModelBase
{
private Data _selectedModel;
public ObservableCollection<Data> Models { get; set; } = new ObservableCollection<Data>();
public Data SelectedModel
{
get => _selectedModel;
set
{
if (value == _selectedModel) return;
_selectedModel= value;
OnPropertyChanged("SelectedModel");
}
}
public ICommand Add { get; set; }
}
}
UserControlController (shortened)
namespace Client.Controllers
{
public class UserControlController
{
private readonly UserControlSub mView;
private readonly UserControlViewModel mViewModel;
public UserControlController(UserControlViewModel mViewModel, UserControlSub mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.Add = new RelayCommand(ExecuteAddCommand);
}
private void ExecuteAddCommand(object obj)
{
Console.WriteLine("This code gets never called!");
}
public override ViewModelBase Initialize()
{
foreach (var mod in server.GetAll())
{
mViewModel.Models.Add(mod);
}
return mViewModel;
}
}
}

Related

WinUI 3 Binding from a DataTemplate to the parent ViewModel

In a WinUI 3 application, using CommunityToolkit.Mvvm, I have XXXPage which has a ListDetailsView.
I defined a DateTemplate for the ListDetailsView DetailsTemplate, which contains a user control : XXXDetailControl.
I am trying to bind the InstallClicked event of the XXXDetailControl to the page's ViewModel InstallCommand, with no success.
<DataTemplate x:Key="DetailsTemplate">
<Grid>
<views:XXXDetailControl
DetailMenuItem="{Binding}"
InstallClicked="{ ???? }" />
</Grid>
...
</DataTemplate>
How can I setup this binding so that the event from the control defined in the DataTemplate is binded to the page viewmodel command ? How can I setup this binding so that the selected item is sent with the event ?
XXXPage.xaml :
<Page
x:Class="XXXPage"
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:models="using:XXX.Models"
xmlns:views="using:XXX.Views"
xmlns:behaviors="using:XXX.Behaviors"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:viewmodels="using:XXX.ViewModels"
behaviors:NavigationViewHeaderBehavior.HeaderMode="Never"
mc:Ignorable="d">
<Page.Resources>
...
<DataTemplate x:Key="DetailsTemplate">
<Grid>
<views:XXXDetailControl
DetailMenuItem="{Binding}"
InstallClicked="{Binding ViewModel.InstallCommand, ElementName=?}" CommandParameter="{x:Bind (viewmodels:XXXDetailViewModel)}" />
</Grid>
...
</DataTemplate>
</Page.Resources>
<Grid x:Name="ContentArea">
...
<controls:ListDetailsView
x:Uid="ListDetails"
x:Name="ListDetailsViewControl"
DetailsTemplate="{StaticResource DetailsTemplate}"
ItemsSource="{x:Bind ViewModel.Items}"/>
</Grid>
</Page>
XXXPage.cs :
public sealed partial class XXXPage: Page
{
public XXXViewModel ViewModel
{
get;
}
public XXXPage()
{
ViewModel = App.GetService<XXXViewModel >();
InitializeComponent();
}
}
the XXXViewModel :
public class XXXViewModel : ObservableRecipient, INavigationAware
{
private XXXDetailViewModel? _selected;
public XXXDetailViewModel? Selected
{
get => _selected;
set
{
SetProperty(ref _selected, value);
}
}
public ObservableCollection<XXXDetailViewModel> Items { get; private set; } = new ObservableCollection<XXXDetailViewModel>();
public ICommand InstallCommand;
}
If you can use Command and CommandParameter inside your user control, you can do it this way.
DetailsControl.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Windows.Input;
namespace CustomControlEvent;
public sealed partial class DetailControl : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text),
typeof(string),
typeof(DetailControl),
new PropertyMetadata(default));
public static readonly DependencyProperty ClickCommandProperty = DependencyProperty.Register(
nameof(ClickCommand),
typeof(ICommand),
typeof(DetailControl),
new PropertyMetadata(default));
public static readonly DependencyProperty ClickCommandParameterProperty = DependencyProperty.Register(
nameof(ClickCommandParameter),
typeof(object),
typeof(DetailControl),
new PropertyMetadata(default));
public DetailControl()
{
this.InitializeComponent();
}
public object ClickCommandParameter
{
get => (object)GetValue(ClickCommandParameterProperty);
set => SetValue(ClickCommandParameterProperty, value);
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public ICommand ClickCommand
{
get => (ICommand)GetValue(ClickCommandProperty);
set => SetValue(ClickCommandProperty, value);
}
}
DetailsControl.xaml
<UserControl
x:Class="CustomControlEvent.DetailControl"
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:local="using:CustomControlEvent"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Button
Command="{x:Bind ClickCommand, Mode=OneWay}"
CommandParameter="{x:Bind ClickCommandParameter, Mode=OneWay}"
Content="{x:Bind Text, Mode=OneWay}" />
</Grid>
</UserControl>
MainPageViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace CustomControlEvent;
public partial class MainPageViewModel : ObservableObject
{
[RelayCommand]
private void Run(object commandParameter)
{
}
[ObservableProperty]
private ObservableCollection<DetailsViewModel> items = new()
{
new DetailsViewModel() { Details = "A" },
new DetailsViewModel() { Details = "B" },
new DetailsViewModel() { Details = "C" },
};
}
MainPage.xaml.cs
using Microsoft.UI.Xaml.Controls;
namespace CustomControlEvent;
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public MainPageViewModel ViewModel { get; } = new();
}
MainPage.xaml
This page is named ThisPage in order to bind from the DataTemplate.
<Page
x:Class="CustomControlEvent.MainPage"
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:local="using:CustomControlEvent"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="ThisPage"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<StackPanel>
<StackPanel.Resources>
<DataTemplate
x:Key="DetailsTemplate"
x:DataType="local:DetailsViewModel">
<Grid>
<local:DetailControl
ClickCommand="{Binding ElementName=ThisPage, Path=ViewModel.RunCommand}"
ClickCommandParameter="{x:Bind}"
Text="{x:Bind Details, Mode=OneWay}" />
</Grid>
</DataTemplate>
</StackPanel.Resources>
<ListView
ItemTemplate="{StaticResource DetailsTemplate}"
ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}" />
</StackPanel>
</Page>
Did you try to give the Page a Name like this?:
<Page ...
Name="thePage">
<Page.Resources>
...
<DataTemplate x:Key="DetailsTemplate">
<Grid>
<views:XXXDetailControl
DetailMenuItem="{Binding}"
InstallClicked="{Binding ViewModel.InstallCommand, ElementName=thePage}" ... />
</Grid>
...
</DataTemplate>
</Page.Resources>
This seems to work for a ListView.

Button Command not execute function

So I have created a button and I link it to an ICommand NavigateToVM but it does not hit the execute function when I try to click the button. I am I doing something wrong. I'll post the code that is relevant. Thanks in advance.
The Profile button is the button I am trying to get to work. It is just a standard Icommand.
{
public class NavigationBarVM : BaseViewModel
{
public ICommand NavigateToVMCmd { get; set; }
public NavigationBarVM()
{
}
public NavigationBarVM( NavigationStore _navigationStore)
{
NavigateToVMCmd = new NavigateToProfileCommand(this, _navigationStore);
}
}
}
namespace WpfNotes.Commands
{
public class NavigateToProfileCommand : CommandBase
{
private readonly NavigationBarVM navBarVM;
private readonly NavigationStore _navigationStore;
public NavigateToProfileCommand(NavigationBarVM VM, NavigationStore navigationStore)
{
navBarVM = VM;
_navigationStore = navigationStore;
}
public override void Execute(object parameter)
{
_navigationStore.CurrentViewModel = new ProfileVM();
Debug.WriteLine("Stuff");
}
}
}
<UserControl x:Class="WpfNotes.View.NavigationBar"
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:WpfNotes.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
xmlns:VM ="clr-namespace:WpfNotes.ViewModel">
<UserControl.Resources>
<VM:NavigationBarVM x:Key="vm"/>
</UserControl.Resources>
<Border BorderBrush="Black" BorderThickness="2">
<UniformGrid Columns="4" Height="40">
<Button BorderThickness="0" Content="Profile" Command="{Binding Source={StaticResource vm}, Path = NavigateToVMCmd }"></Button>
</UniformGrid>
</Border>
</UserControl>
You should instantiate the NavigationBarVM programmatically and inject it with a NavigationStore:
public partial class NavigationBar : UserControl
{
public MainWindow()
{
InitializeComponent();
DataContext = new NavigationBarVM(new NavigationStore());
}
}
You can then bind directly to the command of the view model:
<Button BorderThickness="0" Content="Profile"
Command="{Binding NavigateToVMCmd}" />
The following initializes the NavigationBarVM using the default constructor which is pretty useless and should be removed in your case as it doesn't initialize the command:
<VM:NavigationBarVM x:Key="vm"/>

Adding initialized ViewModels to ObservableCollection displaying null property values

When adding ViewModels to an ObservableCollection, which is shown on the MainWindow as an ItemsControl with the ObservableCollection as the ItemsSource. The initial values of the View are displayed as null. I know this because on debugging and changing the value of the TextBox I see that the Name field is set to null, but when I press the button to add new ViewModels it is setting the Name field but then not displaying the name. This app has been condensed for ease of debugging. So it seems that while the ObservableCollection is communicating to the view it is not receiving the proper values somehow.
MainWindow
<Window x:Class="LifeCalculator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:myControl="clr-namespace:LifeCalculator.Views" xmlns:views="clr-namespace:LifeFinanceCalculator.Views"
Title="{Binding Title}" Height="350" Width="525">
<Grid>
<Button Margin="47,24,373,269" Content="Add ViewModels" Command="{Binding AddCommandItem}"/>
<ScrollViewer Margin="28,75,37,72">
<ItemsControl ItemsSource="{Binding ListExampleItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:exampleView/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
MainWindowViewModel
using Prism.Mvvm;
using System.Collections.ObjectModel;
using LifeFinanceCalculator.ViewModels;
using System.Windows.Input;
using Prism.Commands;
namespace LifeCalculator.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private ObservableCollection<exampleViewModel> _listExampleItems;
public ObservableCollection<exampleViewModel> ListExampleItems
{
get => _listExampleItems;
set
{
SetProperty(ref _listExampleItems, value);
}
}
public ICommand AddCommandItem { get; set; }
public MainWindowViewModel()
{
_listExampleItems = new ObservableCollection<exampleViewModel>();
AddCommandItem = new DelegateCommand(ListItem);
}
private void ListItem()
{
_listExampleItems.Add(new exampleViewModel() { Name = "Chris" });
_listExampleItems.Add(new exampleViewModel() { Name = "Olivia" });
}
}
}
exampleView
<UserControl x:Class="LifeFinanceCalculator.Views.exampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBox Text="{Binding Name}" FontSize="22"/>
</Grid>
</UserControl>
exampleViewModel
using Prism.Mvvm;
namespace LifeFinanceCalculator.ViewModels
{
public class exampleViewModel : BindableBase
{
private string _name;
public string Name
{
get => _name;
set
{
SetProperty(ref _name, value);
}
}
public exampleViewModel()
{
}
}
}
The ViewModelLocator used by Prism was the issue. The ViewModelLocator re-intializes the View with a new ViewModel. To solve the issue :
prism:ViewModelLocator.AutoWireViewModel = "False"

How to set DataBinding for UserControl with ViewModel

I have a usercontrol with couple of controls inside. So I decide to use ViewModel to do managing for all those bindable value. But I find my binding is always null. So how to setup binding for ViewModel in usercontrol
MainWindows.xaml
<Window x:Class="Test.MainWindow"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<cus:Wizard WizardModel="{Binding MyModel}"/>
</StackPanel>
</Window>
MainWindows.xaml.cs
public partial class MainWindow : Window
{
private ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
}
}
ViewModel.cs(MainWindow viewmodel)
public class ViewModel : INotifyPropertyChanged
{
private Model _MyModel;
public Model MyModel
{
get
{
return _MyModel;
}
set
{
_MyModel = value;
NotifyPropertyChanged("MyModel");
}
}
}
Wizard.xaml(my UserControl)
<UserControl mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Something}" />
</Grid>
</UserControl>
Wizard.xaml.cs
public partial class Wizard : UserControl
{
private readonly object modelLock = new object();
private Model CurrentModel = new Model();
public Wizard()
{
InitializeComponent();
DataContext = CurrentModel;
}
public Model WizardModel
{
get { return (Model)this.GetValue(WizardModelProperty); }
set { this.SetValue(WizardModelProperty, value); }
}
public static readonly DependencyProperty WizardModelProperty = DependencyProperty.Register("WizardModel", typeof(Model), typeof(Wizard), new PropertyMetadata(null, new PropertyChangedCallback(ModelChanged)));
private static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Wizard)d).OnModelChanged();
}
private void OnModelChanged()
{
lock (this.modelLock)
{
if(CurrentModel != null)
{
CurrentModel = null;
}
if (WizardModel != null)
{
CurrentModel = WizardModel;
}
}
}
}
The WizardModel in UserControl is always null. So how to setup this ViewModel in UserControl
A UserControl that is supposed to operate on a particular view model class - or more precisely on a class with a particular set of public properties - may directly bind to the view model properties in its XAML.
Given a view model like
public class Model
{
public string Something { get; set; }
}
you may write a UserControl with nothing more than this XAML
<UserControl ...>
...
<TextBox Text="{Binding Something}" />
...
</UserControl>
and this code behind
public partial class Wizard : UserControl
{
public Wizard()
{
InitializeComponent();
}
}
If you now set its DataContext to an instance of Model (or any other class with a Something property), it will just work:
<local:Wizard DataContext="{Binding MyModel}"/>
Since the value of the DataContext property is inherited from parent to child elements, this will also work:
<StackPanel DataContext="{Binding MyModel}">
<local:Wizard/>
</StackPanel>
However, the UserControl still dependends on the existence of a Something property in its DataContext. In order to get rid of this dependence, your control may expose a dependency property
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register(nameof(MyText), typeof(string), typeof(Wizard));
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
and bind the element in its XAML to its own property
<UserControl ...>
...
<TextBox Text="{Binding MyText,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
...
</UserControl>
Now you would bind the control's property instead of setting its DataContext:
<local:Wizard MyText="{Binding MyModel.Something, Mode=TwoWay}"/>

How to excecute a command declared in child viewmodel in an MVVM app?

I've got a MainWindowVM and multiple child viewmodels inheriting from it.
MainWindowVM inherits from ViewModelBase which implements INotifyPropertychanged.
Each view has DataContext set to CurrentViewModel defined in MainWindowVM and every button
has got a binding to a command.
If I put the commands (and other command-handling code in the constructor) in the MainWindowVM,
button clicks in every view works as expected. I set MainControlVM as CurrentViewModel in the constructor of MainWindowVM.
Except for MainControlVM and MainWindowVM, setting commands in any other VM means they wont execute.
However, I want to have commands only in the VMs they are used.
I found many tutorials on MVVM with only one or two viewmodels so this situation isnt an issue for them.
Edit including code:
This is the relevant code:
Part of one of the child views in XAML with a binding:
<Grid DataContext="{Binding CurrentViewModel}" Margin="0,0,-186,0">
<Button Content="Add" HorizontalAlignment="Left" Margin="25,249,0,0" VerticalAlignment="Top" Width="62" Height="32"
Command="{Binding AddCategoryVMCommand}" />
MainWindowVM class contains:
public ICommand AddCategoryVMCommand { get; private set; }
and, in the constructor:
AddCategoryVMCommand = new RelayCommand(() => ExecuteAddCategoryVMCommand());
and:
protected void ExecuteAddCategoryVMCommand()
{
CurrentViewModel = new AddCategoryVM();
}
....and the same kind of code for each command. Aso, CurrentViewModel is set in the MainWindowVM class. This is the property that the MainWindow view uses to determine which view to display along with a datatemplate:
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
this.RaiseNotifyPropertyChanged("CurrentViewModel");
}
}
How can I make commands execute when declared in child viewmodel?
There are a lot of comments going on, all out of sync and they appear to convolute the issue so I thought I would try to solve your problem with a basic example. The example deals solely with the command binding issue you appear to have.
I have created 3 ViewModel's, MyViewModel1 and MyViewModel2 are derived of MyViewModel. There is a command defined in the base ViewModel which is used to load the CurrentViewModel. The other 2 ViewModels contain their own commands.
public class MyViewModel : INotifyPropertyChanged
{
private MyViewModel currentViewModel;
public RelayCommand<object> MyCommand { get; set; }
public MyViewModel()
{
MyCommand = new RelayCommand<object>(MyCommandExecute);
}
public MyViewModel CurrentViewModel
{
get { return currentViewModel; }
set
{
if (value != currentViewModel)
{
currentViewModel = value;
OnPropertyChanged();
}
}
}
protected virtual void MyCommandExecute(object obj)
{
switch (int.Parse(obj.ToString()))
{
case 1:
CurrentViewModel = new MyViewModel1();
break;
case 2:
CurrentViewModel = new MyViewModel2();
break;
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MyViewModel1 : MyViewModel
{
public RelayCommand<object> MyCommand1 { get; set; }
public MyViewModel1()
{
MyCommand1 = new RelayCommand<object>(MyCommand1Execute);
}
private void MyCommand1Execute(object obj)
{
Debug.WriteLine("MyCommand1");
}
}
public class MyViewModel2 : MyViewModel
{
public RelayCommand<object> MyCommand2 { get; set; }
public MyViewModel2()
{
MyCommand2 = new RelayCommand<object>(MyCommand2Execute);
}
private void MyCommand2Execute(object obj)
{
Debug.WriteLine("MyCommand2");
}
}
The code behind the UserControl1 is
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel1), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public MyViewModel1 ViewModel
{
get { return GetValue(ViewModelProperty) as MyViewModel1; }
set { SetValue(ViewModelProperty, value); }
}
}
I have created the ViewModel Property as a DependencyProperty so I can bind to it from the MainWindow.
The Xaml of the user control is
<UserControl x:Class="StackOverflow._20937791.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" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:this="clr-namespace:StackOverflow._20937791"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<StackPanel DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
<Button Content="View 1 Command" Command="{Binding Path=MyCommand1}" />
</StackPanel>
</UserControl>
Note I have set up the DataContext on the first content element of the control. The bindings on all child elements are against the ViewModel of the UserControl while any incoming bindings (from the parent control) will be evaluated from the DataContext of that parent control.
Another point to note is that by defining the DataContext in the Xaml, you will get autocomplete in the Binding expressions which will cut down on bad expression errors.
The second UserControl is the same but the ViewModel is of type MyViewModel2.
Finally, the code for the MainWindow is
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
The Xaml is
<Window x:Class="StackOverflow._20937791.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:StackOverflow._20937791"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="200" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type this:MyViewModel1}">
<this:UserControl1 ViewModel="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type this:MyViewModel2}">
<this:UserControl2 ViewModel="{Binding}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Show View 1" Command="{Binding Path=MyCommand}" CommandParameter="1" Width="100" Margin="4" />
<Button Content="Show View 2" Command="{Binding Path=MyCommand}" CommandParameter="2" Width="100" Margin="0 4" />
</StackPanel>
<ContentControl Content="{Binding Path=CurrentViewModel}" Margin="20" />
</StackPanel>
</Window>
The UserControl is referenced in the main window and it has its ViewModel passed in.
The application shows a window that looks like
I hope this helps.
Firt, FYI - your approach is called the strategy pattern.
Now what you are doing sounds right but it's hard withou seeing your xaml.
Maybe you need to raise a propertychanged event after setting your vm properties?
It would be helpful if you would post your code .But if I havent misunderstood your question then you can try this
<Button Command="{Binding MainControlVM.ClickCommand}"
Set the binding MainControlVM.ClickCommand .Here ClickCommand is the name of your Command.
Update
I think the issue is in Setting the CurrentViewModel. You are setting the CurrentViewModel in the Action Of Command. I think you want to set the CurrentViewModel on the basis of Command. I think this could be better by CommandParameter . Like Bind all Buttons to same Base ViewModel Command and from each Command pass the different CommandParameter and then on Command compare that CommandParameter and set CurrentViewModel accordingly.
ViewModelBase ,Child1ViewModel ,Child2ViewModel
public class ViewModelBase:INotifyPropertyChanged
{
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(MyAction,()=>true));
}
}
public void MyAction(object obj)
{
if(obj == null )
return;
//if CommandParameter is Cild1VM
if (obj.ToString() == "Child1VM")
CurrentViewModel = new Child1ViewModel();
//if CommandParameter is Cild1VM
else if (obj.ToString() == "Child2VM")
CurrentViewModel = new Child2ViewModel();
}
ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
this.RaiseNotifyPropertyChanged("CurrentViewModel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaiseNotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class Child1ViewModel : ViewModelBase
{ }
public class Child2ViewModel : ViewModelBase
{ }
xaml
<StackPanel>
<Button Content="Foo" Command="{Binding ClickCommand}" CommandParameter="Child1VM"/>
<Button Content="Bar" Command="{Binding ClickCommand}" CommandParameter="Child2VM"/>
</StackPanel>
xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModelBase();
}
}
I hope this will give you an idea.

Categories