WPF how to binding an attached event to viewmodel? - wpf

<TreeView x:Name="TestTree"
ItemsSource="{Binding Children}"
ItemTemplateSelector="{StaticResource TemplateSelector}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TreeViewItem.Seleted">
<i:InvokeCommandAction
Command="{Binding SelectedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
As above codes mentioned, I wanna get the selected treeviewitem data from the view, while the binding event TreeViewItem.Seleted which is an attached event(member event is okay) cannot be received in the viewmodel. How to binding an attached event to viewmodel?

then, you won't get TreeViewItem event in the TreeView ? you need a custom item template. I do it like this on a datagrid with MVVMLight (but no item)
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick" >
<command:EventToCommand Command="{Binding Path=OpenEquipementCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding Path=SelectionChangedCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
so i think you must do
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged" >
<command:EventToCommand Command="{Binding Path=SelectionChangedCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>

Why don't just use plain old eventhandler?
<TreeView x:Name="TreeView1" SelectedItemChanged="TreeView_SelectedItemChanged" />
public partial class MainWindow : Window
{
MainWindowViewModel ViewModel => (MainWindowViewModel) DataContext;
private void TreeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var element = (FrameworkElement)sender;
var item = element.DataContext;
ViewModel.SelectionChangedCommand.Invoke(item);
//alternativelly:
ViewModel.SelectedItem = TreeView1.SelectedItem;
}
}
Just because WPF support binding, it does not mean you have to use it everywhere even if it's very complicated. The code I've written is not violation of MVVM.
If you have a good reason to avoid code behind, you may implement this eventhandler in a custom behavior implemented as attached property, so it would look like this:
<TreeView local:TreeViewBehavior.SelectionChangedCommand="{Binding SelectionChangedCommand}" />

Related

WPF: how to get my TextBox text property using command

So this is what i have try:
<TextBox Name="TextBoxLatter">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<i:InvokeCommandAction Command="{Binding Path=TextBoxKeyDownCommand}"
CommandParameter="{Binding ElementName=TextBoxLatter, Path=Text}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
And inside my Execute method my parameter is null:
public void Execute(object parameter)
{
}
Your current approach should work if you handle the KeyUp event instead of KeyDown.
But you should bind the Text property of TextBoxLatter to a string source property of the view model. You could then access it directly in the Execute method of the command:
public void Execute(object _)
{
string text = this.YourProperty;
//...
}
XAML:
<TextBox Name="TextBoxLatter" Text="{Binding YourProperty, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<i:InvokeCommandAction Command="{Binding Path=TextBoxKeyDownCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>

Inner GroupBox does not swallow the Interactivity event

I have a GroupBox within a parent GroupBox. Both of them have their own
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ...}" />
</i:EventTrigger>
</i:Interaction.Triggers>
When I press the inner GroupBox it fires its own Command and then the parent Command is also triggered.
How do I prevent that? How do I make the inner GroupBox swallow the event?
You could use another implementation of TriggerAction that supports passing the event args as a command parameter to the command, for example the EventToCommand class in the MvvmLight library:
<GroupBox Header="Outer" xmlns:mvvm="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding OuterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock>...</TextBlock>
<GroupBox Header="Inner" Grid.Row="1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<mvvm:EventToCommand Command="{Binding InnerCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock>inner...</TextBlock>
</GroupBox>
</Grid>
</GroupBox>
public class ViewModel
{
public ViewModel()
{
OuterCommand = new RelayCommand(arg => true, (arg)=> { MessageBox.Show("outer"); });
InnerCommand = new RelayCommand(arg => true,
(arg) =>
{
MessageBox.Show("inner");
MouseButtonEventArgs mbea = arg as MouseButtonEventArgs;
if (mbea != null)
mbea.Handled = true;
});
}
public RelayCommand OuterCommand { get; }
public RelayCommand InnerCommand { get; }
}
The ugly thing with this solution is that the view model has a dependency upon the view related MouseButtonEventArgs type though. If you don't like this you can implement your own behaviour as suggested by #adabyron here:
MVVM Passing EventArgs As Command Parameter
Then you could set the Handled property of the MouseButtonEventArgs directly in the behaviour instead of passing it along to the view model.

WPF XAML ListView MouseOverItem

Do you know is it possible to get element on MouseOver from ListView in WPF using XAML?
I would like to bind mouse over element to command parameter.
What should I type in Path ?
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding SetOnMousePlayerCommand}"
CommandParameter="{Binding ElementName=leftPlayersListViewGame, Path=XXX}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Probably I have to do it in another way ? Could you tell my how ?
if you are looking to access the Event Args here how you should proceed:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<command:EventToCommand Command="{Binding Mode=OneWay,Path=MouseEnterCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
private RelayCommand<MouseEventArgs> _mouseEnterCommand;
public RelayCommand<MouseEventArgs> MouseEnterCommand
{
get
{
return _mouseEnterCommand
?? (_mouseEnterCommand= new RelayCommand<MouseEventArgs>(
(s) =>
{
//your logic
}));
}
}
but if you are looking for the sender of the Event, so here is your answer Pal :
Passing event args and sender to the RelayCommand

Wpf Combobox SelectionChanged event CommandParameter confuse

I have some academic question here. Look at markup:
<Grid Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ComboBox SelectedIndex="{Binding SelectedIndex}"
Margin="5"
Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding TestCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox},
Path=SelectedIndex}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ComboBoxItem>Item1</ComboBoxItem>
<ComboBoxItem>Item2</ComboBoxItem>
<ComboBoxItem>Item3</ComboBoxItem>
</ComboBox>
<Button Grid.Row="1"
Content="Set SelectedIndex to 0"
Width="100"
Command="{Binding ButtonCommand}"
Margin="5">
</Button>
</Grid>
This is DataContext class.
class Class1Context : ViewModelBase
{
private int _selectedIndex;
public Int32 SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
RaisePropertyChanged("SelectedIndex");
}
}
private RelayCommand<Object> _testCommand;
public RelayCommand<Object> TestCommand
{
get
{
return _testCommand ?? (_testCommand =
new RelayCommand<Object>(TestMethod));
}
}
private void TestMethod(Object obj)
{
var index = (Int32) obj;
var selIndex = SelectedIndex;
}
private RelayCommand _buttonCommand;
public RelayCommand ButtonCommand
{
get
{
return _buttonCommand ?? (_buttonCommand =
new RelayCommand(ButtonCommandMethod));
}
}
private void ButtonCommandMethod()
{
SelectedIndex = 0;
}
}
So, where is the problem? Here it is. When I select Item2 or Item3, then SelectedIndex property equals 1 or 2. For example, I clicked Item2 and SelectedIndex equals 1 now. Hence, when I click the Button and set SelectedIndex to 0 it generates the event SelectionChanged in Combobox. It is logically. Then, event fires the bounded command TestCommand.
And in TestMethod index (CommandParameter) equals to 1 (one!) and it is a problem, in spite of SelectedIndex of DataContext equals to 0 (zero).
So, is it a Wpf bug or something else?
I did it like this:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<ComboBox>
<ComboBox.Items>
<ComboBoxItem Content="item1" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Selected">
<i:InvokeCommandAction Command="{Binding item1Cmd}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBoxItem>
<ComboBoxItem Content="item2" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Selected">
<i:InvokeCommandAction Command="{Binding item2Cmd}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBoxItem>
<ComboBoxItem Content="item3" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Selected">
<i:InvokeCommandAction Command="{Binding item3Cmd}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
This way worked fine for me. I'm aware of a little code overhead using the same code for each ComboBoxItems.
If your ComboBox is needs a dynamic load then you should be able to add the Interaction.Triggers in code behind
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding TestCommand}" ></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>

MVVM-Light, firing events from a button inside a data grid column template

MVVM light has been a pleasure to learn, but here I am stuck. The problem is event firing.
In the code below, one button the works and fires events. The other button doesnt. No binding errors are reported in the output. Is there anything obvious I am missing?
<Grid x:Name="LayoutRoot">...
<StackPanel>
<Button Content="THIS BUTTON WORKS">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Command:EventToCommand Command="{Binding DataContext.HandleAddQuestionActionCommand, ElementName=LayoutRoot, Mode=OneWay}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<sdk1:DataGrid ItemsSource="{Binding QuestionActions}" AutoGenerateColumns="False" >
<sdk1:DataGrid.Columns>
<sdk1:DataGridTextColumn Binding="{Binding Answer.Name}" Header="Answer"/>
<sdk1:DataGridTemplateColumn Header="Edit">
<sdk1:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="THIS BUTTON DONT WORK" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Command:EventToCommand Command="{Binding DataContext.HandleEditQuestionActionCommand, ElementName=LayoutRoot, Mode=OneWay}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</sdk1:DataGridTemplateColumn.CellTemplate>
</sdk1:DataGridTemplateColumn>
</sdk1:DataGrid.Columns>
</sdk1:DataGrid>
</StackPanel>
ViewModel code:
public RelayCommand<RoutedEventArgs> HandleAddQuestionActionCommand {
get; private set;
}
public RelayCommand<RoutedEventArgs> HandleEditQuestionActionCommand {
get; private set;
}
HandleAddQuestionActionCommand = new RelayCommand<RoutedEventArgs>(e =>{...});
HandleEditQuestionActionCommand = new RelayCommand<RoutedEventArgs>(e =>{...});
Your data context is lost in the DataGrid DataGridTemplateColumn since the DataGrid.Columns isn't a dependency property. Because of this, you can't use element-to-element data binding from within your DataGridTemplateColumn.
However, this is easily fixed thanks to MVVM Light Toolkit's ViewModelLocator.
I don't know what your ViewModel is called, but assuming it is MainViewModel you can change your button binding to this:
<sdk1:DataGridTemplateColumn Header="Edit">
<sdk1:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="THIS BUTTON WILL WORK NOW ;-)" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Command:EventToCommand Command="{Binding Source={StaticResource Locator},
Path=MainViewModel.HandleEditQuestionActionCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</sdk1:DataGridTemplateColumn.CellTemplate>
</sdk1:DataGridTemplateColumn>
The button inside the DataGrid has a DataContext of QuestActions since the Binding is based on the the DataGrid's ItemSource Property. That being the case, you'll need to find the DataContext of the DataGrid itself (or the UserControl or whatever parent that has the Command in it's DataContext) to get to your Command:
<Command:EventToCommand
Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type sdk1:DataGrid}},
Path=DataContext.ViewSchemaCommand, Mode=OneWay}"
PassEventArgsToCommand="True" />
This solution only works for static view models. check out Dan Whalin's page out for an alternative answer. http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx
You can create a resource like so (don't forget your reference):
<UserControl.Resources>
<controls:DataContextProxy x:Key="DataContextProxy" />
</UserControl.Resources>
or
<sdk:Page.Resources>
<controls:DataContextProxy x:Key="DataContextProxy"/>
</sdk:Page.Resources>
Use in control like so:
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Content">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding Source={StaticResource DataContextProxy}, Path=DataSource.MyCommand}"
CommandParameter="{Binding Path=SomeValue}"
PassEventArgsToCommand="False">
</cmd:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
ViewModel
Define RelayCommand:
public RelayCommand<object> MyCommand { get; set; }
Set RelayCommand in Constructor:
MyCommand = new RelayCommand<object>((e) =>
{
if (e != null && e is int)
{
int varName = int.Parse(e.ToString());
//DoSomething...
}
});

Resources