Clicking ContextMenu-option only triggers RelayCommand once [duplicate] - c#

This question already has an answer here:
WPF MenuItem style parameters not available on menu first open
(1 answer)
Closed 2 years ago.
Each ListBox item has a ContextMenu "Kick" option. It does trigger the CanKickPlayer() method when I right-click the first ListBox item for the first time, but never again if I repeat the process on the same or a different ListBox item. Questions:
How to do so CanKickPlayer() triggers every time I choose the ContextMenu "Kick" option?
Why is the passed parameter in CanKickPlayer() method null?
MainWindow.xaml
<Window x:Class="ContextMenuTriggeredOnce.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"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="200">
<GroupBox Header="Players">
<ListBox ItemsSource="{Binding Players}" SelectedItem="{Binding SelectedPlayer}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Kick" Command="{Binding KickPlayerCommand}" CommandParameter="{Binding SelectedPlayer}" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</GroupBox>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainViewModel MainViewModel { get; set; }
public MainWindow()
{
MainViewModel = new MainViewModel();
DataContext = MainViewModel;
InitializeComponent();
}
}
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
public ObservableCollection<string> Players { get; set; } = new ObservableCollection<string>();
private string _selectedPlayer;
public string SelectedPlayer
{
get => _selectedPlayer;
set
{
if (value == null)
{
return;
}
SetProperty(ref _selectedPlayer, value);
}
}
private readonly RelayCommand _kickPlayerCommand;
public ICommand KickPlayerCommand => _kickPlayerCommand;
public MainViewModel()
{
Players.Add("Player1");
Players.Add("Player2");
_kickPlayerCommand = new RelayCommand(OnKickPlayer, CanKickPlayer);
}
private void OnKickPlayer(object command)
{
Players.Remove(command.ToString());
_kickPlayerCommand.InvokeCanExecuteChanged();
}
private bool CanKickPlayer(object command)
{
return command != null;
}
}
BaseViewModel.cs
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
RelayCommand.cs
class RelayCommand : ICommand
{
private readonly Action<object> _executeAction;
private readonly Func<object, bool> _canExecuteAction;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
{
_executeAction = executeAction;
_canExecuteAction = canExecuteAction;
}
public void Execute(object parameter) => _executeAction(parameter);
public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;
public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

You need to set the CommandParameter before Command.
<MenuItem Header="Kick" CommandParameter="{Binding SelectedPlayer}" Command="{Binding KickPlayerCommand}" />
(I don't use the Command pattern. I prefer using the Click event and from the Click event handler calling a function in the ViewModel. If I need to disable the menu item, I use a bool property in the ViewModel and bind that to the IsEnabled property on the menu item.)

Related

Set up a binding between two properties in code behind

In my view, I have a ListBox with some templated items that contain buttons.
<ListBox x:Name="MyListBox" ItemTemplate="{DynamicResource DataTemplate1}"
ItemsSource="{Binding MyItems}">
</ListBox>
And the template for generated items:
<DataTemplate x:Key="DataTemplate1">
<StackPanel Orientation="Horizontal">
<Button Width="50" Click="Button_Click" />
</StackPanel>
</DataTemplate>
When user clicks a button on one of those ListBox items, I want to send the index of that ListBox item to my ViewModel.
So figured to use Binding as it seems to be the way in MVVM. But I'm struggling to set up a binding in code between two properties.
My View code is as follows:
public partial class ItemView : UserControl
{
ViewModel.ItemViewModel VM;
public ItemView()
{
InitializeComponent();
VM = new ViewModel.ItemViewModel();
this.DataContext = VM;
}
private int clickedItemIndex;
public int ClickedItemIndex { get => clickedItemIndex; set => clickedItemIndex = value; }
private void Button_Click(object sender, RoutedEventArgs e)
{
var ClickedItem = (sender as FrameworkElement).DataContext;
ClickedItemIndex = MyListBox.Items.IndexOf(ClickedItem);
}
}
I get the index and set it to ClickedItemIndex property,
I also have property in my ViewModel:
public int SomeInt { get; set; }
Now how do I set up a binding between these two properties?
I'm quite new to MVVM and still learning it. So, maybe this not the correct approach. But I need to have a way for each individual listbox item to be able to call upon an effect in more global viewmodel. For example, if I wanted to have a "Remove" button on each of the listbox items, I would somehow need to send the index to the viewmodel and call the removeItem method with index as the parameter. Or is there a better way to do similar things?
I have a sample app created just for this scenario. I know it seems a lot of code at first glance. Copy this code in your project, that will help debug and get a hang of it(MVVM, databinding, commands and so on).
usercontrol.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
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:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Model}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Name}"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.UpdateCommand}"
CommandParameter="{Binding}"
Content="Update"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.RemoveCommand}"
CommandParameter="{Binding}"
Content="Remove"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Models}">
</ListBox>
</Grid>
usercontrol.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
View model
public class ViewModel : INotifyPropertyChanged
{
private Models _Models;
public Models Models
{
get { return _Models; }
set { _Models = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Models)));
}
}
public ViewModel()
{
Models = new Models();
UpdateCommand = new Command(o => true, UpdateItem);
RemoveCommand = new Command(o => true, RemoveItem);
}
void RemoveItem(object item)
{
Model m = (item as Model);
Models.Remove(m);
}
void UpdateItem(object item)
{
Model m = (item as Model);
m.Name = m.Name + " updated";
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ICommand UpdateCommand { get; private set; }
public ICommand RemoveCommand { get; private set; }
}
Icommand implementation
public class Command : ICommand
{
private readonly Func<object, bool> _canExe;
private readonly Action<object> _exe;
public Command(Func<object,bool> canExecute,Action<object> execute)
{
_canExe = canExecute;
_exe = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExe(parameter);
}
public void Execute(object parameter)
{
_exe(parameter);
}
}
Model and a collection of models
public class Models : ObservableCollection<Model>
{
public Models()
{
Add(new Model ());
Add(new Model ());
Add(new Model ());
Add(new Model ());
}
}
public class Model : INotifyPropertyChanged
{
static int count = 0;
public Model()
{
Name = "Model "+ ++count;
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
You don't need to use a Button in order to select the item. When you click/tap on the item it will get automatically selected.
Then simply bind ListBox.SelectedIndex to your view model property SomeInt and it will update on every selection.
Data binding overview in WPF
You can also get the item itself by binding ListBox.SelectedItem to your view model.
You can handle new values by invoking a handler from the property's set method:
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
private int currentItemIndex;
public int CurrentItemIndex
{
get => this.currentItemIndex;
set
{
this.currentItemIndex = value;
OnPropertyChanged();
// Handle property changes
OnCurrentItemIndexChanged();
}
}
private MyItem currentItem;
public MyItem CurrentItem
{
get => this.currentItem;
set
{
this.currentItem = value;
OnPropertyChanged();
}
}
protected virtual void OnCurrentItemIndexChanged()
{
// Handle the new this.CurrentItemIndex value
}
// Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ItemView .xaml
<UserControl>
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<ListBox ItemsSource="{Binding MyItems}"
SelectedIndex="{Binding CurrentItemIndex}"
SelectedItem="{Binding CurrentItem}" />
</UserControl>

[UWP/MVVM]Enable/Disable Button in RadDataGrid Data Template Column that have commands bound to them upon conditions

I have set a bool property and have bound it to the IsEnabled in the xaml but the ICommand CanExecute method overrides the IsEnabled in xaml, so my bool property is ineffective.
When I define the conditions within the CanExecute method in the view model, It either disables all buttons in which the method is bound to, or enables all of them.
Its a grid that displays 3 different buttons for each row, and each button goes to a new xaml screen. If there is no data for the particular condition on the row the button is on then the button needs to be disabled.
How do i go about setting this so that buttons are disabled upon a condition?
Custom Command:
public class CustomCommand : ICommand
{
private Action<object> execute;
private Predicate<object> canExecute;
public CustomCommand(Action<object> execute, Predicate<object> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
}
remove
{
}
}
public bool CanExecute(object parameter)
{
//throw new NotImplementedException();
bool b = canExecute == null ? true : canExecute(parameter);
return b;
}
public void Execute(object parameter)
{
execute(parameter);
}
}
xaml
<DataTemplate>
<Button Command="{Binding Source={StaticResource VM},
Path=Command}" CommandParameter="{Binding}" >
<SymbolIcon Symbol="Edit" Foreground="AliceBlue" />
</Button>
</DataTemplate>
CanExecute in VM
private bool CanGetDetails(object obj)
{
return true;
}
You can always do your conditional statement within the CanExecute function of your custom command, no need for you to bind IsEnabled property with your button that is bound to a command. Here's a sample implementation, hope this helps.
Custom Command:
public class CustomCommand<T> : ICommand
{
private readonly Action<T> _action;
private readonly Predicate<T> _canExecute;
public CustomCommand(Action<T> action, Predicate<T> canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_action((T)parameter);
}
public event EventHandler CanExecuteChanged;
}
As you can see here, I created an object that implements the ICommand interface, this custom command accepts a generic type parameter which is used to evaluate a condition (CanExecute: this tells whether to enable or disable a command (in UI, the button), normally use to check for permissions, and other certain conditions) this parameter is also used to execute the action (Execute: the actual logic/action to be performed), The command contructor accepts delegate parameters that contain signatures for these 2 methods, the caller may choose lambda or standard methods to fillup these parameters.
Sample ViewModel:
public class ViewModel1: INotifyPropertyChanged
{
public ViewModel1()
{
// Test Data.
Items = new ObservableCollection<ItemViewModel>
{
new ItemViewModel{ Code = "001", Description = "Paint" },
new ItemViewModel{ Code = "002", Description = "Brush" },
new ItemViewModel{ Code = "003", Description = "" }
};
EditCommand = new CustomCommand<ItemViewModel>(Edit, CanEdit);
}
public CustomCommand<ItemViewModel> EditCommand { get; }
private bool CanEdit(ItemViewModel item)
{
return item?.Description != string.Empty;
}
private void Edit(ItemViewModel item)
{
Debug.WriteLine("Selected Item: {0} - {1}", item.Code, item.Description);
}
private ObservableCollection<ItemViewModel> _items { get; set; }
public ObservableCollection<ItemViewModel> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Page x:Name="root"
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vms="using:App1.ViewModels"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
d:DesignHeight="450" d:DesignWidth="800">
<Page.DataContext>
<vms:ViewModel1 x:Name="Model"/>
</Page.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0 0 0 15">
<TextBlock Text="{Binding Code}" />
<TextBlock Text="{Binding Description}" />
<Button Content="Edit" Command="{Binding DataContext.EditCommand, ElementName=root}" CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Page>
I think you can pick a lot of code from the RelayCommand of MVVMLight. Try to change your event to
public event EventHandler CanExecuteChanged
{
add
{
if (canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
and add also a function
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
Then, whatever you put as your Predicate on the command, at the Predicate's boolean setter do:
SomeCustomCommand.RaiseCanExecuteChanged()
Hope I helped.

wpf bind event or command to function on item data context

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();
}
}

How to take a WPF CommandParameter to the ViewModel in MVVM?

I hope somebody can help me out here. Simplified the code for posting.
We have a main window (MvvmTestView) with a menu, and a 2nd window (SettingsView) which holds several tabs. I can open the SettingsView window alright. I can even select which Tab to open by setting this in the code.
How can I get back the correct value with the command parameter from the XAML code so that the correct tab opens?
MvvmTestView.xaml:
<Window x:Class="MvvmTest.Views.MvvmTestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MvvmTest.ViewModels"
WindowStartupLocation="CenterScreen"
Title="MvvmTestView"
Height="500"
Width="500">
<Window.DataContext>
<vm:MvvmTestViewModel/>
</Window.DataContext>
<Grid>
<DockPanel>
<Menu>
<MenuItem Header="Menu">
<MenuItem
Header="Tab01"
Command="{Binding SettingsViewCommand}"
CommandParameter="0"/>
<MenuItem
Header="Tab02"
Command="{Binding SettingsViewCommand}"
CommandParameter="1"/>
</MenuItem>
</Menu>
</DockPanel>
<DockPanel>
<Label Content="MainView" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DockPanel>
</Grid>
</Window>
SettingView.xaml
<Window x:Class="MvvmTest.Views.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabData="clr-namespace:MvvmTest.Views"
xmlns:vm="clr-namespace:MvvmTest.ViewModels"
WindowStartupLocation="CenterScreen"
Title="SettingsView"
Height="400"
Width="400">
<Window.DataContext>
<vm:MvvmTestViewModel/>
</Window.DataContext>
<Grid>
<TabControl
SelectedIndex="{Binding SettingsSelectedIndex, Mode=TwoWay}">
<tabData:Tab01View/>
<tabData:Tab02View/>
</TabControl>
</Grid>
</Window>
SettingsViewModel.cs
using System.ComponentModel;
namespace MvvmTest.ViewModels
{
public class SettingsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
private int _settingsSelectedIndex;
public int SettingsSelectedIndex
{
get
{
return _settingsSelectedIndex;
}
set
{
_settingsSelectedIndex = value;
OnPropertyChanged("SettingsSelectedIndex");
}
}
}
}
MvvmTestViewModel.cs
using MvvmTest.Commands;
using MvvmTest.Views;
using System.ComponentModel;
using System.Windows.Input;
namespace MvvmTest.ViewModels
{
internal class MvvmTestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private SettingsViewModel SettingsViewModel;
public MvvmTestViewModel()
{
SettingsViewModel = new SettingsViewModel();
SettingsViewCommand = new SettingsViewCommand(this);
}
public ICommand SettingsViewCommand
{
get;
private set;
}
public void SettingsWindow()
{
SetIndex();
SettingsView settingsView = new SettingsView()
{
DataContext = SettingsViewModel
};
settingsView.ShowDialog();
}
public int SetIndex()
{
SettingsViewModel.SettingsSelectedIndex = 1;
return SettingsViewModel.SettingsSelectedIndex;
}
}
}
SettingsViewCommand.cs
using MvvmTest.ViewModels;
using System;
using System.Windows.Input;
namespace MvvmTest.Commands
{
internal class SettingsViewCommand : ICommand
{
private MvvmTestViewModel settingsViewModel;
public SettingsViewCommand(MvvmTestViewModel settingsViewModel)
{
this.settingsViewModel = settingsViewModel;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
settingsViewModel.SettingsWindow();
}
}
}
I suggest to avoid creating multiple command classes like SettingsViewCommand : ICommand. Instead use some general-purpose command class (e.g. RelayCommand from MvvmFoundation NuGet package)
assuming you added MvvmFoundation to your project, refactor MvvmTestViewModel class like this:
internal class MvvmTestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private SettingsViewModel SettingsViewModel;
public MvvmTestViewModel()
{
SettingsViewModel = new SettingsViewModel();
SettingsViewCommand = new RelayCommand<int>(SettingsWindow);
}
public ICommand SettingsViewCommand
{
get;
private set;
}
public void SettingsWindow(int index)
{
SettingsViewModel.SettingsSelectedIndex = index;
SettingsView settingsView = new SettingsView()
{
DataContext = SettingsViewModel
};
settingsView.ShowDialog();
}
}
CommandParameter from a view is passed to SettingsWindow method in a viewModel and used to change selected index

Use ICommand to change the state of the ViewModel

I would like to use ICommand to change the Paddle1.Y int value of my ViewModel. Am I supposed to create a class implementing ICommand interface? I have done that. But since it is a class, it doesn't have access to my ViewModel's Paddle1 property without creating a property for it. I would prefer to create the command within my ViewModel for this reason. At this point I'm trying to pass the Paddle1 to the Command as a CommandParameter in XAML. I am failing at this, and I'm not sure it is the cleanest approach to editing the state of my ViewModel either.
Could I get a code example of my UpKeyPressed command being bound to either a button or the keyboard up key? With no CommandParameter would be more clean, if the command could access my ViewModel Paddle1 property.
My ViewModel:
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
{
private readonly Paddle Paddle1;
private Paddle Paddle2;
public GamePlayViewModel()
{
Paddle1 = new Paddle();
Paddle2 = new Paddle();
UpKeyPressed();
}
public ICommand UpKeyPressed()
{
var r = new UpKeyPressed();
r.Execute(Paddle1);
return r;
}
}
public class UpKeyPressed : ICommand
{
public void Execute(object parameter)
{
var paddle = parameter as Paddle;
Debug.Assert(paddle != null, "paddle != null");
paddle.IncreaseY();
Debug.WriteLine(paddle.Y);
}
public bool CanExecute(object parameter)
{
return parameter != null;
}
public event EventHandler CanExecuteChanged;
}
}
My XAML page that uses the viewmodel as a dataContext:
<Window x:Class="Pong.Windows.Views.GamePlayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Pong.Core.ViewModels;assembly=Pong.Core"
Title="GamePlayView" Height="350" Width="525">
<Grid>
<Button CommandParameter="{Binding ElementName=Paddle1}"
Command="{StaticResource UpKeyPressed}" >
Click
</Button>
</Grid>
<Window.DataContext>
<local:GamePlayViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=UpKeyPressed}"
Key="O"
Modifiers="Control"/>
</Window.InputBindings>
</Window>
Data structure of my solution
My attempt to fix:
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
{
private readonly Paddle Paddle1;
private Paddle Paddle2;
private ICommand _doSomething;
public ICommand DoSomethingCommand
{
get
{
if (_doSomething == null)
{
_doSomething = new UpKeyPressed(Paddle1);
}
return _doSomething;
}
}
public GamePlayViewModel()
{
Paddle1 = new Paddle();
Paddle2 = new Paddle();
}
}
public class UpKeyPressed : ICommand
{
private Paddle Paddle1;
public UpKeyPressed(Paddle paddle)
{
Paddle1 = paddle;
}
public void Execute(object parameter)
{
//var paddle = parameter as Paddle;
//Debug.Assert(paddle != null, "paddle != null");
//paddle.IncreaseY();
Paddle1.IncreaseY();
//Debug.WriteLine(paddle.Y);
Debug.WriteLine(Paddle1.Y);
}
public bool CanExecute(object parameter)
{
return Paddle1 != null;
}
public event EventHandler CanExecuteChanged;
}
}
XAML attempt (no errors but not workling upon pressing the 'O' key):
<Window x:Class="Pong.Windows.Views.GamePlayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:Pong.Core.ViewModels;assembly=Pong.Core"
Title="GamePlayView" Height="350" Width="525">
<Grid>
</Grid>
<Window.DataContext>
<viewModels:GamePlayViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding DoSomethingCommand}"
Key="O"
Modifiers="Control"/>
</Window.InputBindings>
Looked at your attempt, there are some things we need to be fix, first your CanExecute should not involve the parameter anymore:
public bool CanExecute(object parameter) {
return Paddle1 != null;
}
Secondly your XAML binding is wrong, you already have DataContext of your view-model flown in your visual tree, you just need a simple Binding with some Path specified like this:
<KeyBinding Command="{Binding DoSomethingCommand}"
Key="O"
Modifiers="Control"/>

Categories