How to create CommandBinding with a Parameter? - wpf

Is there a way to create a CommandBinding with a parameter?
You can normally add bindings to commands for controls doing something like this:
<Control.CommandBindings>
<CommandBinding Command="ApplicationCommands.XXX" CanExecute="XXX_CanExecute"
Executed="XXX_Executed">
</CommandBinding>
</Control.CommandBindings>
I was wondering if there was also a way to pass a paramater?

No, you dont have a CommandParameter on the CommandBinding but you can use the InputBindings from UIElement to add for example a MouseBinding, which has a Command and CommandParameter attribute.
<Control>
<Control.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{StaticResource MyCommand}"
CommandParameter="{x:Static system:Boolean.TrueString}"/>
</Control.InputBindings>
</Control>

You have to assign the parameter value to the ICommandSource.CommandParameter property of the element that invokes the command (for example Button.CommandParameter). This property also accepts a Binding.
You can retrieve the parameter from the ExecutedRoutedEventArgs.Parameter and CanExecuteRoutedEventArgs.Parameter property.
<Window>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Delete"
CanExecute="DeleteCommand_CanExecute"
Executed="DeleteCommand_Executed" />
</Control.CommandBindings>
</Window.CommandBindings>
<Button Content="X"
Command="{x:Static ApplicationCommands.Delete}"
CommandParameter="abc" />
</Window>
private void DeleteCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
var commandParameter = e.Parameter as string; // Returns "abc"
}
private void DeleteCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
var commandParameter = e.Parameter as string; // Returns "abc"
}
When clicking the Button or an ICommandSource in general, it sends the routed command along with the command parameter up the logical tree. The CommandBinding can catch this event and handle it using the registered handlers.
The point is that the command source must define the command parameter and not the CommandBinding. This way, the same CommandBinding can handle different command sources that send different command parameters.

Have you tried the CommandParameter property?
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="MyHeader"
Command="{Binding MyCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItem}" />
</DataGrid.ContextMenu>

Related

Prism for WPF - How to send CancelEventArgs to viewmodel?

How to send CancelEventArgs to the viewmodel on window close? I tried following way, but CloseWindow command method always receive CancelEventArgs argument as null. How to get CancelEventArgs to viewmodel in prism way?
<!-- View-->
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Closing">
<interactivity:InvokeCommandAction Command="{Binding CloseWindowCommand}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
ViewModel
CloseWindowCommand = DelegateCommand<CancelEventArgs>.FromAsyncHandler(CloseWindow);
public async Task CloseWindow(CancelEventArgs args)
{
//Do Stuff
}
You should use Prism's own InvokeCommandAction class:
xmlns:prism="http://prismlibrary.com/"
...
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Closing">
<prism:InvokeCommandAction Command="{Binding CloseWindowCommand}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
The InvokeCommandAction class that is part of the Blend SDK doesn't support passing the EventArgs as a command parameter.

Binding commands to ToggleButton Checked and Unchecked events

I have a ToggleButton in my C# WPF application where I would like to bind one Command to the Checked event and one Command to the Unchecked event.
What I have currently is the following:
<ToggleButton Name="btnOpenPort" Style="{StaticResource myOnOffBtnStyle}" Content="Open Port"
Checked="btnOpenPort_Checked" Unchecked="btnOpenPort_Unchecked"
IsChecked="{Binding Path=PortViewModel.PortIsOpen, Mode=OneWay}"
Canvas.Left="75" Canvas.Top="80" Height="25" Width="100"/>
But this is not what I aim to do. Because in this case, I would have to set properties in the code behind for the Checked and Unchecked event.
Instead, I would like to call a Command (ICommand) in my ViewModel once the Checked or Unchecked event gets fired so that I don't need any code-behind for my toggle button.
Is there a way to bind a command directly for these two events in XAML?
Similar to the command property of the "standard" button control in WPF?
EDIT
This is how it works with regards to #har07 hint:
1: Added references if you dont have it yet:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
2: Implemented Interaction.Triggers for Checked and Unchecked events:
<ToggleButton
Name="btnOpenPort" Style="{StaticResource myOnOffBtnStyle}" Content="Open Port"
IsChecked="{Binding Path=PortViewModel.PortIsOpen, Mode=OneWay}"
Canvas.Left="75" Canvas.Top="80" Height="25" Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=PortViewModel.OpenPort}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding Path=PortViewModel.ClosePort}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ToggleButton>
With this solution, I don't have to change a single line of code in my ViewModel or my code behind.
I can just call my ICommand as I would do it with a standard button following MVVM pattern.
you may not be able to bind two commands for each checked and unchecked directly however you can still bind a command, which will be invoked for both. you also have option for attached behaviors if you need different command for both events.
<ToggleButton Command="{Binding MyCommand}"/>
in the vm
public ICommand MyCommand { get; private set; }
you will need to initialize it accordingly
and to determine the current state you may have a condition on the bonded property PortIsOpen
void Execute(object state)
{
if(PortIsOpen)
{
//checked
}
else
{
//unchecked
}
}
or perhaps you may pass it as a parameter too
eg
<ToggleButton Command="{Binding MyCommand}"
CommandParameter="{Binding IsChecked,RelativeSource={RelativeSource Self}}"/>
and use it as
void Execute(object state)
{
if((bool)state)
{
//checked
}
else
{
//unchecked
}
}
Maybe we can use EventTriggers
<ToggleButton>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=CheckedCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding Path=UncheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ToggleButton>
to use Triggers we have to reference System.Windows.Interactivity
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
You can put the logic to handle checked/unchecked event in the setter of PortIsOpen property :
private bool _portIsOpen;
public bool PortIsOpen
{
get { return _portIsOpen; }
set
{
if(value) HandleCheckedEvent();
else HandleUnCheckedEvent();
....
}
}
Or you can use Ineraction.Triggers extension to bind event to commmand :
WPF Binding UI events to commands in ViewModel
<ToggleButton Name="btnOpenPort" Style="{StaticResource myOnOffBtnStyle}" Content="Open Port"
Checked="{Binding ICommand}" Unchecked="{Binding ICommand}"
IsChecked="{Binding Path=PortViewModel.PortIsOpen, Mode=OneWay}"
Canvas.Left="75" Canvas.Top="80" Height="25" Width="100"/>
Replace ICommand with your ICommand property name.

How to create hotkey for button by modifying xaml?

I am trying to learn keybinding in WPF by a simple example.
here is my XAML file:
<Window.Resources>
<RoutedUICommand x:Key="myNewCommand"></RoutedUICommand>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource myNewCommand}" Executed="Button_Click"></CommandBinding>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="{Binding myNewCommand}" Key="B" />
</Window.InputBindings>
<Grid>
<Button Command="{Binding myNewCommand}" Click="Button_Click" Content="Click Here"/>
</Grid>
And this is code behind for Button_Click :
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("hello");
}
I get the "hello" message when I click on button, but no response when I press "B" on keyboard.
I want to have this binding without changing the Button_click, can I do it just in XAML? how?
Your command bindings aren't correct. You should replace {Binding myNewCommand} with {Binding Source={StaticResource myNewCommand}}. It is also not necessary to have a Click handler on the button when you have already bound a command.
<Window.Resources>
<RoutedUICommand x:Key="myNewCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource myNewCommand}"
Executed="MyCommandExecuted"/>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="{Binding Source={StaticResource myNewCommand}}" Key="B" />
</Window.InputBindings>
<Grid>
<Button Command="{Binding Source={StaticResource myNewCommand}}"
Content="Click Here"/>
</Grid>
The Executed handler:
private void MyCommandExecuted(object sender, RoutedEventArgs e)
{
MessageBox.Show("hello");
}

How to fire event from button inside datagrid in silverlight and MVVM

I have button at first column in datagrid. I am using MVVM and try to bind Command to Command in ViewModel but when I click button in each row, it don't work (It don't call Command in ViewModel) but if I move that button out of datagrid it's working properly.
How can I fire event from button inside datagrid in MVVM?
Update 1:
XAML's code is:
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center">
<Button x:Name="button" Content="View" Margin="5" DataContext="{StaticResource XDataContext}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ViewOrganizationCommand}"
CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
ViewModel's code is:
public ViewModelCommand ViewOrganizationCommand { get; set; }
Instead of using EventTrigger, why not simply bind to Button.Command directly, like so?
<Button
...other properties...
Command="{Binding ViewOrganizationsCommand}"
CommandParameter="{Binding}"/>
That will bind the command, and set the CommandParameter to the DataContext of the Button, which presumably is a good thing to bind the parameter to. If it's not, just bind CommandParameter to something else that helps you uniquely identify the specific row being clicked on.
This article brings the solution with DataContextProxy. Applying this solution makes possible to write a button code like Austin Lamb's answer.
The command binding using the Event Trigger looks fine (This is how i always do it),
But I suspect that your command is never assigned to (You are using Automatic Properties)
I usually do it like this:
private ViewModelCommand viewOrganizationCommand;
public ViewModelCommand ViewOrganizationCommand
{
get
{
if (viewOrganizationCommand == null)
viewOrganizationCommand = new ViewModelCommand(Action, CanDoIt);
return viewOrganizationCommand;
}
}
try setting you Button's DataContext to the StackPanel and set the Command and CommandParameter of the Button.
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center" DataContext="{StaticResource XDataContext}">
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand}" CommandParameter="{Binding ElementName=dtgOrganizations, Path=SelectedItem}" >
</Button>
</StackPanel>
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
I solved this problem by use EventToCommand behavior in MVVMLight Toolkit
Each row of the DataGrid has it's DataContext set to that object. If the ItemSource for the grid is an ObservableCollection, each row's DataContext is the Organization object.
There are two ways to handle this.
Create a wrapping or extension ViewModel that exposes the command. Then the command should fire.Here is some psuedo code.
public class OrganizationExtensionViewModel
{
<summary>
/// Private currentOrginization property.
/// </summary>
private Organization currentOrginization;
public Organization CurrentOrganization
{
get
{
return this.currentOrginization;
}
set
{
if (this.currentOrginization != value)
{
this.currentOrginization = value;
this.RaisePropertyChanged("CurrentOrganization");
}
}
}
public ViewModelCommand ViewOrganizationCommand { get; set; }
public OrganizationExtensionViewModel(Organization o)
{
this.CurrentOrganization = o;
this.ViewOrganizationCommand = new ViewModelCommand(this.ViewOrgnaizationClicked);
}
}
Define the ViewModel in the xaml as a StaticResource and refer to it as the binding path.
...
In the grid
<Button x:Name="button" Content="View" Margin="5" Command="{Binding ViewOrganizationCommand, Source={StaticResource viewModel}}" />

MVVM Light is too fast :)

I have a simple WM7 Page with a TextBox. Futher, I assigned EventToCommand (a RelayCommand<string>) to this TextBox, reacting to the TextChanged event. For testing pourposes I made additional method TextBox_TextChanged in the page's code behind. Both the command and TextBox_TextChanged print a message box with the textbox content.
Initial value of the TextBox is "ABC". Then I press D and:
TextBox_TextChanged prints ABCD.
The command prints ABC. D is missing.
Why is the command so fast?
Command declaration:
public RelayCommand<string> TextChanged {get; private set;}
Command initialization:
TextChanged = new RelayCommand<string>((s) => MessageBox.Show(s));
Command binding:
<TextBox x:Name="SearchTextBox" Margin="10,0" TextWrapping="Wrap" Text="{Binding SearchString, Mode=TwoWay}" FontStyle="Italic" TextChanged="SearchTextBox_TextChanged" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding TextChanged, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
I can't reproduce this behaviour. I have tried using EventToCommand and a Behaviour(which simply listens to TextChanged event).
Without seeing the code I suspect this might be to do with how you get the text of the search box or a logic error elsewhere.
This is a snippet of how I use EventToCommand:
<TextBox Name="SearchTextBox">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding TestTextChangedCommand,Mode=OneWay}" CommandParameter="{Binding Path=Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
<i:Interaction.Triggers>
</TextBox>
In the viewmodel
m_TestTextChangedCommand = new RelayCommand<string>(val => System.Diagnostics.Debug.WriteLine(val));
As you can see I used a commandparameter to pass the value of the textbox to the viewmodel. This way the viewmodel doesn't have to know about the textbox to get the text value.
An alternative to this approach would be to use behaviours and TwoWay binding to update a property:
<TextBox Name="SearchTextBox" Text="{Binding TextInViewModel, Mode=TwoWay}" >
<i:Interaction.Behaviors>
<sc:UpdateOnTextChangedBehavior/>
</i:Interaction.Behaviors>
</TextBox>
UpdateOnTextChangedBehavior class:
public class UpdateOnTextChangedBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged +=
new TextChangedEventHandler(AssociatedObject_TextChanged);
}
void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(((TextBox)sender).Text);
BindingExpression binding =
this.AssociatedObject.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -=
new TextChangedEventHandler(AssociatedObject_TextChanged);
}
}
What the above does is mimick the behaviour of desktop WPF Binding with UpdateSourceTrigger=PropertyChanged, which is missing in Silverlight. So what will happen, whenever you type into the text box TextInViewModel property will get updated. This property doesn't haven to be a DependencyProperty, it could just be a normal CLR property.
This works with TextBox via parameter for RelayCommand. IOW - RelayCommand<TextBox>
<TextBox Height="72" HorizontalAlignment="Left" Margin="8,136,0,0" Name="txtFilter" Text="" VerticalAlignment="Top" Width="460" >
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding SearchedTextChanged}" CommandParameter="{Binding ElementName=txtFilter}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</TextBox>
public RelayCommand<TextBox> SearchedTextChanged { get; set; }
SearchedTextChanged = new RelayCommand<TextBox>(OnSearchedTextChanged);
private void OnSearchedTextChanged(TextBox val)
{
if (val != null)
{
System.Diagnostics.Debug.WriteLine(val.Text);
}
}
I had a similar issue and found that the databinding operation does not always fire until the TextBox loses focus. However, the Command will fire immediately.
If you want to guarantee that the databinding has occurred before you use the value, you can call the BindingExpression.UpdateSource() method on your control. Try something like this:
var bindTarget = SearchTextBox.GetBindingExpression(TextBox.TextProperty);
bindTarget.UpdateSource();
To avoid referring to your TextBox directly in your ViewModel (as you should with MVVM), you can use FocusManager.GetFocusedElement(). This is particularly useful when dealing with ApplicationBar buttons as they don't seem to receive focus when used.
Some code I sued (similar to yours Command example):
Command declaration:
public RelayCommand<string> TextChanged {get; private set;}
Command initialization:
TextChanged = new RelayCommand<string>((s) => MessageBox.Show(s));
Command binding:
<TextBox x:Name="SearchTextBox" Margin="10,0" TextWrapping="Wrap" Text="{Binding SearchString, Mode=TwoWay}" FontStyle="Italic" TextChanged="SearchTextBox_TextChanged" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding TextChanged, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=SearchTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
For some reasons messagebox shows a string with one character delay.

Resources