TreeViewItem MouseDoubleClick event and MvvmLight - wpf

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

Related

Binding Comboboxes inside an Itemscontrol

I am having trouble with a combination of ComboBox'es inside an ItemsControl.
What I am trying to do is create a List of ComboBox'es. At the start there is only one default ComboBox with a default value. If you select any other type than the default type, a new ComboBox is added with the default type in it.
Now, each Item in the List actually contains of 2 Combobox'es. The second box displays the number, this type is present.
Before:
After:
Now I want to update the numbers in the second box if any other number in the ItemsControl changes. How do I do that?
Here is the relevant code:
<ItemsControl Grid.Column="0" Grid.Row="2" ItemsSource="{Binding ChosenAppartmentTypeList}" Margin="10">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Margin="10" SelectedIndex="0" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GeneralProjectDataView}}, Path=DataContext.AppartmentTypeList}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GeneralProjectDataView}}, Path=DataContext.ComboBoxSelectedItemChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GeneralProjectDataView}}, Path=DataContext.NumberList}" SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}, Path=SelectedItem.Count}" SelectedIndex="0">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Those are the lists:
public ObservableCollection<AppartmentType> AppartmentTypeList { get; set; }
public ObservableCollection<AppartmentType> ChosenAppartmentTypeList { get; set; }
And those are the properties:
public class AppartmentType : ValidatableBindableBase
{
private string name;
private int count;
public string Name
{
get { return name; }
set { SetProperty(ref name, value); }
}
public int Count
{
get { return count; }
set { SetProperty(ref count, value); }
}
}
First, you can observe your property ChosenAppartmentTypeList via PropertyChanged event and update the ApartmentType items.
Reference
Secondly, your SelectedItem on second combobox must be bound two-way.

InvokeCommandAction not calling

I have a combobox and I need a command in my view model to bind to its ContextMenuOpening event. I've tried referencing System.Windows.Interactivity and using InvokeCommandAction, but the command is not calling. Does anyone see where I'm going wrong?
<ComboBox x:Name="comboBoxAs" Grid.Column="0" VerticalAlignment="Top" Margin="928,62,0,0" Height="25"
ItemsSource="{Binding Source={StaticResource sas}}"
SelectedItem="{Binding Path=as, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource ComboBoxDefault}" HorizontalAlignment="Left" Width="212" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<i:InvokeCommandAction Command="{Binding ContextMenuOpeningCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
ViewModel:
public ICommand ContextMenuOpeningCommand
{
get
{
if (_contextMenuOpeningCommand == null)
{
_contextMenuOpeningCommand = new RelayCommand<object>(param => this.ContextMenuOpening(),
null);
}
return _contextMenuOpeningCommand;
}
}
public void ContextMenuOpening()
{
System.Windows.MessageBox.Show("test", "test");
}
private ICommand _contextMenuOpeningCommand;
Please try DropDownOpened to see whether the command gets hit. I tried it and it works here. Hope this helps :)

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>

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