I'm currently in the process of mastering the C# WPF MVVM pattern and have stumbled upon a pretty big hurdle...
What I am trying to do fire off a LoginCommand that when successfully executed will allow me to change the parent window's viewmodel. The only issue is I can't quite think of a way to change the parent window's viewmodel without breaking the MVVM design pattern because I can't access the parent window's ContentControl that sets its path to the active UserControlViewModel in the window.
Here's the scenario:
In our App.xaml we have two DataTemplates:
<DataTemplate DataType="{x:Type ViewModels:LoginViewModel}">
<Views:LoginView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:LoggedInViewModel}">
<Views:LoggedView />
</DataTemplate>
In our MainWindow we have:
<ContentControl Content="{Binding ViewModel}" />
The MainWindow code behind will set the ViewModel = LoginViewModel
In our LoginViewModel we have:
<Button Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=pwPasswordBoxControlInXaml}" />
Now for the money... the LoginCommand:
public void Execute(object parameter)
{
// Do some validation
// Async login task stuff
// ...
// Logged in... change the MainWindow's ViewModel to the LoggedInViewModel
}
How can I make the Execute method change the window's viewmodel without breaking the MVVM pattern?
Things I've tried thus far:
Making the MainWindow have a static Instance singleton that I can access and then change the ViewModel property from the command.
Attempting to implement some form of routed command listener in the MainWindow and then have commands fire off routed command events to be handled by the parent window.
I've done a quick demo to show one way of doing it. I've kept it as simple as possible to give the general idea. There are lots of different ways of accomplishing the same thing (e.g. you could hold a reference to MainWindowViewModel inside LoginViewModel, handle everything there then call a method on MainWindowViewModel to trigger the workspace change, or you could use Events/Messages, etc).
Definitely have a read of Navigation with MVVM though. That's a really good introduction that I found helpful when I was getting started with it.
The key thing to take away from this is to have an outer MainWindowViewModel or ApplicationViewModel which handles the navigation, holds references to workspaces, etc. Then the choice of how you interact with this is up to you.
In the code below, I've left out the clutter from defining Window, UserControl, etc. to keep it shorter.
Window:
<DockPanel>
<ContentControl Content="{Binding CurrentWorkspace}"/>
</DockPanel>
MainWindowViewModel (this should be set as the DataContext for the Window):
public class MainWindowViewModel : ObservableObject
{
LoginViewModel loginViewModel = new LoginViewModel();
LoggedInViewModel loggedInViewModel = new LoggedInViewModel();
public MainWindowViewModel()
{
CurrentWorkspace = loginViewModel;
LoginCommand = new RelayCommand((p) => DoLogin());
}
private WorkspaceViewModel currentWorkspace;
public WorkspaceViewModel CurrentWorkspace
{
get { return currentWorkspace; }
set
{
if (currentWorkspace != value)
{
currentWorkspace = value;
OnPropertyChanged();
}
}
}
public ICommand LoginCommand { get; set; }
public void DoLogin()
{
bool isValidated = loginViewModel.Validate();
if (isValidated)
{
CurrentWorkspace = loggedInViewModel;
}
}
}
LoginView:
In this example I'm binding a Button on the LoginView to the LoginCommand on the Window DataContext (i.e. MainWindowViewModel).
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding UserName}"/>
<Button Content="Login" Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.LoginCommand}"/>
</StackPanel>
LoginViewModel:
public class LoginViewModel : WorkspaceViewModel
{
private string userName;
public string UserName
{
get { return userName; }
set
{
if (userName != value)
{
userName = value;
OnPropertyChanged();
}
}
}
public bool Validate()
{
if (UserName == "bob")
{
return true;
}
else
{
return false;
}
}
}
LoggedInView:
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding RestrictedData}"/>
</StackPanel>
LoggedInViewModel:
public class LoggedInViewModel : WorkspaceViewModel
{
private string restrictedData = "Some restricted data";
public string RestrictedData
{
get { return restrictedData; }
set
{
if (restrictedData != value)
{
restrictedData = value;
OnPropertyChanged();
}
}
}
}
WorkspaceViewModel:
public abstract class WorkspaceViewModel : ObservableObject
{
}
Then some other classes you probably already have implemented (or alternatives).
ObservableObject:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand:
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{ }
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
}
App.Xaml:
<DataTemplate DataType="{x:Type ViewModels:LoginViewModel}">
<Views:LoginView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:LoggedInViewModel}">
<Views:LoggedInView />
</DataTemplate>
<ContentControl Content="{Binding ViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:LoginViewModelClass}">
<!-- some LoginView -->
</DataTemplate>
<DataTemplate DataType="{x:Type vm:LoggedInViewModelClass}">
<!-- some LoggedInView -->
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
Related
I've been struggling with this issue for a while now, tried all of the answers I have seen so far and I still get the same error message
Cannot find source: RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Menu', AncestorLevel='1'.
And my button does nothing
What I'm trying to accomplish
I have this main window called Menu which has a content control and a top bar menu. The idea is that when I press a button from the homeView it changes the mainView to the one selected.
I have already tried this solution WPF MVVM navigate views but got this error message as a result. What I can do for the moment is change views using the top bar on the Menu window but I can not make the childView to execute a command from the parentView
Here's what I have
In my menu window
<Window.DataContext>
<viewModel:MenuPrincipalVistaControlador/>
</Window.DataContext>
<TheThingsInsideMyWindow/>
<ContentControl Grid.Row="1" Margin="0" Content="{Binding vistaActual}"/>
where vistaActual is a reference to a property currentView in my main ViewModel
My data templates
<DataTemplate DataType="{x:Type viewModel:CasaVistaControlador}">
<view:MenuInicioVista/>
</DataTemplate>
<DataTempate DataType="{x:Type viewModel:CajaVistaControlador}">
<view:CajaVista/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:AgregarUsuarioVistaControlador}">
<view:VistaAgregarUsuario/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:AjusteVistaControlador}">
<view:AjustesVista/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:CitaVistaControlador}">
<view:CitaVista/>
</DataTemplate>
Inside of my HomeView
<Button x:Name="btnCitas" Height="150" Width="150" Margin="250,80,0,0" VerticalAlignment="Top" Style="{StaticResource MaterialDesignRaisedButton}" RenderTransformOrigin="0.496,2.246" materialDesign:ButtonAssist.CornerRadius="10" FontFamily="Bahnschrift" FontSize="20" BorderBrush="{x:Null}" Command="{Binding Path=DataContext.CitaVistaComando , RelativeSource={RelativeSource AncestorType={x:Type Menu}}}" />
Classes
Part of me thinks it might be something on my base classes that's making the bind to not work so going from here will be the classes that make everything work
My MainViewModel (datacontext for menu)
class MenuPrincipalVistaControlador: ObservableObject
{
public CasaVistaControlador CasaVista { get; set; }
public CajaVistaControlador CajaVista { get; set; }
public CitaVistaControlador CitaVista { get; set; }
private object _vistaActual;
public RelayCommand CasaVistaComando { get; set; }
public RelayCommand CajaVistaComando { get; set; }
public RelayCommand CitaVistaComando { get; set; }
public object vistaActual
{
get { return _vistaActual; }
set { _vistaActual = value;
OnPropertyChanged();
}
}
public MenuPrincipalVistaControlador()
{
CasaVista = new CasaVistaControlador();
CajaVista = new CajaVistaControlador();
vistaActual = CasaVista;
CasaVistaComando = new RelayCommand(o =>
{
vistaActual = CasaVista;
});
CajaVistaComando = new RelayCommand(o =>
{
vistaActual = CajaVista;
});
CitaVistaComando = new RelayCommand(o =>
{
vistaActual = CitaVista;
});
}
}
My ObservableObject class
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
My custom RelayCommand class
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);
}
}
I'm attempting to create a class & method which could be used on any window and page to change the current page displayed on the MainWindow window.
So far I got:
class MainWindowNavigation : MainWindow
{
public MainWindow mainWindow;
public void ChangePage(Page page)
{
mainWindow.Content = page;
}
}
The main window itself:
public MainWindow()
{
InitializeComponent();
MainWindowNavigation mainWindow = new MainWindowNavigation();
mainWindow.ChangePage(new Pages.MainWindowPage());
}
Unfortunately this ends up with System.StackOverflowException.
The main reason for creating this is that I want to be able to change the mainWindow.Content from a page which is currently displayed in mainWindow.Content.
I have already reviewed MVVM but I don't think it is worth using it for a small application like this as all I want it to do is Display a Welcome Page on open, then on the side there will be few buttons. Once pressed the mainWindow.Content correctly changes to a page where a user can enter login detail and then on the button press on the login page I want to change the mainWindow.Content to a different page on successful validation of the login details entered.
Using MVVM is absolutely fine as it will simplify the implementation of your requirement. WPF is build to be used with the MVVM pattern, which means to make heavy use of data binding and data templates.
The task is quite simple. Create a UserControl (or DataTemplate) for each view e.g., WelcomePage and LoginPage with their corresponding view models WelcomePageViewModel and LoginPageViewModel.
A ContentControl will display the pages.
The main trick is that, when using an implicit DataTemplate (a template resource without an x:Key defined), the XAML parser will automatically lookup and apply the correct template, where the DataType matches the current content type of a ContentControl. This makes navigation very simple, as you just have to select the current page from a collection of page models and set this page via data binding to the Content property of the ContentControl or ContentPresenter:
Usage
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type WelcomePageviewModel}">
<WelcomPage />
</DataTemplate>
<DataTemplate DataType="{x:Type LoginPageviewModel}">
<LoginPage />
</DataTemplate>
</Window.Resources>
<StackPanel>
<!-- Page navigation -->
<StackPanel Orientation="Horizontal">
<Button Content="Show Login Screen"
Command="{Binding SelectPageCommand}"
CommandParameter="{x:Static PageName.LoginPage}" />
<Button Content="Show Welcome Screen"
Command="{Binding SelectPageCommand}"
CommandParameter="{x:Static PageName.WelcomePage}" />
</StackPanel>
<!--
Host of SelectedPage.
Automatically displays the DataTemplate that matches the current data type
-->
<ContentControl Content="{Binding SelectedPage}" />
<StackPanel>
</Window>
Implementation
Create the individual page controls (the controls that host the page content). This can be a Control, UserControl, Page or simply a plain DataTemplate:
WelcomePage.xaml
<UserControl>
<StackPanel>
<TextBlock Text="{Binding PageTitle}" />
<TextBlock Text="{Binding Message}" />
</StackPanel>
</UserControl>
LoginPage.xaml
<UserControl>
<StackPanel>
<TextBlock Text="{Binding PageTitle}" />
<TextBox Text="{Binding UserName}" />
</StackPanel>
</UserControl>
Create the page models:
IPage.cs
interface IPage : INotifyPropertyChanged
{
string PageTitel { get; set; }
}
WelcomePageViewModel.cs
class WelcomePageViewModel : IPage
{
private string pageTitle;
public string PageTitle
{
get => this.pageTitle;
set
{
this.pageTitle = value;
OnPropertyChanged();
}
}
private string message;
public string Message
{
get => this.message;
set
{
this.message = value;
OnPropertyChanged();
}
}
public WelcomePageViewModel()
{
this.PageTitle = "Welcome";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
LoginPageViewModel.cs
class LoginPageViewModel : IPage
{
private string pageTitle;
public string PageTitle
{
get => this.pageTitle;
set
{
this.pageTitle = value;
OnPropertyChanged();
}
}
private string userName;
public string UserName
{
get => this.userName;
set
{
this.userName = value;
OnPropertyChanged();
}
}
public LoginPageViewModel()
{
this.PageTitle = "Login";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Create an enumeration of page identifiers (to eliminate magic strings in XAML and C#):
PageName.cs
public enum PageName
{
Undefined = 0, WelcomePage, LoginPage
}
Create the MainViewModel which will manage the pages and their navigation:
MainViewModel.cs
An implementation of RelayCommand can be found at
Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern - Relaying Command Logic
class MainViewModel
{
public ICommand SelectPageCommand => new RelayCommand(SelectPage);
private Dictionary<PageName, IPage> Pages { get; }
private IPage selectedPage;
public IPage SelectedPage
{
get => this.selectedPage;
set
{
this.selectedPage = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
this.Pages = new Dictionary<PageName, IPage>
{
{ PageName.WelcomePage, new WelcomePageViewModel() },
{ PageName.LoginPage, new LoginPageViewModel() }
};
this.SelectedPage = this.Pages.First().Value;
}
public void SelectPage(object param)
{
if (param is PageName pageName
&& this.Pages.TryGetValue(pageName, out IPage selectedPage))
{
this.SelectedPage = selectedPage;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You probably want to define MainWindowNavigation as a static class with a method that simply changes the Content of the current MainWindow:
static class MainWindowNavigation
{
public static void ChangePage(Page page)
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
mainWindow.Content = page;
}
}
You can then call the method from any class without having a reference to the MainWindow:
MainWindowNavigation.ChangePage(new Pages.MainWindowPage());
I made a Memory Game with a start menu, and a separate page where the game takes place. On the main menu you can select your theme and press a button to go to the game. I also have a button on the game screen, but I can not figure out how to make it link to the main menu.
I honestly don't know how to go about doing this.
This is the button that links from the main page to the game(in the xaml):
<Button DockPanel.Dock="Top" Padding="25" Click="Play_Clicked" Background="#FF0E0E0E" Foreground="#FFF3FF00" FontSize="18">Start Game</Button>
this is the code in xaml.cs:
private void Play_Clicked(object sender, RoutedEventArgs e)
{
var startMenu = DataContext as StartMenuViewModel;
startMenu.StartNewGame(categoryBox.SelectedIndex);
}
This is the code from "StartMenuViewModel" that contains the "StartNewGame":
public void StartNewGame(int categoryIndex)
{
var category = (SlideCategories)categoryIndex;
GameViewModel newGame = new GameViewModel(category);
_mainWindow.DataContext = newGame;
}
This code works, does anyone know how to make a similar button to go from the game screen to the main menu?
The easiest and most lightweight way opposed to using a Frame, is to create a view model for each page. Then create a main view model which holds all pages and manages their selection. A ContentControl will display the view models using a DataTemplate assigned to the ContentControl.ContentTemplate property or in a multi page scenario either a DataTemplateSelector assigned to ContentControl.ContentTemplateSelector or implicit templates by only defining the DataTemplate.DataType without the Key attribute:
The View
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel x:Key="MainViewModel" />
</Window.DataContext>
<Window.Resources>
<!--
The templates for the view of each page model.
Can be moved to dedicated files.
-->
<DataTemplate DataType="{x:Type PageA}">
<Border Background="Coral">
<TextBlock Text="{Binding Title}" />
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type PageB}">
<Border Background="DeepSkyBlue">
<TextBlock Text="{Binding Title}" />
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Load Page A"
Command="{Binding SelectPageFromIndexCommand}"
CommandParameter="0" />
<Button Content="Load Page B"
Command="{Binding SelectPageFromIndexCommand}"
CommandParameter="1" />
<!-- The actual page control -->
<ContentControl Content="{Binding SelectedPage}" />
</StackPanel>
</Window>
The View Model
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.Pages = new ObservableCollection<IPage>() {new PageA() {Title = "Page A"}, new PageB() {Title = "Page B"}};
// Show startup page
this.SelectedPage = this.Pages.First();
}
// Define the Execute and CanExecute delegates for the command
// and pass to constructor
public ICommand SelectPageFromIndexCommand => new SelectPageCommand(
param => this.SelectedPage = this.Pages.ElementAt(int.Parse(param as string)),
param => int.TryParse(param as string, out int index));
private IPage selectedPage;
public IPage SelectedPage
{
get => this.selectedPage;
set
{
if (object.Equals(value, this.selectedPage))
{
return;
}
this.selectedPage = value;
OnPropertyChanged();
}
}
private ObservableCollection<IPage> pages;
public ObservableCollection<IPage> Pages
{
get => this.pages;
set
{
if (object.Equals(value, this.pages))
{
return;
}
this.pages = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
SelectPageCommand.cs
class SelectPageCommand : ICommand
{
public SelectPageCommand(Action<object> executeDelegate, Predicate<object> canExecuteDelegate)
{
this.ExecuteDelegate = executeDelegate;
this.CanExecuteDelegate = canExecuteDelegate;
}
private Predicate<object> CanExecuteDelegate { get; }
private Action<object> ExecuteDelegate { get; }
#region Implementation of ICommand
public bool CanExecute(object parameter) => this.CanExecuteDelegate?.Invoke(parameter) ?? false;
public void Execute(object parameter) => this.ExecuteDelegate?.Invoke(parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
#endregion
}
The Page Models
IPage.cs
// Base type for all pages
interface IPage : INotifyPropertyChanged
{
string Title { get; set; }
}
PageA.cs
// IPage implementation.
// Consider to introduce dedicated interface IPageA which extends IPage
class PageA : IPage
{
public string Title { get; set; }
// Implementation of INotifyPropertyChanged
}
PageB.cs
// IPage implementation.
// Consider to introduce dedicated interface IPageB which extends IPage
class PageB : IPage
{
public string Title { get; set; }
// Implementation of INotifyPropertyChanged
}
I am very new to WPF and relatively new to C# programming (programming in general), and I'm trying to develop a WPF application.
I have tried to go through several posts similar to this, but I can't seem to find the answer of why this is not working.
So, I'm having a hard time understanding the MVVM architecture, how and what it requires to switch between multiple user controls binded to a single <ContentControl />.
From what I understand and read so far, is that I have to bind the view model like this:
<ContentControl Content="{Binding ApplicationViewModel}"/>
So here is what I want to a achieve:
An ApplicationWindow.xaml with sidebar menu on the left side that will be shown at all times when the application is running, and a <ContentControl/> on the remaining space. Buttons shown on the sidebar menu will be:
Main (will show MainView.xaml User Control, should be the default User Control)
Settings (will show SettingsView.xaml User Control)
Exit (will close the application)
I understand that I need to bind the buttons to ICommand commands, and I understand the concept of a RelayCommand.cs class.
So let's jump into the simplified code of my idea and figure out what I need to understand and what I may have misunderstood in the process.
What MainView.xaml and SettingsView.xaml contain are not important right now, as I'm just trying to figure out how to show them in my application.
Here's the ApplicationWindow.xaml:
<Window x:Class="WpfApp1.ApplicationWindow"
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"
xmlns:v="clr-namespace:WpfApp1.View"
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
mc:Ignorable="d"
Title="ApplicationWindow" Height="1080" Width="1920"
WindowStyle="None" WindowState="Maximized">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MainViewModel}">
<v:MainView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<v:SettingsView/>
</DataTemplate>
</Window.Resources>
<DockPanel>
<!--Menu bar on the left-->
<Border DockPanel.Dock="Left">
<StackPanel Orientation="Vertical" Background="Gray" Width="120">
<Button Content="Main" Command="{Binding ShowMainCommand}"/>
<Button Content="Settings" Command="{Binding ShowSettingsCommand}"/>
<Button Content="Exit" Command="{Binding ExitApplicationCommand}"/>
</StackPanel>
</Border>
<!--The content control that view the current view-->
<ContentControl Content="{Binding ApplicationViewModel}"/>
</DockPanel>
</Window>
Note: DataContext is set to ApplicationViewModel.cs in App.xaml.cs by overriding the OnStartup() method.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ApplicationWindow app = new ApplicationWindow
{
DataContext = new ApplicationViewModel()
};
app.Show();
}
}
Here's the ApplicationViewModel.cs:
public class ApplicationViewModel : ViewModelBase
{
#region Fields
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
private ICommand _showMainCommand;
private ICommand _showSettingsCommand;
private ICommand _exitApplicationCommmand;
#endregion
#region Constructor
public ApplicationViewModel()
{
ViewModels = new List<ViewModelBase>
{
new MainViewModel(),
new SettingsViewModel()
};
CurrentViewModel = ViewModels[0];
}
#endregion
#region Public Properties
public List<ViewModelBase> ViewModels
{
get
{
return _viewModels;
}
set
{
if (_viewModels != value)
{
_viewModels = value;
OnPropertyChanged(nameof(ViewModels));
}
}
}
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if(_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged(nameof(CurrentViewModel));
}
}
}
#endregion
#region Commands
public ICommand ShowMainCommand
{
get
{
if(_showMainCommand == null)
{
_showMainCommand = new RelayCommand(action => ShowMain());
}
return _showMainCommand;
}
}
public ICommand ShowSettingsCommand
{
get
{
if (_showSettingsCommand == null)
{
_showSettingsCommand = new RelayCommand(action => ShowSettings());
}
return _showSettingsCommand;
}
}
public ICommand ExitApplicationCommand
{
get
{
if (_exitApplicationCommmand == null)
{
_exitApplicationCommmand = new RelayCommand(action => ExitApplication());
}
return _exitApplicationCommmand;
}
}
#endregion
#region Private Methods
private void ShowMain()
{
CurrentViewModel = ViewModels[0];
}
private void ShowSettings()
{
CurrentViewModel = ViewModels[1];
}
private void ExitApplication()
{
MessageBoxResult result = MessageBox.Show("Are you sure you want to exit?", "Exit", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
System.Windows.Application.Current.Shutdown();
}
}
#endregion
}
So, from what I understand, the ApplicationWindow.xaml should be able to determine which view to show out from what the CurrentViewModel is set to.
For the sake of information (or miss-information), here are ViewModelBase.cs:
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And RelayCommand.cs:
public class RelayCommand : ICommand
{
#region Fields
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
#endregion
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
#endregion
#region ICommand
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion
}
I hope my thought process on this was clear to you, and that one of you smart programmers out there can help solving this, and help me understand why this isn't turning out as I want it to.
In case of what I'm trying to do is harder than Elon Musk's project on making life multiplanetary, feel free to explain why and suggest me a better way to
Your Content control binding should be pointed at the actual property you change when switching ViewModels
<ContentControl Content="{Binding CurrentViewModel}"/>
i'm new in WPF and MVVM. I read many articles about WPF commands, but i have still problem with sending value from property text of textbox to ViewModel.
I'm using entity framework code first.
I want to show text from textbox in MessageBox, but when I click to button with command, linked property of viewmodel is null.
Please can you help me?
View- DetailIncidentWindow.xaml
xmlns:wm="clr-namespace:Admin.ViewModels"
<StackPanel>
<StackPanel.DataContext>
<wm:CommentViewModel/>
</StackPanel.DataContext>
<TextBlock Text="Text komentáře:" Style="{StaticResource TextBlockLabel}" Margin="0,10,0,0"/>
<TextBox Name="TextBox_textKomentar" Width="auto" Height="100" TextWrapping="Wrap" Text="{Binding TextKomentar, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding TextKomentar, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
Ribbon button- DetailIncidentWindow.xaml
<Custom:RibbonGroup.DataContext>
<wm:CommentViewModel/>
</Custom:RibbonGroup.DataContext>
<Custom:RibbonButton
LargeImageSource="..\Shared\img\save_diskete.png"
Label="Show text"
Command="{Binding ButtonCommand}">
</Custom:RibbonButton>
ViewModel- KomentarViewModel.cs
namespace Admin.ViewModels
{
class CommentViewModel:BaseViewModel
{
#region Data
private string textKomentar;
public string TextKomentar
{
get
{
return textKomentar;
}
set
{
textKomentar = value;
OnPropertyChanged("TextKomentar");
}
}
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
OnPropertyChanged("ButtonCommand");
}
}
#endregion
#region Constructor
public CommentViewModel()
{
ButtonCommand = new RelayCommand(new Action<object>(ShowMessage));
}
#endregion
#region Methods
public void ShowMessage(object obj)
{
MessageBox.Show(TextKomentar);
}
#endregion
}
}
Command- RelayCommand.cs
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello World");
}
}
#endregion
}
You should not create multiple instances of your view model, like you do in
<StackPanel.DataContext>
<wm:CommentViewModel/>
</StackPanel.DataContext>
and
<Custom:RibbonGroup.DataContext>
<wm:CommentViewModel/>
</Custom:RibbonGroup.DataContext>
The value of the DataContext property is inherited by child elements, so you could just set it at the top level, e.g. the Window:
<Window ...>
<Window.DataContext>
<wm:CommentViewModel/>
</Window.DataContext>
...
</Window>