WPF Binding and Observable Model fail - c#

In a simple trying-to-learn-WPF experiment I'm trying to bind a property ("InternalName") of an instance of MyModel to the contents of TextBlock "MainWindowTextBlock". Clicking the ``ChangeNameButton" changes the InternalName property of mymodel, but that property change never makes it through to the TextBlock. Nothing happens. What am I doing wrong?
XMAL
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:UserControlExperiments"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Grid.Row ="0">
<Button Width="100" Height="20" Name="ChangeName" Content="Change the Name" Click="ChangeNameButtonClick"/>
<TextBlock Text=""/>
<TextBlock Name="MainWindowTextBox" Width="100" Height="20" Text="{Binding Path = mymodel.InternalName, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Window>
CODE BEHIND
public partial class MainWindow : Window
{
public MyModel mymodel;
public MainWindow()
{
InitializeComponent();
DataContext = this.DataContext;
mymodel = new MyModel("The old name");
}
private void ChangeNameButtonClick(object sender, RoutedEventArgs e)
{
mymodel.InternalName = "A new name!";
}
}
public class MyModel : INotifyPropertyChanged
{
private string internalname;
public event PropertyChangedEventHandler PropertyChanged;
public MyModel(string nm)
{
InternalName = nm;
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string InternalName
{
get { return internalname; }
set
{
if (internalname != value)
{
internalname = value;
OnPropertyChanged("InternalName");
}
}
}
}
}

The following markup tries to bind to a property named "mymodel" of the current DataContext of the TextBlock, which is inherited from the parent window:
<TextBlock Name="MainWindowTextBox"
Text="{Binding Path = mymodel.InternalName}"/>
So you need to set the DataContext of the window to itself:
DataContext = this;
And you also need to make mymodel a public property since you cannot bind to fields:
public MyModel mymodel { get; }
Then it should work but you probably also want to change the name of the property to comply with the C# naming standards.
You can also remove Mode=TwoWay from the binding. It makes no sense for a TextBlock.

Related

WPF Usercontrol Bindings with MVVM ViewModel not working

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;
}
}
}

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}"/>

WPF control binding to a single variable

I can't seem to bind a control's value to an object. I want to bind a TextBox to a string object, the idea is that when textbox's text changes, it should automatically change the object as well. couldn't figure out what I'm doing wrong. Here is what I have tried:
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
string str;
public MainWindow()
{
InitializeComponent();
this.DataContext = str;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
and MainWindow.xaml:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="150">
<Grid Margin="0,0,642,319">
<TextBox HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="{Binding str}" VerticalAlignment="Top" Width="120" Margin="0,0,-120,-46" />
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click" Height="23" Margin="0,28,-75,-51" RenderTransformOrigin="0.423,2.257" />
</Grid>
</Window>
So, when I enter something to the textbox and click the button, I should see the text in str while debugging but it is always null
Change the str to a auto property:
public string str { get; set; }
Change the DataContext to:
DataContext = this;
The DataContext is the class which will hold your binding properties/commands/events.
The properties/commands/events need to be public in order to be accessible by your view.
For the two-way binding to work, you have to notify to the UI binding that the property has been changed and for that you need to implement the INotifyPropertyChanged interface for the class which holds the properties which have been bound in the UI. You will need a private property and you cannot notify from a auto-property.
Simple Example:
public class Sample : INotifyPropertyChanged
{
private string _str;
public string Str
{
get { return _str; }
set
{
_str = value;
NotifyPropertyChanged(nameof(Str));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
First, data bindings in WPF only work with public properties. So you must explicitely declare one in your code behind (instead of string str;)
public string str { get; set; }
Second, the DataContext property of a view defines the object / class in which the property will be searched for the bindings. The line this.DataContext = str; in your example means that you want your bindings in the view to be looked for inside the str object (which is a string). You should replace this line by
this.DataContext = this;
so that the bindings will be searched inside the code behind of this view itself.
Remark
You could also stay with the line this.DataContext = str; if str is a public property and bind using an expression such as
<TextBox Text="{Binding .}" />
which will bind to the value of the DataContext property.
Maybe you can use MVVM light to do the binding.

Cannot get internal XAML binding to work against a Dependency Property

I have a user control "CtrlComments", this control has the following XAML (It's super basic).
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:wpftoolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
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"
x:Name="ucRoot">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="ID: " />
<TextBlock Text="{Binding Path=Deployment.Id}" />
</StackPanel>
</Grid>
The code behind is as follows, it's the bare basics to get the control to function. The key is the DependencyObject typeof(DeploymentDto) which has an int property called Id that we are interested in showing on our window as per XAML binding above.
public partial class CtrlComments : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty DeploymentProperty =
DependencyProperty.Register("Deployment", typeof(DeploymentDto),
typeof(CtrlComments), new PropertyMetadata(new DeploymentDto()));
public DeploymentDto Deployment
{
get
{
return (DeploymentDto)GetValue(DeploymentProperty);
}
set
{
SetValue(DeploymentProperty, value);
OnPropertyChanged(new PropertyChangedEventArgs("Deployment"));
}
}
public CtrlComments()
{
InitializeComponent();
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
Our problem is, despite the fact that the binding between the parent control and my user control via the dependency property is working (verified) and the OnPropertyChanged method firing, the TextBlock in my XAML isn't updating.
I have noticed that when the OnPropertyChanged method is run, the eventhandler is null meaning no one is notified that there was a property change.
I don't understand why this is the case though. If you could help explain where we are going wrong it would be enormously appreciated.
Thanks!
I have tried to replicate your problem and while doing so, I figured that the problem for me was in the following line in CtrlComments:
this.DataContext = this;
Dropping this line just made it work for me. Also note (as #Aron wrote in the comments) that the OnPropertyChanged of INotifyPropertyChanged shouldn't be called while in the setter of the DependencyProperty. At least for me it isn't necessary to implement INPC at all.
In the XAML file where you are using the UserControl you are most likely going to have another DataContext set (on a higher level, perhaps in the Window), and thus I guess it isn't inherited to the user control if already set in there (or overwritten). Below is my working code, but perhaps I misunderstood exactly what you're doing. If that is the case, please extend your question to include how you are using the UserControl, as that is a key to answering the question if this doesn't work :)
CtrlComments.xaml:
<UserControl x:Class="WpfApplication1.CtrlComments"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="ID: "/>
<TextBlock Text="{Binding Path=Deployment.Id}"/>
</StackPanel>
</Grid>
</UserControl>
CtrlComments.xaml.cs:
namespace WpfApplication1
{
public partial class CtrlComments : UserControl
{
public static readonly DependencyProperty DeploymentProperty =
DependencyProperty.Register("Deployment", typeof(DeploymentDto), typeof(CtrlComments), new PropertyMetadata(new DeploymentDto { Id = 5 }));
public DeploymentDto Deployment
{
get { return (DeploymentDto)GetValue(DeploymentProperty); }
set
{
SetValue(DeploymentProperty, value);
}
}
public CtrlComments()
{
InitializeComponent();
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication1"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<local:CtrlComments x:Name="testUC" Height="100" Deployment="{Binding Deployment}"/>
<Button Click="Button_Click" Height="50" Width="100"/>
</StackPanel>
</Window>
MainWindow.xaml.cs:
namespace WpfApplication1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
}
private DeploymentDto deployment = new DeploymentDto { Id = 2 };
public DeploymentDto Deployment
{
get { return deployment; }
set { deployment = value; OnPropertyChanged("Deployment"); }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Deployment = new DeploymentDto { Id = new Random().Next(100) };
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}
DeploymentDto:
public class DeploymentDto
{
public int Id { get; set; }
}
It's quite ugly to bind MainWindow.DataContext to its code-behind, but since it's just used for example purposes I hope it's okay :)

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