Reputation: 1532
I have this code:
public ICommand ChangePageCommand
{
get
{
if (this.changePageCommand == null)
{
this.changePageCommand = new ActionCommand(
parameter => {
this.ChangeViewModel((IPageViewModel)parameter);
},
parameter => parameter is IPageViewModel);
}
return this.changePageCommand;
}
}
Whenever I call this particular command, passing a IPageViewModel
implementing class, the second parameter for ActionCommand
, which is the predicate, always is false. I basically have all the IPageViewModel
s in a dictionary. This is my entire ViewModel class:
using VexLibrary.Windows;
using System.Windows.Input;
using System.Collections.Generic;
using VexLibrary.DesktopClient.ViewModels;
using System.Linq;
namespace VexLibrary.DesktopClient.ViewModels
{
class ApplicationViewModel : ViewModel
{
private ICommand changePageCommand;
private IPageViewModel currentPageViewModel;
private Dictionary<string, IPageViewModel> pageViewModels;
public ApplicationViewModel()
{
this.PageViewModels.Add("Dashboard", new DashboardViewModel(this));
this.PageViewModels.Add("Statistics", new StatisticsViewModel());
this.CurrentPageViewModel = this.PageViewModels["Dashboard"];
}
public ICommand ChangePageCommand
{
get
{
if (this.changePageCommand == null)
{
this.changePageCommand = new ActionCommand(
parameter => {
this.ChangeViewModel((IPageViewModel)parameter);
},
parameter => parameter is IPageViewModel);
}
return this.changePageCommand;
}
}
public IPageViewModel CurrentPageViewModel
{
get
{
return this.currentPageViewModel;
}
set
{
if (this.currentPageViewModel != value)
{
this.currentPageViewModel = value;
NotifyPropertyChanged();
}
}
}
public Dictionary<string, IPageViewModel> PageViewModels
{
get
{
if (this.pageViewModels == null)
this.pageViewModels = new Dictionary<string, IPageViewModel>();
return this.pageViewModels;
}
}
public void ChangeViewModel(IPageViewModel viewModel)
{
this.CurrentPageViewModel = this.pageViewModels.FirstOrDefault(element => element.Value == viewModel).Value;
}
}
}
For some reason, although the parameter implements the IPageViewModel
, it always results to false. Here's my ActionCommand
class:
using System;
using System.Windows.Input;
namespace VexLibrary.Windows
{
public sealed class ActionCommand : ICommand
{
private readonly Action<Object> action;
private readonly Predicate<Object> predicate;
public event EventHandler CanExecuteChanged;
/// <summary>
/// Initializes a new instance of the <see cref="ActionCommand"/> class.
/// </summary>
/// <param name="action">The <see cref="Action"/> delegate to wrap.</param>
public ActionCommand(Action<Object> action) : this(action, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ActionCommand"/> class.
/// </summary>
/// <param name="action">The <see cref="Action"/> delegate to wrap.</param>
/// <param name="predicate">The <see cref="Predicate{Object}"/> that determines whether the action delegate may be invoked.</param>
public ActionCommand(Action<Object> action, Predicate<Object> predicate)
{
if (action == null)
{
throw new ArgumentNullException("action", "You must specify an Action<T>.");
}
this.action = action;
this.predicate = predicate;
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public bool CanExecute(object parameter)
{
if (predicate == null)
{
return true;
}
return predicate(parameter);
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
action(parameter);
}
/// <summary>
/// Executes the action delegate without any parameters.
/// </summary>
public void Execute()
{
Execute(null);
}
}
}
Here's a use case:
<Button Command="{Binding ChangePageCommand}" CommandParameter="{Binding PageViewModels[Dashboard]}">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
The thing here is, CanExecute
always evaluates to false, hence, the Button click is disable here. But, I tried replacing the predicate to just true
which makes the program work totally as expected. Here's the DashboardViewModel which is what I'm passing to the command here:
using VexLibrary.Windows;
using System.Windows.Input;
namespace VexLibrary.DesktopClient.ViewModels
{
class DashboardViewModel : ViewModel, IPageViewModel
{
private string name = "Dashboard";
private ApplicationViewModel parentViewModel;
private ICommand statisticsPageCommand;
public DashboardViewModel(ApplicationViewModel parentViewModel)
{
this.parentViewModel = parentViewModel;
}
public ICommand StatisticsPageCommand
{
get
{
return new ActionCommand(
parameter => this.parentViewModel.ChangeViewModel(this.parentViewModel.PageViewModels["Statistics"])
);
}
}
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
NotifyPropertyChanged();
}
}
}
}
Upvotes: 0
Views: 182
Reputation: 5121
Change the xaml of the button the otherway around so the CommandParameter is evaluated before the command.
<Button CommandParameter="{Binding PageViewModels[Dashboard]}" Command="{Binding ChangePageCommand}">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
Also when you change the CommandParameter's binding, you need to manually force the command's CanExecuteChanged evaluation.
You are probably better off using just the name as the parameter however.
<Button Command="{Binding ChangePageCommand}" CommandParameter="Dashboard">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
And in the viewmodel make following changes.
public ICommand ChangePageCommand
{
get
{
return new Command(
parameter => { this.ChangeViewModel(parameter as string); },
parameter =>
{
var str = parameter as string;
return !string.IsNullOrEmpty(str) && this.PageViewModels.ContainsKey(str);
});
}
}
public void ChangeViewModel(string viewName)
{
this.CurrentPageViewModel = this.pageViewModels.FirstOrDefault(element => element.Key == viewName).Value;
}
Upvotes: 1
Reputation: 949
As the parameter
is the same to both CanExecute
and Execute
, the only way for your snippet
this.changePageCommand = new ActionCommand(
parameter => { this.ChangeViewModel((IPageViewModel)parameter); },
parameter => parameter is IPageViewModel);
to not throw an exception in the first assignment (explicit casting) and to yield null as result in the second assignment is, to pass null
as parameter. So check for null and react appropriatly.
Hope this helps.
Upvotes: 2