I want to execute a command when the user selects a row in a DataGrid.
I see it is possible to wrap cell contents in buttons (although I don't want the button style) - but I don't want to do it at the cell-level.
I also see it's possible to use behaviours to link a command to an event. But preferably I should not have to resort to behaviors for such a common task.
Is it possible to do this via plain old command databinding?
So: 1) user clicks DataGrid row 2) command on view model is fired.
You should use "Interactivity" assembly and SelectionChanged event.
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding People}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Where "i" is namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Also you can write binding to SelectedItem property of the DataGrid and in the set accessor you can invoke your command, but the first solution that i presented you above is better.
If you want to invoke command from main view model and pass SelectedItem from DataGrid you can use CommadParameter:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}"
CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
When items has own command you can use following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=SelectedItem.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Or if elements has own view model that is assigned to it's DataContext, you can use following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=SelectedItem.DataContext.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Be advised that #kmatyaszek's answer is outdated in .NET 5.0 and above, we should use Microsoft.Xaml.Behaviors instead of Microsoft.Expression.Interactions.
So the i in the namespace should be:
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
See here for details.
Related
I have a text box A with an interaction trigger. The Data Context of the text box is a property in the view model. However, the ClearCommand in defined in the view Model. How can I change the Data Context of the Interaction Triggers or its Command to the view model it self.
Thank you
<TextBox Name="TextBoxA"Text="{Binding myObject.TextPrp,UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding ClearCommand}"
CommandParameter="{Binding ElementName=TextBoxB,Path=Text}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
You have to bind to relative source like this
<TextBox Name="TextBoxA"Text="{Binding myObject.TextPrp,UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type YourUserControl}, Path=DataContext. ClearCommand}"
CommandParameter="{Binding ElementName=TextBoxB,Path=Text}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
The title pretty much explains the question.
I have the following in the view in a Template:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SizeChanged">
<i:InvokeCommandAction>
<i:InvokeCommandAction.Command>
<Binding Source="{StaticResource eventBindingHelper}" Path="DataC.SizeChangedCommand"></Binding>
</i:InvokeCommandAction.Command>
<i:InvokeCommandAction.CommandParameter>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type igDP:LabelPresenter}}"/>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
The command binding works if it's bound to the viewmodel. But i would like to bind it to a behavior and i can't seem to find a way.
Make sure to add the reference to Behaviours SDK, and then you can do the following:
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Tapped" SourceObject="{Binding ElementName="Control to trigger"}"> //Bind behaviour the the element you want to trigger
<Core:InvokeCommandAction Command="{Binding Command}" CommandParameter="{Binding parameter}"/> // Bind command and parameter to behaviour
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
Then you can switch between the EventNames in EventTriggerBehaviour to trigger the controls as you want.
I used
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tapped"
SourceName="myControlName">
<i:InvokeCommandAction Command="{Binding Command}"
CommandParameter="{Binding parameter}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Similar to the accepted answer but with different namespaces/class names.
I have an editable ComboBox.
<ComboBox IsEditable="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding CritereChangedCommand, Mode=OneWay}"/>
</i:EventTrigger>
<i:EventTrigger EventName="TextBoxBase.TextChanged">
<cmd:EventToCommand Command="{Binding CritereChangedCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
I use GalaSoft.MvvmLight.Command.EventToCommand to bind the SelectionChanged event.
I also would like to bind the TextChanged event, but it is a little bit tricky:
This event is only accessible by the ComboBox TextBoxBase property, and I can't find the proper way to bind this event.
You can see one of my unsuccessful attempt: SelectionChanged binding works fine, but TextChanged binding does not.
I also tried this syntax:
<ComboBox IsEditable="True">
<TextBoxBase>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding CritereChangedCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBoxBase>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding CritereChangedCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
But this won't even compile. I get an error "Type that can be instantiated expected" on the TextBoxBase tag.
Any idea ?
I know, very late response... but I hope it will help someone.
The problem here is that the EventTrigger class in Microsoft.Expression.Interactivity.dll uses reflection to find the event by the value of the EventName property and this won't work for attached events like TextBoxBase.TextChanged.
One option, which I use, is to implement your own custom EventTrigger class as described here and use this one instead of EventTrigger (the link there to CommandAction implementation is broken, but I managed to get it using internet archive).
Another option, which I don't like because it's not classic MVVM Command use, is to bind the Text property of the ComboBox to a property in the ViewModel and invoke the command from the ViewModel code on the setter, like this:
<ComboBox IsEditable="True"
Text="{Binding Text, Mode=TwoWay}" />
And in the ViewModel:
private string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged("Text");
if(CritereChangedCommand.CanExecute())
CritereChangedCommand.Execute();//call your command here
}
}
I have found a way to work around the problem:
I create an invisible TextBox, bound to the ComboBox, and I bind the command on the TextChanged event of the TextBox.
It's not pretty, but it works...
<ComboBox Name="CbRubrique" IsEditable="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding CritereChangedCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<TextBox Text="{Binding ElementName=CbRubrique, Path=Text, UpdateSourceTrigger=PropertyChanged}" Visibility="Collapsed">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding CritereChangedCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
I am trying to run a command from a GridViewColumn, and the RadGridView is bound to my ViewModel. The code below runs the command, but when I select a value from the ComboBox, it saves the row immediately without allowing me to make changes to other cells first. This behavior happens whether or not I have the command bound to the RADComboBox. If I take the command away, still happens.
XAML:
<telerik:RadGridView x:Name="GV1"
ItemsSource="{Binding Path=Material}"
AutoGenerateColumns="False"
IsReadOnly="{Binding IsGridReadOnly}"
ShowGroupPanel="False"
VerticalAlignment="Top"
HorizontalAlignment="Center"
RowDetailsVisibilityMode="VisibleWhenSelected">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnded">
<cmd:EventToCommand Command="{Binding RowEditEndedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="CancelRowEdit">
<cmd:EventToCommand Command="{Binding CancelEditCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<telerik:RadGridView.Columns>
<telerik:GridViewColumn Header="Material Code">
<telerik:GridViewColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox ItemsSource="{Binding DataSource.AllMaterials, Source={StaticResource DCP}}"
DisplayMemberPath="Code"
SelectedValuePath="Code"
SelectedValue="{Binding Path=MaterialCode, Mode=TwoWay}"
IsEnabled="{Binding Path=IsMaterialEditable, Mode=TwoWay}"
Command="{Binding DataSource.MaterialCodeChangedCommand, Source={StaticResource DCP}}"/>
</DataTemplate>
</telerik:GridViewColumn.CellEditTemplate>
<telerik:GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MaterialCode}" />
</DataTemplate>
</telerik:GridViewColumn.CellTemplate>
</telerik:GridViewColumn>
<!--7 Other Columns here-->
</telerik:RadGridView.Columns>
</telerik:RadGridView>
Code Behind
public RelayCommand MaterialCodeChangedCommand { get; private set; }
//IN Constructor
this.MaterialTypeChangedCommand = new RelayCommand(MaterialTypeChange);
private void MaterialCodeChange()
{
//Command code here
}
When I take the ComboBox out of the CellEditTemplate and try to use the GridViewComboBox, I can't figure out how to get the binding to work, but it doesn't fire off the RowEditEnded just by selecting the drop down so that part work correctly with this code:
<telerik:GridViewComboBoxColumn Header="Material Type"
ItemsSource="{Binding DataSource.AllTypeCodes, Source={StaticResource DCP}}"
SelectedValueMemberPath="Code"
DataMemberBinding="{Binding Path=MaterialType, Mode=TwoWay}"
DisplayMemberPath="Display">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding DataSource.MaterialTypeChangedCommand, Source={StaticResource DCP}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</telerik:GridViewComboBoxColumn>
If this is unclear let me know, it's late and I may not be clear. Thanks for any help you can offer. I should note that the RowEditEnded acts like this on other drop downs in my application as well when set up in the CellEditTemplate. Converting to the to the GridViewComboBoxCOlumn resolves that but then the binding issue...
i have a tabcontrol. i'm trying to pass the tabcontrol as a parameter to figure out the selected tab item so i can get the tab header name. Binding this doesn't seem to work. ideas?
<TabControl Background="#FFF9F9F9" Height="650">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<n:ExecuteCommandAction Command="{Binding UpdateTabCommand}" Parameter="{Binding this}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
in my viewmodel constructor i have:
_updateTabCommand = new ActionCommand< TabControl>(UpdateTab);
private method:
public void UpdateTab(TabControl tabControl)
{
var tabItem = (TabItem)tabControl.SelectedItem;
1) Use ElementName binding.
Example:
<TabControl Background="#FFF9F9F9"
Height="650"
Name="TabControl1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateTabCommand}"
CommandParameter="{Binding ElementName=TabControl1}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TabControl>
2) Use FindAncestor binding:
Example:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateTabCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
First of all there is no such thing as {Binding this} in WPF. If you want to refer to the element on which you are setting Binding use RelativeSource binding with mode set to Self.
Second observation. Passing UI elements to ViewModel smells badly (impacts testability, increases code coupling and most likely the class will end up violating more good design principles). The fix is really simple: just bind TabControl.SelectedItem to the field on ViewModel.