Wpf Combobox SelectionChanged event CommandParameter confuse - wpf

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>

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>

WPF how to binding an attached event to viewmodel?

<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}" />

TreeViewItem MouseDoubleClick event and MvvmLight

How do I set CommandParameter in the below code so that it will point to currently selected item?
<TreeView Grid.Column="0" HorizontalAlignment="Stretch" DockPanel.Dock="Left" ItemsSource="{Binding Path=ServerItems, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding ConnectServer}" PassEventArgsToCommand="True" CommandParameter="{Binding SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Databases}">
<TextBlock Text="{Binding}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
ViewModel code:
public RelayCommand<ServerItem> ConnectServer {
get;
private set;
}
ConnectServer = new RelayCommand<ServerItem>(param => ConnectToServer(param));
public void ConnectToServer(ServerItem item) {
MessageBox.Show(item.ToString());
}
Code execution doesn't get to ConnectToServer method because exception is thrown, telling me that cast from System.Windows.Input.MouseButtonEventArgs to type MadMin.Model.ServerItem is not possible.
You'll need to use a RelativeSource Binding in order to reach the TreeView.SelectedItem property from within the Trigger. Try this Binding for your CommandParameter instead:
CommandParameter="{Binding SelectedItem,
RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}"
{Binding SelectedItem, RelativeSource={RelativeSource Self}}

EventTrigger not working inside ItemsControl in MVVM

I want to bind multiple buttons dynamically in MVVM.
1.I Dynamically created buttons using ItemControl
2. It did not Invoke Trigger Click Event.
Please help me on this.
<ItemsControl ItemsSource="{Binding ComponentList,Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Tag="{Binding WorkFlowCompId}">
<Button.Content>
<TextBlock Text="{Binding ComponentName,Mode=TwoWay}"/>
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ComponentSelected}"
CommandParameter="{Binding WorkFlowCompId,Mode=TwoWay}" >
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your problem is that the command is getting the context from its template and there it cannot access the root of the ViewModel. Add this class to your solution:
public class DataContextProxy : FrameworkElement
{
public DataContextProxy()
{
this.Loaded += new RoutedEventHandler(DataContextProxyLoaded);
}
void DataContextProxyLoaded(object sender, RoutedEventArgs e)
{
Binding binding = new Binding();
if (!String.IsNullOrEmpty(BindingPropertyName))
{
binding.Path = new PropertyPath(BindingPropertyName);
}
binding.Source = this.DataContext;
binding.Mode = BindingMode;
this.SetBinding(DataContextProxy.DataSourceProperty, binding);
}
public Object DataSource
{
get { return (Object)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);
public string BindingPropertyName { get; set; }
public BindingMode BindingMode { get; set; }
}
then use it in you XAML like so:
<UserControl.Resources>
<library:DataContextProxy x:Key="DataContextProxy"/>
</UserControl.Resources>
Then in your command binding:
<Button Tag="{Binding WorkFlowCompId}">
<Button.Content>
<TextBlock Text="{Binding ComponentName,Mode=TwoWay}"/>
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataSource.ComponentSelected, Source={StaticResource DataContextProxy}"
CommandParameter="{Binding WorkFlowCompId,Mode=TwoWay}" >
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
A first pass at what you Xaml should look like:-
<ItemsControl ItemsSource="{Binding ComponentList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding SelectComponent}">
<TextBlock Text="{Binding ComponentName}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I suspect as Derek alludes to in his comment you have a ComponentSelected command on the container. However you should move this command ot the view model for the component. Note I've also renamed it to SelectComponent so that is sounds like an action rather than a property.
TwoWay binding has been removed it wouldn't be doing anything in this case. Assigning a Tag value from a simple binding should be setting off alarm bells that the design is having some problems.
BTW, since you are doing a form of selection would not a ListBox be more appropriate in this case?

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