WPF binding doesn't work when property changes - c#

This is my first question here, please understand. I've spent on this problem hours of digging nothing works for me, maybe somebody will explain me this strange (for me) problem?
I've made my app in WPF with MVVM
I got in MainWindow.xaml with usercontrol which loads view with binding:
<UserControl Content="{Binding CurrentView}" />
MainWindow DataContext is MainViewModel, which derives from BaseViewModel, where i set and get CurrentView from and implement INotifyPropertyChanged.
First CurrentView is LoginViewModel - it loads in constructor of MainViewModel properly and set the view (Usercontrol Loginview.xaml).
And I don't understand why when I change CurrentView property from this loaded LoginViewModel (it definitely changes - I checked it and NotifyPropertyChanged raises) - my view doesn't change - it's still LoginView, but should be WelcomeView.
But when I change the same property with the same code from MainViewModel - my view changes properly. Somebody could point where's an error? Is it impossible to change CurrentView property from outside of MainViewModel even it's not the part of MainViemodel but another class or what? What I'm missing here?
CODE:
public class BaseViewModel : NotifyPropertyChanged
{
private object? _currentView;
public object? CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged(nameof(CurrentView));
}
}
}
public class MainViewModel : BaseViewModel
{
public LoginViewModel LoginViewModel { get; set; }
public WelcomeViewModel WelcomeViewModel { get; set; }
[..]
public ICommand LoginCommand { get; set; } //- this works
public MainViewModel()
{
LoginViewModel = new();
WelcomeViewModel = new();
CurrentView = LoginViewModel;
// COMMANDS
LoginCommand = new RelayCommand(o => DoLogin(), o => CanLogin()); // this works
}
private bool CanLogin()
{
return true;
}
private void DoLogin()
{
CurrentView = WelcomeViewModel;
}
}
public class LoginViewModel : BaseViewModel
{
[...]
public WelcomeViewModel WelcomeViewModel { get; set; }
// COMMANDS PROPERTIES
public ICommand LoginCommand { get; set; }
public LoginViewModel()
{
WelcomeViewModel = new();
LoginCommand = new RelayCommand(o => DoLogin(), o => CanLogin());
}
private bool CanLogin()
{
return true;
}
private void DoLogin()
{
MessageBox.Show("Login!"); // message box test showes
// here will be some authentication
CurrentView = WelcomeViewModel; // property CurrentView changes
// CurrentView = new MainViewModel().WelcomeViewModel; // this way also doesn't work
}
}
and finally XAML from UserControl LoginView.xaml (command runs properly, property CurrentView changes, but view remains the same:
<Button
Width="200"
Height="50"
Margin="10"
Command="{Binding LoginCommand}"
Content="Login"
FontSize="18" />
<!-- Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}},
Path=DataContext.LoginCommand}" THIS WORKS! -->
App.xaml has:
<DataTemplate DataType="{x:Type vievmodels:LoginViewModel}">
<viewscontents:LoginView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vievmodels:WelcomeViewModel}">
<viewscontents:WelcomeView/>
</DataTemplate>

The question is: how to set DataContext ....
Do this.
Main ViewModel:
public class MainViewModel : BaseInpc
{
#region CurrentContent
private object? _currentContent;
public object? CurrentContent { get => _currentContent; set => Set(ref _currentContent, value); }
private RelayCommand _setCurrentCommand;
public RelayCommand SetCurrentCommand => _setCurrentCommand
??= new RelayCommand(content => CurrentContent = content);
#endregion
public LoginViewModel LoginViewModel { get; } = new LoginViewModel();
public WelcomeViewModel WelcomeViewModel { get; } = new WelcomeViewModel();
public MainViewModel()
{
CurrentContent = WelcomeViewModel;
}
}
public class WelcomeViewModel: BaseInpc // Derived not from MainViewModel!
{
// Some Code
}
public class LoginViewModel: BaseInpc // Derived not from MainViewModel!
{
// Some Code
}
Create an instance of MainViewModel in the application resources:
<Application.Resources>
<local:MainViewModel x:Key="mainVM"/>
</Application.Resources>
In Windows XAML:
<Window ------------
------------
DataContext="{DynamicResource mainVM}">
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginViewUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type WelcomeViewModel}">
<local:WelcomeViewUserControl/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentContent}"/>
An example of a button toggling CurrentContent:
<Button Command="{Binding SetCurrentCommand, Source={StaticResource mainVM}}"
CommandParameter="{Binding LoginViewModel, Source={StaticResource mainVM}}"/>
BaseInpc and RelayCommand classes.

The mistake you make is that there are two CurrentView variables. One is in MainViewModel and the other one is in LoginViewModel. Both classes are derived from BaseViewModel but that doesn't mean they share the same instance of CurrentView. Both have a newinstance of the CurrentView variable. Meaning that only one is bound to the DataContext of the page.
What i'm unable to see it where you assign which CurrentView to the DataContext. So i'm not able to completely answering this question.
But it looks like you have 1 window filled with 2 controls.
To solve this, you should create a 3rd ViewModel which only contains the CurrentView. Use this instance on a parent where the UserControl is used. And use the other ViewModels to the usercontrol itself.

Related

How can i bind to the sub-ViewModel using ViewModel inheritance?

In my application i have the following MasterViewModel1-class.
public class MasterViewModel1 : ViewModelBase
{
private ObservableCollection<ObservableObject> _MainGrid;
public ObservableCollection<ObservableObject> MainGrid
{
get => _MainGrid;
set
{
_MainGrid = value;
RaisePropertyChanged();
}
}
public ObservableCollection<FilterItem> FilterItems
{
get;
set;
}
public MasterViewModel1()
{
CreateDefaultMenu();
}
public void CreateDefaultMenu()
{
FilterItems = new ObservableCollection<FilterItem>
{
new FilterItem(OnFilterClicked)
{
Content = "Filter"
},
new FilterItem(OnFilterCancelClicked)
{
Content = "Filter aufheben"
}
};
}
public virtual void OnFilterClicked() { }
public virtual void OnFilterCancelClicked() { }
The MasterViewModel1-class is inherited by the TestViewModel-class.
public class TestViewModel : MasterViewModel1
{
private Kunde _NeuerKunde;
public Kunde NeuerKunde
{
get => _NeuerKunde;
set => _NeuerKunde = value;
}
private string _Kundenmatchcode;
public string Kundenmatchcode
{
get => _Kundenmatchcode;
set
{
_Kundenmatchcode = value;
RaisePropertyChanged();
}
}
public TestViewModel()
{
NeuerKunde = new Kunde();
}
}
I use the MasterViewModel1-class and its view for reusable reasons, because in the future there will be many more views which will inherit the MasterViewModel.
Inside the MasterView in need to bind to both, the MasterViewModel, so i have the "Base-Design".
And i need to bind to the "Sub"ViewModel, in this example the TestViewModel.
View of the MasterViewModel1
In the image u can see the MasterView. The red marked region is the place where the TestViewModel (TestView) should be placed. I can't use staticresource!!! It have to be dynamic, so if i instanciate another ViewModel, which also inherites from MasterViewModel1. The red marked region should change depending on the instantiated ViewModel.
I hope it's clear enought.
If u need further informations please ask.
Generally, all public properties of a superclass are visible and accessible via every subclass. You can bind to every public property.
If you want to change the layout or appearance of a view based on the actual implementation or type, you should use a DataTemplate which describes how the view is structured and bound to the model's data.
A simple ContentControl will serve as the dynamic view host.
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private ViewModelBase currentView;
public ViewModelBase CurrentView
{
get => this.currentView;
set
{
this.currentView= value;
OnPropertyChanged();
}
}
public ICommand ToggleViewCommand => new RelayCommand(param => this.CurrentView = this.Views.FirstOrDefault(view => view != this.CurrentView));
private List<ViewModelBase> Views { get; }
public MainViewModel()
{
this.Views = new ObservableCollection<ViewModelBase>()
{
new TestViewModel() { Value = "TestViewModel View" },
new AnotherTestViewModel() { Name = "AnotherTestViewModel View" }
}
this.CurrentView = this.Views.First();
}
}
TestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string value;
public string Value
{
get => this.value;
set
{
this.value = value;
OnPropertyChanged();
}
}
}
AnotherTestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
}
TestView.xaml
<Window>
<Window.DataContext>
<TestViewModel />
</Window.DataContext>
<TextBlock Text="{Binding Value}" />
</Window>
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<!-- Define the views as an implicit (keyless) DataTemplate -->
<DataTemplate DataType="{x:Type TestViewModel}">
<!-- Show a view as a UserControl -->
<TestView />
</DataTemplate>
<DataTemplate DataType="{x:Type AnotherTestViewModel}">
<!-- Or add a elements -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Rectangle Height="80" Width="80" Fill="Red" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Command="{Binding ToggleViewCommand}" Content="Toggle View" />
<!--
Host of the different views based on the actual model type (dynamic view).
The implicit DataTemplates will apply automatically
and show the view that maps to the current CurrentView view model type
-->
<ContentControl Content="{Binding CurrentView}" />
</StackPanel>
</Window>

Binding Command to ViewModel inside UserControl

I want to bind a UserControl to a ViewModel to use Commands/Events.
My application consists of a MainWindow with a ContentControl inside, which is used to display a UserControls (the actual content) for navigation purposes.
MainWindow.xaml
<Window>
<Window.Resources>
<DataTemplate DataType="">
<View: />
</DataTemplate>
</Window.Resources>
<Menu>
<MenuItem Header="Connection" Command="..." />
</Menu>
<Grid>
<ContentControl Content="{Binding SelectedViewModel}" />
</Grid>
</Window>
MainViewModel.cs
class MainViewModel : ViewModelBase {
public ICommand MenuCommand;
private object _SelectedViewModel;
public object SelectedViewModel
{
get { return _SelectedViewModel; }
set
{
_SelectedViewModel = value;
RaisePropertyChanged("SelectedViewModel");
}
}
public MainViewModel()
{
ICommand = new RelayCommand(MenuClick);
}
private void MenuClick(object obj)
{
SelectedViewModel = new ConnectionViewModel();
}
}
This is how the navigation of my app works. The only problem I'm having is that I can't seem
to use Commands (Button for example) in the UserControl itself.
ConnectionView.xaml
<UserControl>
<Grid>
<Button Command="{Binding ButtonCommand}" Content="Button" />
</Grid>
</UserControl>
ConnectionViewModel.cs
class ConnectionViewModel : ViewModelBase {
public ICommand ButtonCommand;
public ConnectionViewModel()
{
ButtonCommand = new RelayCommand(ButtonClick);
}
private void ButtonClick(object obj)
{
MessageBox.Show("Clicked");
}
}
I can fill ListViews in the UserControl View but I can't get the Button Command working. What exactly is the problem, where did I go wrong?
ButtonCommand must be a property for you to be able to bind to it:
public ICommand ButtonCommand { get; private set; }
You have defined it as a field:
public ICommand ButtonCommand;

How to bind a ReactiveList of view models to a ListView containing Views of the view model

I have a ParentViewModel which contains a ReactiveList of ChildViewModels. I would like to bind it to a ListView which will display ChildViews. ChildView binds some text to a Label and a sets an Image resource based on a status enum:
ParentViewModel.cs Simple container for ChildViewModels
public class ParentViewModel : ReactiveObject
{
public ReactiveList<ChildViewModel> Children { get; }
= new ReactiveList<ChildViewModel>();
}
ChildViewModel.cs Has some text and a Status
public class ChildViewModel : ReactiveObject
{
public string SomeText { get; set; }
public enum Status
{
Unknown,
Known
}
public Status CurrentStatus { get; set; } = Status.Unknown;
}
ParentView.xaml UserControl which wraps a ListView, using ChildView for the data template. I made sure to add the ViewModel="{Binding}" on my DataTemplate.
<UserControl x:Class="MyProject.UI.ParentView"
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:vm="clr-namespace:MyProject.View_Models"
xmlns:ui="clr-namespace:MyProject.UI">
<ListView x:Name="ChildList" BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<!--I have also tried the following line, didn't work -->
<!--<DataTemplate DataType="{x:Type vm:ChildViewModel}">-->
<ui:ChildView ViewModel="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
ParentView.xaml.cs Creates a dependency property for its ParentViewModel, and binds the view model's Children to the ChildList ListView
public partial class ParentView : UserControl, IViewFor<ParentViewModel>
{
public static readonly DependencyProperty ViewModelProperty
= DependencyProperty.Register(
"ViewModel",
typeof(ParentViewModel),
typeof(ParentView));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (ParentViewModel)value; }
}
public ParentViewModel ViewModel
{
get { return (ParentViewModel )GetValue(ViewModelProperty); }
set
{
SetValue(ViewModelProperty, value);
BindToViewModel(value);
}
}
private void BindToViewModel(ParentViewModel viewModel)
{
this.OneWayBind(viewModel, vm => vm.Children, v => v.ChildList.ItemsSource);
}
}
ChildView.xaml Simple UserControl with an Image and a Label
<UserControl x:Class="MyProject.UI.ChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal">
<Image Name="StatusIcon" Margin="2" />
<Label Name="DisplayName" />
</StackPanel>
</UserControl>
ChildView.xaml.cs Binds the ChildViewModel and sets image data
public partial class ChildView : UserControl, IViewFor<ChildViewModel>
{
public static readonly DependencyProperty ViewModelProperty
= DependencyProperty.Register(
"ViewModel",
typeof(ChildViewModel),
typeof(ChildView));
public ChildView()
{
InitializeComponent();
}
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (ChildViewModel)value; }
}
public ChildViewModel ViewModel
{
get { return (ChildViewModel )GetValue(ViewModelProperty); }
set
{
SetValue(ViewModelProperty, value);
BindToViewModel(value);
}
}
private void BindToViewModel(ChildViewModel viewModel)
{
viewModel
.WhenAnyValue(vm => vm.CurrentStatus)
.Subscribe(status =>
{
// set StatusIcon's image based on status
});
this.Bind(viewModel, vm => vm.DisplayName, v => v.SomeText);
}
}
I have set breakpoints in ChildView to see if the ViewModel properties hit, but they never do. When I add ChildViewModels to ParentVieWModel.Children, a ChildView is created, but it is never bound properly. I could subscribe to Children's CollectionChanged event and manually set the binding, but I would like to do this the proper XAML/ReactiveUI way. What am I missing?
I think you need to fully set up your properties in your ChildViewModel so that they raise changes in the ViewModel that your ChildView can subscribe to. See ViewModels documentation.
string someText ;
public string SomeText {
get { return someText; }
set { this.RaiseAndSetIfChanged(ref someText , value); }
}
Status currentStatus;
public Status CurrentStatus {
get { return currentStatus; }
set { this.RaiseAndSetIfChanged(ref currentStatus, value); }
}
Or if one or both are readonly.
readonly ObservableAsPropertyHelper<string> someText;
public string SomeText{
get { return someText.Value; }
}
readonly ObservableAsPropertyHelper<Status> currentStatus;
public Status CurrentStatus{
get { return currentStatus.Value; }
}

WPF Application With Multiple Child Views - MVVM

My problem is quite simple, I want to have a MainView which in turn will have multiple Views which are dynamic and intractable, like in the diagram below:
But to do this you need multiple ViewModels, and I do not know how to organise them.
My original Idea is to have a MainViewModel, within which I will create properties that will return all my ChildViewModels as shown below, but It seems unprofessional to me and a bad practice.
public class MainViewModel : BaseViewModel
{
private EditPropertiesViewModel _editPropertiesViewModel;
public EditPropertiesViewModel EditPropertiesViewModel
{
get { return _editPropertiesViewModel; }
set
{
_editPropertiesViewModel = value;
base.OnPropertyChanged();
}
}
private UsersDetailsViewModel _usersDetailsViewModel;
public UsersDetailsViewModel UsersDetailViewModel
{
get { return _usersDetailsViewModel; }
set
{
_usersDetailsViewModel = value;
base.OnPropertyChanged();
}
}
//etc. etc..
}
Then from My MainView, I would set the Datacontext to the MainViewModel
Please help me I have no idea what to do, I am totally paused right now.
If you wish to achieve this without PRISM you can make use of ContentControl. For every region you create ContentControl and for every ContentControl you create its ViewModel property. Then you manipulate selected ViewModel associated with ContentControl and ContentControl adjusts view based on type of ViewModel assigned. For clarification take a look
XAML:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:SubArticleViewModel}">
<view:SubArticleView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding ArticleViewModel}"/>
C#
class BaseViewModel : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
class MainViewModel
{
public BaseViewModel ArticleViewModel { get; set; }
}
class SubArticleViewModel : BaseViewModel
{
}
Whenever you assign
ArticleViewModel = new SubArticleViewModel();
DataTemplate defined as resource will be placed as Content of Control.
Above way out creates a lots of work and is more vulnerable for omission. PRISM would be a better choice anyway.
Create AppViewModel class with static ctor like this:
class AppViewModel : BaseViewModel
{
static AppViewModel()
{
_AppModel = new AppViewModel();
}
private static AppViewModel _AppModel;
public static AppViewModel Current
{
get { return _AppModel; }
}
private AppViewModel()
{
//Initialize view models here
MainPageModel = new MainPageViewModel();
}
//VIEW MODELS
public MainPageViewModel MainPageModel { get; private set; }
}
Create BaseViewModel class. All of your VM's should be inherited from it:
class BaseViewModel //implement INotifyPropertyChanged if needed
{
public AppViewModel AppModel
{
get { return AppViewModel.Current; }
}
}
So now you can create UserControl called "MainView":
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
//Prevent view updating in Designer
if (DesignerProperties.GetIsInDesignMode(this))
{
return;
}
var mainVM = AppViewModel.Current.MainPageModel;
DataContext = mainVM;
}
}
In the MainWindow.xaml:
<Window x:Class="MVVM_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MVVM_Test.Views"
Title="MainWindow" Height="350" Width="525">
<views:MainView />
</Window>

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