i am new to wpf and xaml and try to change the content of a window (Login -> Main content and main content -> Login) in an WindowsApplication (Xaml, WPF). So far i have the following for this simple login/logout scenario:
BaseViewModel
public class BaseViewModel : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
BaseMainViewViewModel (Base class for setting the MainViewType Property in the MainWindow. It also contains the command to change the property via the button in the MainViews.)
public class BaseMainViewViewModel : BaseViewModel
{
private static MainViewType _CurrentMainView;
private ICommand _SwitchMainViewCommand;
public BaseMainViewViewModel()
{
SwitchMainViewCommand = new RelayCommand(SwitchMainView);
}
public MainViewType CurrentMainView
{
get { return _CurrentMainView; }
set
{
if (value != _CurrentMainView)
{
_CurrentMainView = value;
OnPropertyChanged(nameof(CurrentMainView));
}
}
}
public ICommand SwitchMainViewCommand
{
get { return _SwitchMainViewCommand; }
set { _SwitchMainViewCommand = value; }
}
#region Test
public void SwitchMainView(object param)
{
Debugger.Break();
switch (CurrentMainView)
{
case MainViewType.Login:
CurrentMainView = MainViewType.Main;
break;
case MainViewType.Main:
CurrentMainView = MainViewType.Login;
break;
default:
break;
}
MessageBox.Show("Login/Logout");
}
#endregion Test
LoginViewModel inherites from BaseMainViewViewModel to get access to the CurrentMainView-Property
public class LoginViewModel : BaseMainViewViewModel {}
MainViewModel her the same
public class MainViewModel : BaseMainViewViewModel {}
MainWindowViewModel
public class MainWindowViewModel: BaseMainViewViewModel {}
LoginMainView
public partial class LoginMainView : UserControl
{
public LoginMainView()
{
InitializeComponent();
DataContext = new LoginViewModel();
}
}
Currently i have only one button (Login-Button) in the LoginMainView. If I click this button, the current LoginMainView should be exchanged with the MainMainView.
<Grid>
<Button Content="Main" Background="Red" Command="{Binding SwitchMainViewCommand}" />
</Grid>
MainMainView
public partial class MainMainView : UserControl
{
public LoginMainView()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
here the same (Logout-Button) correspond to LoginMainView...
<Grid>
<Button Content="Logout" Background="Green" Command="{Binding SwitchMainViewCommand}" />
</Grid>
MainWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
In the MainWindow-View i bind the CurrentMainView-Property (MainViewType) from the BaseMainViewViewModel to the contentpresenter, which i will change by clicking the button in the MainMainView/LoginMainView and the ValueConverter shold do the rest.
<Grid>
<StackPanel>
<Label Content="Test" />
<ContentPresenter Content="{Binding CurrentMainView, Converter={view:MainViewValueConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
MainViewType
public enum MainViewType
{
Login = 0,
Main = 1
}
BaseValueConverter
public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter
where T : class, new()
{
private static T _Converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _Converter ?? (_Converter = new T());
}
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
RelayCommand
public class RelayCommand : ICommand
{
private Action<object> _Execute;
private Predicate<object> _CanExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action<object> execute) : this(execute, DefaultCanExecute) { }
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_Execute = execute ?? throw new ArgumentNullException("execute");
_CanExecute = canExecute ?? throw new ArgumentNullException("canExecute");
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
CanExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return (_CanExecute != null) && _CanExecute(parameter);
}
public void Execute(object parameter)
{
_Execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler eventHandler = CanExecuteChangedInternal;
if (eventHandler != null)
{
eventHandler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
_CanExecute = _ => false;
_Execute = _ => { return; };
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
}
When i start the application, the ValueConverter is called and the correct View (LoginMainView) is loaded. I then click on the button in the LoginMainView, the command (SwitchMainView) is executed, but then the content of MainWindow is not changed into MainMainView because the ValueConverter is not used.
What am i doing wrong? Do i have a fundamental understanding problem? Or is it not possible in this way to map the simple login/logout scenario? Or did i simply overlook something? Can someone please tell me what i have forgotten?
Many thanks in advance to the helpers!
You don't need ValueConverter for that. You are on a right track thoug. Take a look here - this is sample application for ReactiveUI framework (which is my favourite).
It has AppBootrsapper (ViewModel of the application). As the framework does some magick around it, the basic idea is:
MainWindow.Xaml:
<Window x:Class="ReactiveUI.Samples.Routing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:rx="clr-namespace:ReactiveUI;assembly=ReactiveUI"
Title="MainWindow" Height="350" Width="525">
<Grid UseLayoutRounding="True" >
<ContentControl Content="{Binding ActiveViewModel}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type LoginViewModel}">
<!-- here you put your content wof login screen, prefereably as seperate UserControl -->
</DataTemplate>
<DataTemplate DataType="{x:Type MainViewModel}">
<!-- here you put your content main screen, prefereably as seperate UserControl -->
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Window>
Then you just set AppBootstrapper.ActiveViewModel = new LoginViewModel() and you have login screen.
If you login, AppBootstrapper.ActiveViewModel = new MainViewModel() and WPF displays main screen.
All that and much more is done by ReactiveUI framwork - only there instead of putting DataTemplates for ViewModels, you register UserControls as views and RoutedViewHost does all the magick. Don't do that on your own, it's inventing the wheel again.
EDIT to answer the comment:
You put AppBootstrapper.ActiveViewModel = new MainViewModel() in your NavigationService. Navigation meaning the thing that changes displayed view. Most common version is a stack, which top is active ViewModel. When you press Back button, you just pop the stack.
This all applies to MVVM model with Model First navigation, which means you first instantiate ViewModel, and navigation service finds the proper view.
You can do this in the other way: View First navigation. There are some tutorials for WPF page navigation. It works exactly the same, but instead of ViewModel, you create a page (a view) which then creates underlying data.
MVVM app model is so popular, because it allows very clean logic and presentation separation (XAML is ONLY about view, ViewModels contain all logic, Models persist the data), which in turn makes it very easy to share logic between platforms. In fact, if you do that correctly, you can use all your ViewModels in apps written in Xamarin, WPF or UWP, just by creating platform-specific views.
To wrap up, WPF allows you to switch in a property data and it will find a view for it automatically (via DataTemplates). Remember about INotifyPropertyChanged and everything will work
Related
I have been attempting to implement a Sudoku game within a WPF application I am making. I found the following site that pretty much gave me the perfect starting point to try and add the sudoku to my app.
There is however a major difference which I didn't think much of at first. The code from this site bases everything off a single Window, no UserControls at all, which in itself isn't an issue. However, my current implementation bases the whole content of the app on a ContentControl element.
To skip useless details, here is my MainWindow.xaml file (with everything unrelated to the issue removed):
<Window x:Class="BasicGameApp.MainWindow.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:BasicGameApp.MainWindow"
xmlns:viewModel="clr-namespace:BasicGameApp.MainWindow.MVVM.ViewModel"
mc:Ignorable="d"
Height="700"
Width="1080"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
ResizeMode="NoResize"
Background="Transparent"
AllowsTransparency="True">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Border>
<Grid>
<ContentControl Grid.Row="1"
Grid.Column="1"
Margin="10"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
</Window>
I based my UI on this YouTube tutorial if anyone is curious.
The MainViewModel.cs looks like this:
namespace BasicGameApp.MainWindow.MVVM.ViewModel
{
class MainViewModel : ObservableObject
{
#region Commands
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand SudokuViewCommand { get; set; }
#endregion
#region ViewModels
public HomeViewModel HomeVM { get; set; }
public SudokuViewModel SudokuVM { get; set; }
#endregion
private object _currentView;
public object CurrentView
{
get => _currentView;
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVM = new HomeViewModel();
SudokuVM = SudokuViewModel.GetInstance(new SudokuView());
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVM;
});
SudokuViewCommand = new RelayCommand(o =>
{
CurrentView = SudokuVM;
});
}
}
}
And here are the ObservableObject RelayCommand classes:
namespace BasicGameApp.MainWindow.Core
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class RelayCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
As you can see, everything is programmed correctly and works as intended EXCEPT, the SudokuViewModel. As you can see from the code, the SudokuViewModel isn't instantiated as a public class (this is from the tutorial I stated at the beginning). From that tutorial, the instantiation of the view and associated viewmodel is done as follows in the App.xaml.cs:
public partial class App : Application
{
public void ApplicationStartup(object sender, StartupEventArgs args)
{
MainWindow mainWindow = new MainWindow(); // Instantiate the main window
mainWindow.ViewModel = ViewModelClass.GetInstance(mainWindow); // Get an instance of the ViewModel and set the View's ViewModel pointer
mainWindow.Show(); // Now display the view
}
}
My App.xaml file isn't empty however and contains the following:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/BasicGameApp.MainWindow;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModel:HomeViewModel}">
<view:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SudokuViewModel}">
<view:SudokuView/>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
Needless to say that the difference between these two setups makes things rather difficult. I attempted to change the DataTemplate to set the ViewModel property on the SudokuView but I can't seem to get it to Bind correctly:
<view:SudokuView ViewmModel="{Binding //The calling ViewModel}"/>
I'm currently out of ideas as to how I can either adapt the sudoku code to work "without" a viewmodel or adapt the DataTemplate to provide the view with the SudokuViewModel.
Please bare in mind that I am initially an Android developper and this is simply to learn new skills on a personal level.
Pretty new to MVVM still learning.
I have a problem with the navigation on MVVM. I want to have a drawer that is on all the page so I build the drawer on the MainWindow.xaml. My problem is to be able to bind the CurrentViewModel when I'm not inside the mainwindow.
MainWindow
<Grid>
<controls:Drawer DataContext="{Binding Navigator}"/>
<ContentControl Content="{Binding Navigator.CurrentViewModel}"/>
</Grid>
My Navigator is bind to a Drawer.xaml
<materialDesign:DrawerHost
IsLeftDrawerOpen="{Binding ElementName=MenuToggleButton, Path=IsChecked}">
<materialDesign:DrawerHost.LeftDrawerContent>
<Button Content="Home" Command="{Binding UpdateCurrentViewModelCommand}" CommandParameter="Home"/>
<Button Content="Admin" Command="{Binding UpdateCurrentViewModelCommand}" CommandParameter="Admin"/>
My 2 button work perfectly to change the main View display. So wen I click on Home or Admin, I have the Home or Admin view.
My Goal is to be able when i'm inside the view Admin to go back to another view.
Example: If you are in admin and you create a new User, I want after you click Send you go back to the Home window.
This is the part i'm not able to do.
To Update de view I created this command
public class UpdateCurrentViewModelCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private INavigator _navigator;
public UpdateCurrentViewModelCommand(INavigator navigator)
{
_navigator = navigator;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
switch(parameter)
{
case "Home":
_navigator.CurrentViewModel = new HomeViewModel();
break;
case "Admin":
_navigator.CurrentViewModel = new AdminViewModel();
break;
default:
break;
}
}
}
And this is the INavigator Interface
public interface INavigator
{
ViewModelBase CurrentViewModel { get; set; }
ICommand UpdateCurrentViewModelCommand { get; }
}
I don't know if it's clear enought to get help.
If not let me know what you need more!
Thanks.
EDIT
public class Navigator : INavigator, INotifyPropertyChanged
{
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
_currentViewModel = value;
OnPropertyChanged(nameof(CurrentViewModel));
}
}
public ICommand UpdateCurrentViewModelCommand => new UpdateCurrentViewModelCommand(this);
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I want to add a method to my custom control which I can call from a button using command binding, in my MainWindow.xaml. I've come across a few solutions online, however one of them didn't appear to work and the other did. Can someone explain to me the correct way to set this up. The first solution produces and error as mentioned below. The second solution works but I'm not sure of any pros/cons.
Solution 1 - broken
public partial class MyControl : Control
{
...
public static readonly RoutedCommand AlignLeftCommand = null;
static MyControl()
{
binding = new CommandBinding();
binding.Command = AlignLeftCommand;
binding.Executed += new ExecutedRoutedEventHandler(AlignLeft_Executed);
CommandManager.RegisterClassCommandBinding(typeof(MyControl), binding);
}
}
Error:
Severity Code Description Project File Line
Error CS0120 An object reference is required for the non-static field, method, or property...
Solution 2
public partial class MyControl : Control
{
...
public static readonly RoutedCommand AlignLeftCommand = new RoutedCommand();
public MyControl()
{
this.CommandBindings.Add(new CommandBinding(MyControl.AlignLeftCommand, AlignLeft_Executed, null));
}
}
Here is the button calling the method.
<StackPanel Orientation="Horizontal">
<Button Content="Left Edges" FontSize="8"
Command="{x:Static JM:MyControl.AlignLeftCommand}"
CommandTarget="{Binding ElementName=mycontrol}"/>
</StackPanel>
At first, you should define a command binding on the Window like that(create handlers for Executed and CanExecuteevents):
<Window x:Class="CommandBindingWPF.MainWindow"
...The code omitted for the brevity...
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute" />
</Window.CommandBindings>
and declare your Button ix xaml:
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Command="ApplicationCommands.New">New</Button>
</StackPanel>
Handlers should be created in code-behind after you command binding created:
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Hello from Command");
}
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{ }
Update:
For MVVM application:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
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;
}
#endregion // Constructors
#region ICommand Members
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);
}
#endregion // ICommand Members
}
Then create a property in your viewModel. For instance:
public class YourViewModel
{
public RelayCommand YourCommand { get; set; }
public YourViewModel()
{
YourCommand = new RelayCommand(DoSmth, CanDoSmth);
}
private void DoSmth(object obj)
{
Message.Box("Hello from viewModel");
}
private bool CanDoSmth(object obj)
{
//you could implement your logic here. But by default it should be
//set to true
return true;
}
}
And XAML should be look like:
<Button Content="Click me!" Command="{Binding YourCommand}"/>
To get acquainted with MVVM, I recommend you to read Rachel Lim's blog. She has a talent to teach people and she can explain by simple terms. Read Rachel Lim's blog.
To get acquainted with MVVM commands see that post
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.
I have created blank C#/XAML Windows 8 application. Add simple XAML code:
<Page
x:Class="Blank.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel
Margin="0,150"
HorizontalAlignment="Center">
<TextBlock
x:Name="xTitle"
Text="{Binding Title, Mode=TwoWay}"/>
<Button Content="Click me!" Click="OnClick" />
</StackPanel>
</Grid>
</Page>
And the simple code in C# part:
public sealed partial class MainPage
{
private readonly ViewModel m_viewModel;
public MainPage()
{
InitializeComponent();
m_viewModel = new ViewModel
{
Title = "Test1"
};
DataContext = m_viewModel;
}
private void OnClick(object sender, RoutedEventArgs e)
{
m_viewModel.Title = "Test2";
}
}
Now I want to implement ViewModel. I have two way:
Use Dependency Property
Implement INotifyPropertyChanged
For first approach it is:
public class ViewModel : DependencyObject
{
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string)
, typeof(ViewModel)
, new PropertyMetadata(string.Empty));
}
For second it is:
public class ViewModel : INotifyPropertyChanged
{
private string m_title;
public string Title
{
get
{
return m_title;
}
set
{
m_title = value;
OnPropertyChanged("Title");
}
}
protected void OnPropertyChanged(string name)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I prefer the first way, because it allows use coerce (Silverlight for web and for WP7 doesn't have coerce functionality.. WinRT too.. but I'm still looking and hope) and looks more natural for me. But unfortunately, it works as OneTime for the first approach.
Could anybody explain to me why MS abandon using Dependency Property for implementing view model?
You should not be using a DependencyProperty in your ViewModel - you should only use them in your controls. You will never want to bind one ViewModel to another, also ViewModels do not need to persist their values nor provide default values, nor provide property metadata.
You should only use INotifyPropertyChanged in your ViewModels.