Trying to get to grips with MVVM in WPF c#.
I am a slow learner...
I have my MainWindow.xaml.
This is the markup in question:
<Viewbox x:Name="vbxucProductCostMenu" Stretch="{Binding Stretch}" StretchDirection="Both">
//a user control
</Viewbox>
<Button Command="{Binding MagnifyMinimiseCommand}" CommandParameter="UniformToFill">
<Image Source="Images/button_plus_green.png"/>
</Button>
Part of my MainWindow.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MagnifyMinimise();
}
My Viewmodel?
public class MagnifyMinimise : INotifyPropertyChanged
{
public MagnifyMinimise()
{
Minimise();
}
MagnifyMinimiseCommand _magnifyMinimiseCommand = new MagnifyMinimiseCommand();
public MagnifyMinimiseCommand MagnifyMinimiseCommand
{
get { return _magnifyMinimiseCommand; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void Magnify()
{
Stretch = "UniformToFill";
}
public void Minimise()
{
Stretch = "None";
}
public string Stretch { get; set; }
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
my 'ICommand' class:
public class MagnifyMinimiseCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
//how do I set the property of stretch here!!
}
}
When I run this it starts up minimized which is good.
I then want to 'maximize' the viewbox when the user clicks that button.
By setting the breakpoint in the 'Execute' method i can see that it is being invoked and the 'parameter' is set to 'UniformToFill'.
But how do I get the Stretch property to 'read' that?
ADDITONAL:
I have changed it all to this (which does not work):
public class MagnifyMinimise : INotifyPropertyChanged
{
private ActionCommand<string> _magnifyMinimiseCommand;
public MagnifyMinimise()
{
Minimise();
_magnifyMinimiseCommand = new ActionCommand<string>(Magnify);
}
private void Magnify(string stretch)
{
// now the viewmodel handles it instead of the action
Stretch = stretch;
}
public event PropertyChangedEventHandler PropertyChanged;
public void Magnify()
{
Stretch = "UniformToFill";
}
public void Minimise()
{
Stretch = "None";
}
public string Stretch { get; set; }
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class ActionCommand<T> : ICommand where T : class
{
private readonly Action<T> mAction;
public ActionCommand(Action<T> action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction(parameter as T);
}
public event EventHandler CanExecuteChanged;
}
<Button Command="{Binding ActionCommand}" CommandParameter="UniformToFill">
<Image Source="Images/button_plus_green.png" />
</Button>
The easiest way is, like suggested by #Default, to use a RelayCommand. There is one (or an alternative) provided in every major MVVM framework (Prism, MVVM Light, Caliburn.Micro, ...).
That said, if you wanted to solve the issue with your vanilla implementation of a command, you'd just have to pass a reference to the viewmodel in the constructor:
public class MagnifyMinimiseCommand : ICommand
{
public MagnifyMinimiseCommand(MagnifyMinimise viewModel)
{
this.ViewModel = viewModel;
}
protected MagnifyMinimise ViewModel { get; }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.ViewModel.IsMagnifying = "...";
}
}
You need to invoke PropertyChanged for Stretch. That's how i would do it:
private string _stretch;
public string Stretch
{
get { return _stretch; }
set {_stretch = value; OnPropertyChanged("Stretch"); }
}
Also you might want to consider using RelayCommand or DelegateCommand
Another sidenote: In MVVM try not to write any code in the view's code behind. Use App.xaml.cs for setting the DataContext of the view.
EDIT: To answer your question, i would create a DelegateCommand class like this:
class DelegateCommand : ICommand
{
private readonly Action<Object> _execute;
private readonly Func<Object, Boolean> _canExecute;
public DelegateCommand(Action<Object> execute) : this(null, execute) { }
public DelegateCommand(Func<Object, Boolean> canExecute, Action<Object> execute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public Boolean CanExecute(Object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public void Execute(Object parameter)
{
if (!CanExecute(parameter))
{
throw new InvalidOperationException("Command execution is disabled.");
}
_execute(parameter);
}
public void OnCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
and use it like this in your viewmodel:
public DelegateCommand MagnifyMinimiseCommand { get; private set; }
.....
MagnifyMinimiseCommand = new DelegateCommand(param => { Stretch = UniformToFill; });
then
<Button Command="{Binding MagnifyMinimiseCommand}">
<Image Source="Images/button_plus_green.png"/>
</Button>
Instead of using such a specific type of Command, you can create a more generic command and allow the viewmodel to handle the action itself. So create a generic type of ICommand:
public class ActionCommand<T> : ICommand where T : class
{
private readonly Action<T> mAction;
public ActionCommand(Action<T> action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction(parameter as T);
}
public event EventHandler CanExecuteChanged;
}
and then create it like this:
private ActionCommand<string> _magnifyMinimiseCommand;
public MagnifyMinimise()
{
_magnifyMinimiseCommand = new ActionCommand<string>(Magnify);
....
}
private void Magnify(string stretch)
{
// now the viewmodel handles it instead of the action
Stretch = stretch;
}
Also, as a common practice I usually expose the properties to the View as it's interfaces, so the MagnifyMinimiseCommand would for instance be an ICommand instead (you can still use the field to access the ActionCommands stuff).
Related
!!! SOLVED, THANK YOU VERY MUCH !!!
I'm writing my first MVVM application (in WPF C#). Because of that, I want to use commands instead "Click" event defined in a view. The command, which I want to induce is really simple, it should to create and open a view.
I have written RelayCommand class which inherits ICommand interface.
internal class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException("execute");
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
I write a method changing value of the field, which represent a view.
private bool openSoundsWindow;
private bool openChordsWindow;
public bool OpenSoundsWindow
{
get { return openSoundsWindow; }
set { openSoundsWindow = value; }
}
public bool OpenChordsWindow
{
get { return openChordsWindow; }
set { openChordsWindow = value; }
}
public void OpenSounds()
{
openSoundsWindow = true;
}
public void OpenChords()
{
OpenChordsWindow = true;
}
I wrote in view model class commands by RelayCommand and OnPropertyChanged event. View model class inherits INotifyPropertyChanged.
private MainModel model = new MainModel();
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool OpenSoundsWindow
{
get { return model.OpenSoundsWindow; }
set
{
model.OpenSoundsWindow = value;
OnPropertyChanged(nameof(OpenSoundsWindow));
}
}
private ICommand openSounds = null;
public ICommand OpenSounds
{
get
{
if (openSounds == null)
{
openChords = new RelayCommand(
(object o) =>
{
model.OpenSounds();
OnPropertyChanged(nameof(OpenSoundsWindow));
var newSoundsWindow = new Sounds();
newSoundsWindow.Show();
},
(object o) =>
{
return model.OpenSoundsWindow != null;
});
}
return openSounds;
}
}
I created instance of view model in view's xaml code.
xmlns:vm="clr-namespace:HearingTeacher.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
<Window.Resources>
<vm:MainViewModel x:Key="mainViewModel" />
</Window.Resources>
I binded property command for buttons with created commands in view model.
<Button Grid.Row="0" Content="Sounds" Command="{Binding Path=OpenSounds,
UpdateSourceTrigger=PropertyChanged}" />
Compiler doesn't throw any exception, and .NET starts an application correctly, but commands doesn't work.
sorry for so much code.
But unfortunately there is so much connected.
That's why I had to insert so much code.
I tried to keep it to a minimum.
I hope it is enough.
To my problem:
As you can see here there is a button to select a path on the computer.
There is also a textbox which shows the path again.
XAML UserControl Code:
<DockPanel Grid.Row="1" Grid.Column="1">
<Button
Content="Repo Pfad"
Command="{Binding SelectRepoPathCommand}"/>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="2">
<TextBox Text="{Binding repoPath, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DockPanel>
Here the code is stored in the config for the user.
So that he does not have to enter the path again and again at the next start.
Small additional Info:
Since the path to the config is saved successfully, the test box will be updated after the second time you select the path.
This is because the second time the previously saved path is displayed.
ViewModel:
class BuildToolViewModel : ObservableObject
{
private string _repoPath;
public string repoPath
{
get
{
if (Properties.Settings.Default.repoPath != null)
{
return Properties.Settings.Default.repoPath;
}
else
{
return _repoPath;
}
}
set
{
_repoPath = value;
OnPropertyChanged("repoPath");
Properties.Settings.Default.repoPath = _repoPath;
Properties.Settings.Default.Save();
}
}
public RelayCommand SelectRepoPathCommand{ get; private set; }
#endregion #Properties
#region ctor
public BuildToolViewModel()
{
SelectRepoPathCommand = new RelayCommand(SelectRepoPath);
}
#endregion //ctor
#region Methods
public void SelectRepoPath(object sender)
{
repoPath = explorerDialog.OpenFileDialogPath();
}
}
Here is my ObservableObject that inherits from INotifyPropertyChanged.
ObservableObject (INotifyPropertyChanged):
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Here is my RelayCommand that inherits from ICommand.
RelayCommand (ICommand):
class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region ctor
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 //ctor
#region ICommand Members
[DebuggerStepThrough]
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 // ICommand Members
}
Here still like it in the MainWindowViewModel with the ICommand implement.
MainWindowViewModel (ObservableObject):
class MainWindowViewModel : ObservableObject
{
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
Fix you property repoPatch code:
private string _repoPath;
public string repoPath
{
get
{
if (string.IsNullOrEmpty(_repoPath))
{
_repoPath= Properties.Settings.Default.repoPath;
}
return _repopath
}
....................
............
I found the error, the problem is that the "repoPath" can be null or empty, and you are only validating that it is not null. but if the value of "repoPath" is empty, it will always return an empty value, since the value is different from null. You need to change your validation to
if (!string.IsNullOrEmpty(Properties.Settings.Default.repoPath))
also, I can see that you are saving the value of the variable "_repoPath" to the user settings "avlPath" instead of "repoPath", is it correct?
I found a custom routed command example form the Microsoft Examples, It works well.
<Window x:Class="CustomRoutedCommand.MainWindow"
...
xmlns:local="clr-namespace:CustomRoutedCommand">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:MainWindow.ColorCmd}"
Executed="ColorCmdExecuted"
CanExecute="ColorCmdCanExecute"/>
</Window.CommandBindings>
The void ColorCmdExecuted(object sender, ExecutedRoutedEventArgs e), void ColorCmdCanExecute(object sender, CanExecuteRoutedEventArgs e) are defined in the MainWindow.cs.
How to change the XAML if I move these two handlers to xxxx.cs ?
Edit, Add more info
Command handlers are defined in MainWindow.cs, I cut and paste the code to another file as following, Then the compilation goes error. Error CS1061 'MainWindow' does not contain a definition for 'ColorCmdExecuted'
// xxxx.cs
namespace CustomRoutedCommand
{
public class xxxx
{
// ExecutedRoutedEventHandler for the custom color command.
private void ColorCmdExecuted(object sender, ExecutedRoutedEventArgs e)
{
var target = e.Source as Panel;
if (target != null)
{
target.Background = target.Background == Brushes.AliceBlue ? Brushes.LemonChiffon : Brushes.AliceBlue;
}
}
// CanExecuteRoutedEventHandler for the custom color command.
private void ColorCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Source is Panel)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
}
}
You can't move the acual event handlers that you hook up in the XAML markup to another class, but you could implement the logic in another class:
public partial class MainWindow : Window
{
public static RoutedCommand ColorCmd = new RoutedCommand();
public MainWindow()
{
InitializeComponent();
}
private void ColorCmdExecuted(object sender, ExecutedRoutedEventArgs e)
{
xxxx.ColorCmdExecuted(e.Source);
}
private void ColorCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = xxxx.ColorCmdCanExecute(e.Source);
}
}
public class xxxx
{
public static void ColorCmdExecuted(object parameter)
{
var target = parameter as Panel;
if (target != null)
{
target.Background = target.Background == Brushes.AliceBlue ? Brushes.LemonChiffon : Brushes.AliceBlue;
}
}
public static bool ColorCmdCanExecute(object parameter)
{
return parameter is Panel;
}
}
You may want to replace the RoutedCommand with a custom implementation of the ICommand interface that can perform some action when you execute the command directly:
public class RelayCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
if (_execute != null)
_execute(parameter);
}
}
public partial class MainWindow : Window
{
public static RelayCommand ColorCmd = new RelayCommand(xxxx.ColorCmdExecuted, null);
public MainWindow()
{
InitializeComponent();
}
}
public class xxxx
{
public static void ColorCmdExecuted(object parameter)
{
var target = parameter as Panel;
if (target != null)
{
target.Background = target.Background == Brushes.AliceBlue ? Brushes.LemonChiffon : Brushes.AliceBlue;
}
}
}
XAML:
<Button Command="{x:Static local:MainWindow.ColorCmd}" Content="CommandTarget = FristStackPanel" />
Please refer to this blog post for more information about the concept.
You could continue to use static commands, but it is much more common to see an implementation of ICommand used called RelayCommand. This allows us to utilize the MVVM a little more easily so your view model model can take care of the commands. Here's a basic example implementation of ICommand:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Then, you would add the RelayCommand as a property to your view model such as like this:
public class ViewModel : ViewModelBase
{
private RelayCommand colorCmd;
public ICommand ColorCmd
{
get
{
colorCmd ?? colorCmd = new RelayCommand(p => ColorCmdCanExecute(), p => ColorCmdExectued());
return colorCmd;
}
}
private bool ColorCmdCanExecute()
{
//CanExecute code here
...
}
private void ColorCmdExecuted()
{
//Command execute code here
...
}
}
For more information on MVVM and ICommand implementation, there are lots of resources. This one is pretty easy to understand for a WPF beginner and should give you a bit more insight on how to proceed.
I have a simple button that uses a command when executed, this is all working fine but I would like to pass a text parameter when the button is clicked.
I think my XAML is ok, but I'm unsure how to edit my RelayCommand class to receive a parameter:
<Button x:Name="AddCommand" Content="Add"
Command="{Binding AddPhoneCommand}"
CommandParameter="{Binding Text, ElementName=txtAddPhone}" />
public class RelayCommand : ICommand
{
private readonly Action _handler;
private bool _isEnabled;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
Change Action to Action<T> so that it takes a parameter (probably just Action<object> is easiest).
private readonly Action<object> _handler;
And then simply pass it the parameter:
public void Execute(object parameter)
{
_handler(parameter);
}
You could just do
public ICommand AddPhoneCommand
{
get
{
return new Command<string>((x) =>
{
if(x != null) { AddPhone(x); }
};
}
}
Then, of course have your AddPhone:
public void AddPhone(string x)
{
//handle x
}
You can simply do this (no change to RelayCommand or ICommand required):
private RelayCommand _addPhoneCommand;
public RelayCommand AddPhoneCommand
{
get
{
if (_addPhoneCommand == null)
{
_addPhoneCommand = new RelayCommand(
(parameter) => AddPhone(parameter),
(parameter) => IsValidPhone(parameter)
);
}
return _addPhoneCommand;
}
}
public void AddPhone(object parameter)
{
var text = (string)parameter;
...
}
public void IsValidPhone(object parameter)
var text = (string)parameter;
...
}
I am trying to use a RoutedCommand on my view so that I can use the CanExecute functionality, but the only way I can get it to work is with a DelegateCommand from Prism. When I try to use the RoutedCommand the button stays inactive and the CanExecute function never gets used.
I've tried putting a CommandBinding on my XAML but that gives a "Only instance methods on the generated or code-behind class are valid." error. Here is that code:
<Window.CommandBindings>
<CommandBinding Command="AddCommand"
Executed="my:SettingsDialogViewModel.AddCommandMethod"
CanExecute="my:SettingsDialogViewModel.AddCommandMethodCanExecute" />
</Window.CommandBindings>
I've also tried setting up a CommandBinding in code, but that doesn't help either. I'm just not sure how to get it to work, short of sticking it in the code-behind, or implementing some ridiculously complicated looking thing I've found on the web.
Thanks for any help :)
EDIT:
Here are the methods I am trying to use:
public void AddCommandMethod()
{
if (SelectedMain != null)
{
SelectedMain.IsDirty = true;
_faveAppList.Add(SelectedMain);
SelectedMain.ListOrder = _faveAppList.Count;
_mainAppList.Remove(SelectedMain);
_listDirty = true;
}
}
public void AddCommandMethodCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
That isn't the proper MVVM notation. I'll provide one way of doing this.
// MyView.cs
public class MyView : UserControl
{
public MyViewViewModel ViewModel
{
get { return (MyViewViewModel) DataContext;}
set { DataContext = value; }
}
}
// DelegateCommand.cs
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute)
: this(execute, null) {}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public override bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public override void Execute(object parameter)
{
_execute(parameter);
}
}
// MyViewViewModel.cs
public class MyViewViewModel
{
public ICommand AddCommand {get;set;}
public MyViewViewModel()
{
AddCommand = new DelegateCommand (AddCommandMethod, AddCommandMethodCanExecute);
}
private void AddCommandMethod (object parameter)
{
}
private bool AddCommandMethodCanExecute(object parameter)
{
// Logic here
return true;
}
}
// MyView.xaml
<Button Command="{Binding AddCommand}" />
A better option would be to implement the ICommand interface and write your logic in the implemented methods. Then your view model can return your custom command and you could just bind to it from your view.
This will separate the actual command implementation from your view model but you can still nicely implement the logic within your view model.
Something like this:
public abstract class BaseCommand : ICommand
{
// needed to connect to WPF's commanding system
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public abstract bool CanExecute(object parameter);
public abstract void Execute(object parameter);
}
public class AddCommand : BaseCommand
{
private readonly MyViewModel _vm;
public AddCommand(MyViewModel vm)
{
this._vm = vm;
}
public override bool CanExecute(object parameter)
{
// delegate back to your view model
return _vm.CanExecuteAddCommand(parameter);
}
public override void Execute(object parameter)
{
_vm.ExecuteAddCommand(parameter);
}
}
public class MyViewModel
{
public ICommand AddCommand { get; private set; }
public MyViewModel()
{
AddCommand = new AddCommand(this);
}
public bool CanExecuteAddCommand(object parameter)
{
}
public void ExecuteAddCommand(object parameter)
{
}
}
Then just bind controls that issues the command.
<Button Command="{Binding AddCommand}">...</Button>