How to use the CanExecute Method from ICommand on WPF - c#

How does one use the CanExecute Method from the ICommand interface?
In my example i have a SaveCommand which i only what to be enable when the object is saveable. The XAML Code of my Savebutton looks like this:
<Button Content="Save" Command="{Binding SaveCommand, Mode=TwoWay}" />
This is the code of my save class:
class Save : ICommand
{
public MainWindowViewModel viewModel { get; set; }
public Save(MainWindowViewModel viewModel)
{
this.viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
if (viewModel.IsSaveable == false)
return false;
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
viewModel.Save();
}
}
The save property in the ViewModel looks like this:
public ICommand SaveCommand
{
get
{
saveCommand = new Save(this);
return saveCommand;
}
set
{
saveCommand = value;
}
}
This construct didn't work. The button does not enable its self when isSaveable is true.

Instead of defining your own implementation of ICommand, use a RelayCommand.
In the below sample code, the save Button is enabled when the user types something in the TextBox.
XAML:
<Window x:Class="RelayCommandDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Margin="5" Width="120"/>
<Button Content="Save" Command="{Binding SaveCommand}" Margin="3"/>
</StackPanel>
</Window>
Code behind:
using System;
using System.Windows;
using System.Windows.Input;
namespace RelayCommandDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM
{
public String Name { get; set; }
private ICommand _SaveCommand;
public ICommand SaveCommand
{
get { return _SaveCommand; }
}
public VM()
{
_SaveCommand = new RelayCommand(SaveCommand_Execute, SaveCommand_CanExecute);
}
public void SaveCommand_Execute()
{
MessageBox.Show("Save Called");
}
public bool SaveCommand_CanExecute()
{
if (string.IsNullOrEmpty(Name))
return false;
else
return true;
}
}
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();
}
}
}

Related

WPF MVVM. Left DoubleClick on ListViewItem

What I want: If I do a Left DoubleClick on a ListViewItem I want to invoke a Method.
What I have:
Xaml:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Hidden"
Name="StructListView"
Margin="15"
Grid.Row="2"
BorderThickness="0"
SelectedItem="{Binding SelectedStructObject}"
ItemsSource="{Binding StructObjects, Mode=TwoWay}"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding Command}"
CommandParameter="{Binding ElementName=StructListView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
...
MainViewModel:
public class MainViewModel : BaseViewModel
{
private StructElement _selectedStructObject;
public StructElement SelectedStructObject
{
get { return _selectedStructObject; }
set
{
if (_selectedStructObject != value)
{
_selectedStructObject = value;
OnPropertyChanged();
}
}
}
public RelayCommand Command { get; set; }
public ObservableCollection<StructElement> StructObjects { get; set; }
private StructElement _structElement;
public StructElement StructObject
{
get { return _structElement; }
set
{
if (_structElement != value)
{
_structElement = value;
OnPropertyChanged();
}
}
}
public MainViewModel()
{
Command = new RelayCommand(o => { //Method; });
StructObjects = new ObservableCollection<StructElement>();
}
}
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; }
}
public bool CanExecute(object parameter)
{
if (canExecute == null)
{
return true;
}
return canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
}
I know that if I work with a Event I could use something like:
void OnMouseDoubleClick(Object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
// Method
}
}
But since I am working with a RelayCommand, I dont have a Event like MouseButtonEventArgs?
I thought about "triggering" the OnMouseDoubleClick with the RelayCommand?
I am quite knew to Programming and WPF, so if you have further Information on Things I should read into to understand the solution, I would be thankful for everything.
Thanks.
I came across a different Solution from the Link.
In my XAML I use "EventToCommand" from MVVMLight:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding LightCommand}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I made my RelayCommand generic so I can pass an Event (I hope I got the Description right):
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; }
}
public bool CanExecute(object parameter)
{
if (canExecute == null)
{
return true;
}
return canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
}
In my MainViewModel I declare a RelayCommand with a MouseButtonEventArgs and Pass a Method that checks If the MouseButton was a Leftclick and does something:
public RelayCommand<MouseButtonEventArgs> LightCommand { get; set; }
public void dosomething(MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
MessageBox.Show(e.OriginalSource.GetType().ToString());
}
}
public MainViewModel()
{
LightCommand = new RelayCommand<MouseButtonEventArgs>(dosomething);
}

WPF MVVM Switching Between Usercontrols

I am new to MVVM. I found this artcle and it resolve my haf of problems.
https://social.technet.microsoft.com/wiki/contents/articles/30898.simple-navigation-technique-in-wpf-using-mvvm.aspx
However, I need to navigate from one usercontrol to another from the button click event of one usercontrol not from the button on the main window.
Usercontrol 1:
Usercontrol 2:
This is what I have tried sofar;
class Usercontrol1ViewModel : INotifyPropertyChanged
{
public ICommand navCommand { get; set; }
public Usercontrol1ViewModel()
{
navCommand = new BaseCommand(navigate);
}
private void navigate(object obj)
{
NavigationViewModel mainViewModel = new NavigationViewModel();
mainViewModel.SelectedViewModel = new Usercontrol2ViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
NavigationView model class
Class NavigationViewModel : INotifyPropertyChanged
{
public ICommand btn1Command { get; set; }
public ICommand btn2Command { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
btn1Command = new BaseCommand(Opencontrl1);
btn2Command = new BaseCommand(Opencontrl2);
}
private void Opencontrl1(object obj)
{
SelectedViewModel = new Usercontrol1ViewModel();
}
private void Opencontrl2(object obj)
{
SelectedViewModel = new Usercontrol2ViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Main window code behind;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new NavigationViewModel();
}
}
Can somebody guide me how to achieve this with MVVM?
Try the below code. I used the sample code in the article and I am the author of the MSDN article :)
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type local:EmployeeViewModel}">
<local:EmployeeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:DepartmentViewModel}">
<local:DepartmentView/>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<ContentControl x:Name="Pages" DockPanel.Dock="Right" Content="{Binding SelectedViewModel}"/>
</DockPanel>
NavigationViewModel
class NavigationViewModel : INotifyPropertyChanged
{
public EmployeeViewModel EmployeeViewModel { get; set; }
public DepartmentViewModel DepartmentViewModel { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
SelectedViewModel = new EmployeeViewModel(OpenEmp);
}
private void OpenEmp(object obj)
{
if (obj.ToString() == "Dept")
{
SelectedViewModel = new DepartmentViewModel();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
EmployeeView
<Grid>
<StackPanel>
<TextBlock Text="This is employee view"/>
<Button Content="Navigate to Dept View" Command="{Binding Navigate}"/>
</StackPanel>
</Grid>
EmployeeViewModel
class EmployeeViewModel
{
private readonly Action<object> navigate;
public ICommand Navigate { get; set; }
public EmployeeViewModel(Action<object> navigate)
{
Navigate = new BaseCommand(OnNavigate);
this.navigate = navigate;
}
private void OnNavigate(object obj)
{
navigate.Invoke("Dept");
}
}

SizeToContent disables EventTrigger

I'm trying to make window size same as content, but when I use SizeToContent, it for some reason breaks my EventTrigger and it no longer invokes the command when the window is loaded.
Code sample:
WindowView:
<Window x:Class="Tasks.Views.TasksWindow"
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:Tasks.Views"
xmlns:ViewModels="clr-namespace:Tasks.ViewModels"
xmlns:Views="clr-namespace:Tasks.Views"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
SizeToContent="WidthAndHeight"
mc:Ignorable="d"
Title="TasksWindow">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadTasksCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Window.DataContext>
<ViewModels:TasksViewModel/>
</Window.DataContext>
<Views:TasksControl/>
</Window>
ViewModel:
public class TasksViewModel : ObservableObject
{
private readonly ObservableCollection<TaskModel> _tasks
= new ObservableCollection<TaskModel>();
public IEnumerable<TaskModel> Tasks
{
get { return _tasks; }
}
public ICommand LoadTasksCommand
{
get { return new DelegateCommand(LoadTasks); }
}
public void LoadTasks()
{
string directory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
string filePath = Path.Combine(directory, "Tasks.xml");
// Check if save file exists
if (!File.Exists(filePath))
return;
XmlSerializer xs = new XmlSerializer(typeof(TaskList));
using (StreamReader reader = new StreamReader("Tasks.xml"))
{
TaskList results = (TaskList)xs.Deserialize(reader);
foreach (var item in results.TasksList)
_tasks.Add(item);
}
}
}
Base:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
DelegateCommand:
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action) { _action = action; }
public void Execute(object parameter) { _action(); }
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
Model:
[XmlRoot("ArrayOfTaskModel")]
public class TaskList
{
[XmlElement("TaskModel")]
public List<TaskModel> TasksList { get; set; }
}
[Serializable]
public class TaskModel : ObservableObject
{
private string _name;
[XmlElement("Name")]
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChangedEvent("Name");
}
}
}

WPF - MVVM Bindings

I have problem with my application, especially with Bindings in WPF MVVM.
I created Model, ViewModel and View, this is part of my code (only this connected with my problem) When I click the button nemed : PointUp i want to see the amount of Team1 points. Can anyone tell me what i'm doing wrong?
View
<Window x:Class="Tabu.Game
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:Tabu"
xmlns:vm="clr-namespace:Tabu.ViewModel"
mc:Ignorable="d"
Title="Game" Height="600" Width="900" Background="Beige">
<Window.DataContext>
<vm:TeamStatistic />
</Window.DataContext>
<Grid>
<Button x:Name="PointUp" Command="{Binding AddPoints }" Content="+"/>
<Label x:Name="PointsTeam1_label" Content="{Binding Team1.TeamPoints, UpdateSourceTrigger=PropertyChanged }"/>
</Grid>
Model
'namespace Tabu.Model
{
public class Team
{
public bool IsTeamActive { get; set; }
public int TeamMiss { get; set; }
public int TeamPoints { get; set; }
public int TeamMistake { get; set; }
}
}'
ViewModel
namespace Tabu.ViewModel
{
class TeamStatistic : INotifyPropertyChanged
{
public Team Team1 = new Team();
public int TeamPoints
{
get { return TeamPoints; }
set { TeamPoints = value; OnPropertyChanged("TeamPoints"); }
}
public ICommand AddPoints
{
get { return new RelayCommand(() => Add_Points()); }
}
public void Add_Points()
{
Team1.TeamPoints++;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(params string[] propsName)
{
if (PropertyChanged!=null)
{
foreach(string propName in propsName)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public class RelayCommand : ICommand
{
private readonly Func<bool> canExecute;
private readonly Action execute;
public RelayCommand(Action execute)
: this(execute, null) { }
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { if (this.canExecute != null) CommandManager.RequerySuggested += value; }
remove { if (this.canExecute != null) CommandManager.RequerySuggested -= value; }
}
public Boolean CanExecute(object parameter) { return this.canExecute == null ? true : this.canExecute(); }
public void Execute(object parameter) { this.execute(); }
}
}
The problem is here:
public int TeamPoints
{
get { return TeamPoints; } //should be Team1.TeamPoints
set { TeamPoints = value; OnPropertyChanged("TeamPoints"); } //should be Team1.TeamPoints
}
Inside your TeamPoints property in ViewModel you return and set the same property TeamPoints from ViewModel but you should set from Model (Team1). You should return and set Team1.TeamPoints.
public int TeamPoints
{
get { return Team1.TeamPoints; }
set { Team1.TeamPoints = value; OnPropertyChanged("TeamPoints"); }
}
And Add_Points():
public void Add_Points()
{
Team1.TeamPoints++;
OnPropertyChanged("TeamPoints");
}
You have to update your Binding like this.
<Label x:Name="PointsTeam1_label" Content="{Binding TeamPoints, UpdateSourceTrigger=PropertyChanged }"/>
When you bind to Team1.TeamPoints you will not get the Notification from OnPropertyChanged which is inside your TeamPoints property.
I reckon it is because of AddPoints (the command binding). Since this command is binded & you are creating an instance of RelayCommand & returning each time it might be breaking the binding.
A better alternative with CommandBindings are to declare the property and initialize them in the Constructor of the view model.
Ex:
namespace Tabu.ViewModel
{
class TeamStatistic : INotifyPropertyChanged
{
public Team Team1 = new Team();
public int TeamPoints
{
get { return Team1.TeamPoints; }
set { Team1.TeamPoints = value; OnPropertyChanged("TeamPoints"); }
}
private ICommand _AddPoints;
public ICommand AddPoints
{
get { return _AddPoints; }
set { _AddPoints = value; }
}
public void Add_Points()
{
Team1.TeamPoints++;
}
public TeamStatistic ()
{
_AddPoinss = new RelayCommand(Add_Points);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(params string[] propsName)
{
if (PropertyChanged!=null)
{
foreach(string propName in propsName)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}

Pass parameter with a command into the viewmodel

I have trouble sending a parameter from my view to my viewmodel.
View.xaml:
In my view, I have the following:
<TextBox
MinWidth="70"
Name="InputId"/>
<Button
Command="{Binding ButtonCommand}"
CommandParameter="{Binding ElementName=InputId}"
Content="Add"/>
View.xaml.cs:
public MyView()
{
InitializeComponent();
}
public MyView(MyViewModel viewModel) : this()
{
DataContext = viewModel;
}
MyViewModel.cs:
public class MyViewModel : BindableBase
{
public ICommand ButtonCommand { get; private set; }
public MyViewModel()
{
ButtonCommand = new DelegateCommand(ButtonClick);
}
private void ButtonClick()
{
//Read 'InputId' somehow.
//But DelegateCommand does not allow the method to contain parameters.
}
}
Any suggestions to, how I can pass the InputId when I click the button to my viewmodel?
You need to add <object> to your delegate command like this :
public ICommand ButtonCommand { get; private set; }
public MyViewModel()
{
ButtonCommand = new DelegateCommand<object>(ButtonClick);
}
private void ButtonClick(object yourParameter)
{
//Read 'InputId' somehow.
//But DelegateCommand does not allow the method to contain parameters.
}
Do you want to get the text of textbox change your xaml to :
CommandParameter="{Binding Text,ElementName=InputId}"
For proper implementation of ICommand/RelayCommand have a look at this MSDN page.
Summary:
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Func<bool> _canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute.Invoke();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
public MyViewModel()
{
ButtonCommand = new RelayCommand(ButtonClick);
}
private void ButtonClick(object obj)
{
//obj is the object you send as parameter.
}

Categories