I´m a beginner in this language, trying to learn more about the best practices and hows to do the things better...
I have this repo as example application: https://github.com/Albvadi/NavigationMVVM
If you run it, all works well. You can navigate to others views and increment a counter shared in all views.
But, If you uncomment the ActualView assignation in MainViewModel.cs file at line 24 and put a login view in front of the InitialView I don´t know how to redirect the user to the Initialview after login success.
When the login is correct, I fill the user data in the ManagerData and with all of this I need to call the function in the MainViewModel to redirect the user to the Initial view. How can I make that call from the LoginViewModel to the other MainViewModel instead from the View?
UPDATE: Add the code relevant
App.xaml
<Application
x:Class="NavigationMVVM.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NavigationMVVM"
xmlns:Views="clr-namespace:NavigationMVVM.Views"
xmlns:ViewModels="clr-namespace:NavigationMVVM.ViewModels"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate
DataType="{x:Type ViewModels:MainViewModel}">
<local:MainWindow />
</DataTemplate>
<DataTemplate
DataType="{x:Type ViewModels:LoginViewModel}">
<Views:Login />
</DataTemplate>
<DataTemplate
DataType="{x:Type ViewModels:InitialViewModel}">
<Views:Initial />
</DataTemplate>
<DataTemplate
DataType="{x:Type ViewModels:FirstViewModel}">
<Views:First />
</DataTemplate>
<DataTemplate
DataType="{x:Type ViewModels:SecondViewModel}">
<Views:Second />
</DataTemplate>
</Application.Resources>
</Application>
MainWindow.xaml
<Window
x:Class="NavigationMVVM.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:NavigationMVVM"
xmlns:viewModels="clr-namespace:NavigationMVVM.ViewModels"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="MainWindow"
Height="450"
Width="800">
<Window.DataContext>
<viewModels:MainViewModel />
</Window.DataContext>
<Grid>
<ContentControl
Content="{Binding ActualView}" />
</Grid>
</Window>
BaseViewModel.cs
using System.ComponentModel;
namespace NavigationMVVM.Common
{
public class BaseViewModel : INotifyPropertyChanged
{
private BaseViewModel _ActualView;
public BaseViewModel ActualView
{
get => _ActualView;
set
{
_ActualView = value;
RaisePropertyChanged(null);
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
#endregion
}
}
MainViewModel.cs
using NavigationMVVM.Common;
using NavigationMVVM.Models;
using System.Windows.Input;
namespace NavigationMVVM.ViewModels
{
public class MainViewModel : BaseViewModel
{
public DataManager SharedData = new DataManager();
public InitialViewModel InitialVM;
public FirstViewModel FirstVM;
public SecondViewModel SecondVM;
public LoginViewModel LoginVM;
public MainViewModel()
{
LoginVM = new LoginViewModel(SharedData);
InitialVM = new InitialViewModel(SharedData);
FirstVM = new FirstViewModel(SharedData);
SecondVM = new SecondViewModel(SharedData);
ActualView = InitialVM;
//ActualView = LoginVM;
}
public ICommand DisplayFirstView
{
get
{
return new RelayCommand(action => ActualView = FirstVM,
canExecute => true);
}
}
public ICommand DisplaySecondView
{
get
{
return new RelayCommand(action => ActualView = SecondVM,
canExecute => true);
}
}
public ICommand DisplayInitialView
{
get
{
return new RelayCommand(action => ActualView = InitialVM,
canExecute => true);
}
}
}
}
LoginViewModel.cs
using NavigationMVVM.Common;
using NavigationMVVM.Models;
using System.Diagnostics;
using System.Windows.Input;
namespace NavigationMVVM.ViewModels
{
public class LoginViewModel : BaseViewModel
{
private DataManager _DataManager;
public ICommand LoginCmd;
public RelayCommand DoLoginCmd { get; }
private string _Username;
public string Username
{
get => _Username;
set
{
_Username = value;
RaisePropertyChanged(null);
}
}
private string _Password;
public string Password
{
get => _Password;
set
{
_Password = value;
RaisePropertyChanged(null);
}
}
private string _MessageInfo;
public string MessageInfo
{
get => _MessageInfo;
set
{
_MessageInfo = value;
RaisePropertyChanged(null);
}
}
public LoginViewModel(DataManager sharedData)
{
_DataManager = sharedData;
DoLoginCmd = new RelayCommand(param => DoLogin(), canExec => (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password)));
}
public void DoLogin()
{
if (Username == "admin" && Password == "password")
{
_DataManager.User.Name = "Administrator";
_DataManager.User.Mail = "admin#company.com";
MessageInfo = "Login OK!... How to redirect??";
}
else
{
MessageInfo = "Username or Password incorrect!";
}
}
}
}
Thank you.
There are multiple ways to accomplish what you want. I'll post two approaches that are very common when working in an MVVM pattern
Event based approach:
public MainViewModel()
{
LoginVM = new LoginViewModel(SharedData);
LoginVM.PropertyChanged += LoginVM_PropertyChanged;
InitialVM = new InitialViewModel(SharedData);
FirstVM = new FirstViewModel(SharedData);
SecondVM = new SecondViewModel(SharedData);
//ActualView = InitialVM;
ActualView = LoginVM;
}
private void LoginVM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(sender.GetType() == typeof(LoginViewModel) && e.PropertyName == "MessageInfo")
{
var loginVM = (LoginViewModel)sender;
if (loginVM.MessageInfo == "OK")
{
ActualView = InitialVM;
}
}
}
Cunstructor Injection:
private Action _loginAction;
public LoginViewModel(DataManager sharedData, Action loginAction )
{
_DataManager = sharedData;
DoLoginCmd = new RelayCommand(param => DoLogin(), canExec => (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password)));
_loginAction = loginAction;
}
public void DoLogin()
{
if (Username == "admin" && Password == "password")
{
_DataManager.User.Name = "Administrator";
_DataManager.User.Mail = "admin#company.com";
MessageInfo = "Login OK!... How to redirect??";
_loginAction.Invoke();
}
else
{
MessageInfo = "Username or Password incorrect!";
}
}
and
public MainViewModel()
{
LoginVM = new LoginViewModel(SharedData, () => ActualView = InitialVM);
InitialVM = new InitialViewModel(SharedData);
FirstVM = new FirstViewModel(SharedData);
SecondVM = new SecondViewModel(SharedData);
//ActualView = InitialVM;
ActualView = LoginVM;
}
Side note: to put a property "ActualView" on the BaseViewModel is a rather odd choice
Related
I am attempting to prevent my application from deleting a view and then creating a new one each time it's navigated around. I have a dashboard that will run a test program, if I select the settings view, then back to the dashboard, it has deleted the running test and initialized a new view. I need to keep the same view instance alive so that the test can continue to run while the user navigates to the settings view and back again but I cant exactly figure out how to successfully do that. I have attempted making the instance static but that doesn't seem to make a difference.
MainViewModel
class MainVM : ViewModelBase
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set { _currentView = value; OnPropertyChanged(); }
}
public ICommand DashboardCommand { get; set; }
public ICommand SettingsCommand { get; set; }
public static DashboardVM DashboardInstance { get; } = new DashboardVM();
public static SettingsVM SettingsInstance { get; } = new SettingsVM();
private void Dashboard(object obj) => CurrentView = DashboardInstance;
private void Settings(object obj) => CurrentView = SettingsInstance;
public MainVM()
{
DashboardCommand = new RelayCommand(Dashboard);
SettingsCommand = new RelayCommand(Settings);
// Startup Page
CurrentView = DashboardInstance;
}
}
ViewModelBase
public partial class ViewModelBase : ObservableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
MainView - Navigation
<!-- Navigation Panel -->
<Grid HorizontalAlignment="Left" Width="76">
<Border Background="#3D5A8A" CornerRadius="10,0,0,10" />
<StackPanel Height="1200" Width="76">
<!-- Dashboard Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding DashboardCommand}"
IsChecked="True">
<Grid>
<Image Source="Images/dash_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Dashboard"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
<!-- Settings Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding SettingsCommand}">
<Grid>
<Image Source="Images/gear_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Settings"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
</StackPanel>
</Grid>
DashboardVM
class DashboardVM : ViewModelBase
{
enum TestItemStatus
{
Reset,
Queued,
InProgress,
Pass,
Fail
}
private readonly PageModel _pageModel;
private string _StartButtonText,
_WaveRelayEthernetText;
private bool isTestRunning;
public DashboardVM()
{
_pageModel = new PageModel();
_StartButtonText = "Start Test";
_WaveRelayEthernetText = string.Empty;
StartButtonCommand = new RelayCommand(o => StartButtonClick("StartButton"));
}
#region Text Handlers
public string StartButtonText
{
get { return _StartButtonText; }
set { _StartButtonText = value; NotifyPropertyChanged("StartButtonText"); }
}
public string WaveRelayEthernetText
{
get { return _WaveRelayEthernetText; }
set { _WaveRelayEthernetText = value; NotifyPropertyChanged("WaveRelayEthernetText"); }
}
#endregion
private bool TestRunning
{
get { return isTestRunning; }
set { isTestRunning = value;
if (isTestRunning) { StartButtonText = "Stop Test"; }
else { StartButtonText = "Start Test";
ResetTestItems();
}
NotifyPropertyChanged("TestRunning");
}
}
public ICommand StartButtonCommand { get; set; }
private void StartButtonClick(object sender)
{
if(TestRunning)
{
TestRunning = false;
}
else
{
SetTestItemsToQueued();
MessageBox.Show("Please plug in Tube 1");
// Start program.
TestRunning = true;
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.InProgress);
}
}
private string TestItemStatusEnumToString(TestItemStatus temp)
{
if (temp == TestItemStatus.Reset) { return string.Empty; }
else if (temp == TestItemStatus.Queued) { return "Queued"; }
else if (temp == TestItemStatus.InProgress) { return "In Progress"; }
else if (temp == TestItemStatus.Pass) { return "Pass"; }
else if (temp == TestItemStatus.Fail) { return "Fail"; }
else { return string.Empty; }
}
private void SetTestItemsToQueued()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Queued);
}
private void ResetTestItems()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Reset);
}
}
Image for reference:
My Issue was in the App.xaml, I link a DataTemplate file like this:
<ResourceDictionary Source="Utilities/DataTemplate.xaml" />
Inside the data template, I had this code that linked the views to the view models.
<ResourceDictionary [...]">
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<view:Dashboard />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<view:Settings />
</DataTemplate>
</ResourceDictionary>
I changed that code to link the two to this:
<ResourceDictionary [...]>
<view:Dashboard x:Key="DashboardViewKey"/>
<view:Settings x:Key="SettingsViewKey"/>
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<ContentControl Content="{StaticResource DashboardViewKey}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<ContentControl Content="{StaticResource SettingsViewKey}" />
</DataTemplate>
</ResourceDictionary>
I am now receiveing the expected behavior of being able to navigate without the Dashboard constructor being called, thus the view does not destory and recreate.
I hope someone else finds this useful.
Goal:
Using WPF I have created a Class called User.cs
here it is:
class User
{
public int id;
public int access;
public string username;
public int ID
{
get { return id; }
set { id = value; }
}
public int Access
{
get { return access; }
set { access = value; }
}
public string Username
{
get { return username; }
set { username = value; }
}
At my MainWindow.xaml.cs
I create a user and assign a value.
User u = new User();
private void Button_Click(object sender, RoutedEventArgs e)
{
u.id = 1;
u.access = 2;
u.username = "User1";
}
Question
From my new xaml form called Dashboard.xaml.cs. How can I access the information saved from MainWindow.xaml.cs ?
What I have tried
At Dashboard.xaml.cs
private void Window_Loaded(object sender, RoutedEventArgs e)
{
txt.Content = User.username
}
UPDATE: Using MVVM:
After some research and copying some examples here is where I got to.
Project Tree:
Ignore LoginScreen it is not used at all.
UserModel.cs
using System.ComponentModel;
namespace Technical_Application.Model
{
public class UserModel { }
public class User : INotifyPropertyChanged
{
private int id;
private int accessID;
private string username;
public int Id
{
get
{
return id;
}
set
{
if(id != value)
{
id = value;
RaisePropertyChanged("Id");
}
}
}
public int AccessID
{
get
{
return accessID;
}
set
{
if (accessID != value)
{
accessID = value;
RaisePropertyChanged("AccessID");
}
}
}
public string Username
{
get
{
return username;
}
set
{
if( username!= value)
{
username = value;
RaisePropertyChanged("Username");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
UserViewModel.cs
using Technical_Application.Model;
using System.Collections.ObjectModel;
namespace Technical_Application.ViewModel
{
public class UserViewModel
{
public ObservableCollection<User> Users
{
get;
set;
}
public void LoadUser()
{
ObservableCollection<User> users = new ObservableCollection<User>();
users.Add(new User { Id = 1});
users.Add(new User { AccessID = 1 });
users.Add(new User { Username = "User1" });
Users = users;
}
}
}
UserView.xaml
<UserControl x:Class="Technical_Application.Views.UserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Technical_Application.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBlock Text = "{Binding Path = Username, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
**Dashboard.xaml**
<Window x:Class="Technical_Application.Dashboard"
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:Technical_Application"
xmlns:views = "clr-namespace:Technical_Application.Views"
mc:Ignorable="d"
Title="Dashboard" Height="450" Width="800" Loaded="Window_Loaded">
<Grid>
<views:UserView x:Name="UserView" Loaded="UserView_Loaded"/>
</Grid>
</Window>
Dashboard.xaml.cs
namespace Technical_Application
{
/// <summary>
/// Interaction logic for Dashboard.xaml
/// </summary>
public partial class Dashboard : Window
{
public Dashboard()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void UserView_Loaded(object sender, RoutedEventArgs e)
{
Technical_Application.ViewModel.UserViewModel u =
new Technical_Application.ViewModel.UserViewModel();
u.LoadUser();
UserView.DataContext = u;
}
}
}
Next...
I need to figure out how to store information using a button click.
First of all let me recommend that you use MVVM pattern which will make your life easier now and in the future.
Now for your case, you can't directly access a random objects in C# (including forms) unless you have their reference.
the forms usually have a parent/child relationship which you can use to pass information between them. So if you do this in your MainWindow.xaml.cs:
Dashboard dash = new Dashboard(u);
dash.Show();
you can receive the user object in the constructor of the dashboard form. It is possible to act in the other direction and pass information from the child to the parent.
I have some WPF code that looks like this
C#
namespace ItemEventTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = this;
MyItems = new ObservableCollection<MyItem>();
MyItems.Add(new MyItem { Name = "Otto" });
MyItems.Add(new MyItem { Name = "Dag" });
MyItems.Add(new MyItem { Name = "Karin" });
InitializeComponent();
}
public ObservableCollection<MyItem> MyItems { get; set; }
}
public class MyItem :INotifyPropertyChanged
{
private string m_name;
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged();
}
}
public void WhenMouseMove()
{
//Do stuff
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Xaml
<Window x:Class="ItemEventTest.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:ItemEventTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
What I now want to do is when the mouse moves over an item is to call WhenMouseMove on the object that is the source of that item.
I would like to have the function called directly on the object and not by first going through MainWindow or some view model connected to MainWindow. It feels like it should be possible because the data is bound that way but I haven't managed to find a description of how to do it.
If you are seeking solution in MVVM pattern
Edit: Add a reference to System.Windows.Interactivity dll (which is system defined if Blend or VS2015 installed)
then add following namespace xmlns:i="schemas.microsoft.com/expression/2010/interactivity"
// xaml file
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseHoveredItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
// viewmodel (MyItem class)
public void WhenMouseMove()
{
//Do stuff
Console.WriteLine(Name);
}
private RelayCommand _MouseHoveredItemChangedCommand;
public RelayCommand MouseHoveredItemChangedCommand
{
get
{
if (_MouseHoveredItemChangedCommand == null)
{
_MouseHoveredItemChangedCommand = new RelayCommand(WhenMouseMove);
}
return _MouseHoveredItemChangedCommand;
}
}
// RelayCommand class
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
Is there a proper way to create a C#/WPF ViewModel containing subViewModel ?
Objective is:
I have a MainWindow. That window is use to read/create images. There is a button on that windows who switch between 2 UserControl one with IHM used to read image, the other one used to create.
The MainWindow has a MainWindowViewModel with :
command switch
image length
application parameters
I want that both UserControls can acces to MainWindowViewModel field/properties and have they own commands.
Construction will be something like this:
public partial class ReadUserControl : UserControl
{
public ReadUserControl()
{
InitializeComponent();
DataContext = MainViewModel.ReadViewModel;
}
}
public partial class CreateUserControl : UserControl
{
public CreateUserControl()
{
InitializeComponent();
DataContext = MainViewModel.CreateViewModel;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = MainViewModel;
}
}
For example, if a MainViewModel contain a field ImageWidth setting ImageWidth in CreateUserControl change the value for ReadUserControl.
I hope to have been clear, I don't know how design my MainViewModel to achieve this result
EDIT1:
I've created the MainWindowViewModel as a Singleton but i'm still unable to get MainViewModel.CreateViewModel and MainViewModel.ReadViewModel
public class MainWindowViewModel : ViewModelBase
{
private static MainWindowViewModel _instance = null;
public static MainWindowViewModel Instance
{
get
{
if (_instance == null)
_instance = new MainWindowViewModel();
return _instance;
}
}
private MainWindowViewModel()
: base()
{
}
#region CreateViewModel
/* How to create ? */
#endregion
#region ReadViewModel
/* How to create ? */
#endregion
}
Your example will work. At least if you have made your MainViewModel a Singleton.
A more professional approach might be an Constructor-Injection like this.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(MainViewModel vm)
{
InitializeComponent();
DataContext = vm.ReadViewModel;
}
}
With such DependencyInjections you can achieve a higher level of abstraction, since your UserControls can be generalized. (They will all have the same Constructor)
On the other hand, you give every such UserControl the ability, to manipulate the MainViewModel, not aware of side-effects.
In your special case, it would be more safe, to pass only the needed parameters to the UserControl, instead of giving them a bunch of informations, they will never need.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(Icommand command, int imageLength, AppParams appParams)
{
InitializeComponent();
...
// Do with your Constructorparameters what ever you have to
}
}
Edit:
Here a small, dumb implementation of how it could be done:
Code
public class MainViewModel : INotifyPropertyChanged {
private INotifyPropertyChanged _selectedViewModel;
public MainViewModel() {
var cmd = new RelayCommand(x => {
MessageBox.Show("HelloWorld");
}, x => true);
this.RVM = new ReadViewModel(cmd);
this.WVM = new WriteViewModel(cmd);
this.SelectedViewModel = WVM;
}
private ICommand _switchViewModelCommand;
public ICommand SwitchViewModelCommand => this._switchViewModelCommand ?? (this._switchViewModelCommand = new RelayCommand(x => {
if (this.SelectedViewModel == RVM) {
this.SelectedViewModel = WVM;
return;
}
this.SelectedViewModel = RVM;
}));
public INotifyPropertyChanged SelectedViewModel {
get {
return this._selectedViewModel;
}
set {
if (Equals(value, this._selectedViewModel))
return;
this._selectedViewModel = value;
this.OnPropertyChanged();
}
}
public ReadViewModel RVM {
get; set;
}
public WriteViewModel WVM {
get; set;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ReadViewModel : INotifyPropertyChanged {
public ReadViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WriteViewModel : INotifyPropertyChanged {
public WriteViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public ICommand HelloMoonCommand => new RelayCommand(x => { MessageBox.Show("Hello Moon"); });
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid Height="200">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ContentControl Content="{Binding SelectedViewModel, UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ReadViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WriteViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
<Button Content="Say Hello Moon" Command="{Binding HelloMoonCommand}"></Button>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
<Button Content="Switch VM" Command="{Binding SwitchViewModelCommand}" Grid.Row="1"/>
</Grid>
You can pass in the MainViewModel as DataContext for your user control and set the data context of elements as Read/Create model
something like
<Grid> <!--using MainWindowViewModel as data context-->
<Grid DataContext="{Binding Path=CreateViewModel}"> <!--using CreateViewModel as data context-->
.....
</Grid>
<Grid>
How to bind/create MetroWindow.RightWindowCommands in dynamically created MetroWindow through Caliburn.Micro IWindowManager Show method?
For example, I've created a custom IWindowManager implementation to always create MetroWindow instead of default Window. So whenever a new Window is created from Caliburn, it will be MetroWindow instance.
I have a logic that creates dynamically windows through IWindowManager:
ChatManager
public class ChatManager : IChatManager
{
private readonly IChatWindowSettings chatWindowSettings;
private readonly IWindowManager windowManager;
private readonly IChatFactory chatFactory;
private IDictionary<WeakReference, WeakReference> chats;
public ChatManager(IChatWindowSettings chatWindowSettings, IWindowManager windowManager, IChatFactory chatFactory)
{
this.chatWindowSettings = chatWindowSettings;
this.windowManager = windowManager;
this.chatFactory = chatFactory;
chats = new Dictionary<WeakReference, WeakReference>();
}
public void OpenFor(ISender sender)
{
var settings = chatWindowSettings.Create();
var viewModel = CreateOrGetViewModel(sender);
windowManager.ShowWindow(viewModel, null, settings);
}
private IChat CreateOrGetViewModel(ISender sender){//code...}
Those windows are chat windows. This works great. However, I'd like to bind/create a button directly in the MetroWindow RightCommands. This button would be bound to the IChat implementation (which is a view-model):
public class ChatViewModel : Screen, IChat
{
public void DoSomething(){}
}
How can I accomplish such thing?
here are some thoughts for your problem
calling sample
var view = new MainWindow(new ChatViewModel() { ChatName = "Chat name" });
view.Show();
model sample
public class ChatViewModel
{
public string ChatName { get; set; }
private ICommand chatCommand;
public ICommand ChatCommand
{
get
{
return chatCommand
?? (chatCommand = new SimpleCommand() {
CanExecutePredicate = o => true,
ExecuteAction = o => MessageBox.Show("Hurray :-D")
});
}
}
}
window code behind
public partial class MainWindow : MetroWindow
{
public MainWindow(ChatViewModel chatViewModel)
{
this.DataContext = chatViewModel;
InitializeComponent();
}
}
window xaml
<Controls:MetroWindow x:Class="MahAppsMetroSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
Title="MainWindow"
GlowBrush="{DynamicResource AccentColorBrush}"
Height="350"
Width="525">
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<Button Content="{Binding ChatName}"
Command="{Binding ChatCommand}" />
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Grid>
<!-- the content -->
</Grid>
</Controls:MetroWindow>
simple command
public class SimpleCommand : ICommand
{
public Predicate<object> CanExecutePredicate { get; set; }
public Action<object> ExecuteAction { get; set; }
public bool CanExecute(object parameter)
{
if (CanExecutePredicate != null)
return CanExecutePredicate(parameter);
return true; // if there is no can execute default to true
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteAction != null)
ExecuteAction(parameter);
}
}
hope that helps