Add event to item in DataTemplate - c#

In my xaml file with DataTemplates I want to add a DataContextChanged event to my ListBox that is in one of the templates, so i do this:
<DataTemplate x:Key="MyTemplate">
<ListBox Background="Transparent"
DataContext="{Binding Source={StaticResource Locator}}"
DataContextChanged="MyListBox_DataContextChanged"
SelectedItem="{Binding MyViewModel.SelSegment, Mode=TwoWay}"/>
</DataTemplate>
But in which file to implement "MyListBox_DataContextChanged"?

when working with Mvvm you don't handle events directly like you do when working with code behind, in your case the command that handles the DataContextChanged should be implemented in the corresponding ViewModel of the page where this DataTemplate is used,
and finally using a simple hack you can execute the associated Command when the DataContextChanged Event accured, your code should looks like so :
The Xaml :
<DataTemplate x:Key="MyTemplate">
<ListBox Background="Transparent"
DataContext="{Binding Source={StaticResource Locator}}"
SelectedItem="{Binding MyViewModel.SelSegment, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DataContextChanged">
<command:EventToCommand Command="{Binding Mode=OneWay,Path=MyViewModel.MyListBox_DataContextChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</DataTemplate>
and Add the following command to your ViewModel :
private RelayCommand __myListBox_DataContextChangedCommand;
public RelayCommand MyListBox_DataContextChangedCommand
{
get
{
return __myListBox_DataContextChangedCommand
?? (__myListBox_DataContextChangedCommand = new RelayCommand(
() =>
{
//Your Event's Handler Goes Here
}));
}
}
Edit :
You could read more about EventToCommand at
Commands, RelayCommands and EventToCommand

Related

WPF: SelectionChanged command gets fired on unload of UserControl

I am working on wizard kind of application, where I am maintaining the wizards in
a list named List<ViewmodelsForWizard>.
I have used same UserControl UCView to render two different pages by simply tweaking the ViewModel values stored in List<ViewmodelsForWizard>.
The problem is that SelectionChangedCommand for previous page gets fired on load of the next page. (Both pages uses same UserControl UCView and ViewModel)
MainWindow
<Grid DataContext="{Binding currentWizard}">
<Grid.Resources>
<DataTemplate DataType="{x:Type viewmodel}">
<local:UCView DataContext="{Binding}"/>
</DataTemplate>
</Grid.Resources>
<ContentControl Content="{Binding}" />
</Grid>
I have following ViewModel:
//all other properties and commands
private ICommand selectionChangedCommand;
public ICommand SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
selectionChangedCommand = new DelegateCommand(OnSelectionChanged());
return selectionChangedCommand;
}
set
{
selectionChangedCommand = value;
}
}
//all other properties and commands
Selectionchanged in UCView
<ComboBox ItemsSource="{Binding items}"
DisplayMemberPath="data"
SelectedValue="{Binding selected}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Any help would be great. Thanks.

Highlight custom DataTemplate in TreeView

I have a simple TreeView that I'm trying to create a custom DataTemplate for. It displays as desired, but when I attempt to select the TreeViewItem, the highlighting does not work if I click the text in the TreeView. However, if I select just to the left of the text, it works:
The source is pretty straight forward, so I'm guessing I'm just missing a styling connection:
xaml
<TreeView x:Name="treeView"
ItemsSource="{Binding TreeViewItems}"
Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<TreeViewItem Header="{Binding Header}">
<TreeViewItem.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding Command}" />
</TreeViewItem.InputBindings>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I've tried adding this to the xaml, but it didnt help:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}"
BasedOn="{StaticResource {x:Type TreeViewItem}}" />
</TreeView.ItemContainerStyle>
TreeViewModel
public class TreeViewModel : BaseNotifyModel, ITreeViewModel
{
public TreeViewModel(ITreeViewService menuService)
{
TreeViewItems =
new ReadOnlyObservableCollection<MenuItemViewModel>(menuService.TreeViewMenu);
}
public ReadOnlyObservableCollection<MenuItemViewModel> TreeViewItems
{
get
{
return Get<ReadOnlyObservableCollection<MenuItemViewModel>>();
}
private set
{
Set(value);
}
}
}
MenuItemViewModel
public class MenuItemViewModel : BaseNotifyModel
{
public MenuItemViewModel()
{
MenuItems =
new ObservableCollection<MenuItemViewModel>();
}
public String Header
{
get
{
return Get<String>();
}
set
{
Set(value);
}
}
public ICommand Command
{
get
{
return Get<ICommand>();
}
set
{
Set(value);
}
}
public ObservableCollection<MenuItemViewModel> MenuItems
{
get
{
return Get<ObservableCollection<MenuItemViewModel>>();
}
set
{
Set(value);
}
}
}
The TreeView is creating a TreeViewItem for each item in ItemsSource, so don't nest another TreeViewItem inside the one that the TreeView already created for you. That doesn't serve any purpose. Your template should just be providing a way for the existing TreeViewItem to display whatever's in its DataContext (your MenuItemViewModel, in this case).
You want to display the Header property in the tree view item; so just do that. Nothing fancy, just a Label or ContentControl, or even a TextBlock if it's String (though it's a lot of fun in WPF to have the flexibility of arbitrary content). When the user clicks on the content, your command executes. The only thing the user can see in the tree is the content. That's the only visible part of the TreeViewItem item, so that's what the user is going to click on.
Problem number two: Once the input binding is getting LeftClick, that breaks selection in the TreeView. It appears to me that you can't get there from here by that method.
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentControl
Content="{Binding Header}"
Background="Transparent"
>
<ContentControl.InputBindings>
<!-- This invokes the command, but breaks selection -->
<MouseBinding MouseAction="LeftClick"
Command="{Binding Command}" />
</ContentControl.InputBindings>
</ContentControl>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Here's what you can do:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentControl
Content="{Binding Header}"
Background="Transparent"
>
</ContentControl>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<EventSetter Event="Selected" Handler="MenuTreeViewItem_Click" />
</Style>
</TreeView.ItemContainerStyle>
Codebehind
private void MenuTreeViewItem_Click(object sender, RoutedEventArgs e)
{
((MenuItemViewModel)((FrameworkElement)sender).DataContext).Command.Execute(null);
}
There is a way to bind an event to a command in pure XAML, but it requires some C# code (LOL). But I mean, it's "pure XAML" in the sense that it's a nice generalized reusable attached behavior, not an unsightly event handler in your codebehind. Instead, it does exactly what I did above, but it does it in code that you can more easily avert your eyes from, and that you can reuse in pure XAML.
With help from #Ed Plunket and #Evk, I found a solution that will work. I switched to a ContentPresenter and used the Interaction.Triggers to call the command on the MouseLeftButtonUp action.
<TreeView ItemsSource="{Binding TreeViewItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentPresenter Content="{Binding Header}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentPresenter>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Method in ContextMenu for (Hierarchical)DataTemplate calls TreeViewItem's method instead of the one in ViewModel

I have a TreeView with multiple HierarchicalDataTemplate & DataTemplate items and I'm using Caliburn Micro for mvvm. The ItemsSource for the treeview is pointing to a collection in the viewmodel called 'TreeData' and I tried adding a specific ContextMenu for each HierarchicalDataTemplate & DataTemplate.
In the ContextMenu I use the caliburn functionality "cal:Message.Attach" to call a function in the
I made a smaller example of the treeview to illustrate the problem.
In the ViewModel (the collection object):
public class MyViewModel
{
// TreeData object
public ObservableCollection<TestRoot> TreeData = new ObservableCollection<TestRoot>()
{
new TestRoot()
{
Name = "Root item"
}
};
// the function on the viewmodel that should be called
public void DoSomething(object item)
{
MessageBox.Show("MyViewModel - DoSomething called");
}
}
The collection object:
public class TestRoot
{
public string Name { get; set; }
// caliburn calls this instead of the one on the viewmodel
public void DoSomething(object item)
{
MessageBox.Show("TestRoot - DoSomething called");
}
}
MyView.xaml treeview with only one (Hierarchical)DataTemplate:
<TreeView Margin="5" ItemsSource="{Binding TreeData}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type vm:TestRoot}" >
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu>
<!-- caliburn (?) chooses the method on the collection object, not the viewmodel -->
<MenuItem Header="test dosomething" cal:Message.Attach="DoSomething($dataContext)"></MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
In another piece of code, I placed the ContextMenu in the TreeView.ContextMenu. There it worked as it 'should', pointing to the method on the viewmodel.
Looking around for a solution, I see things like "inheritance context". I think it might have something to do with it, but I'm not sure. How can I tell caliburn it must look in the viewmodel for my method, instead of the item in the TreeView I clicked on?
Or is there another possibility? For example: defining the different ContextMenus in the Resources and pointing them to the DataTemplates? But, wont that cause the exact same problem?
Please note that I'd like to keep the code-behind as minimal as possible. Thanks
update
For the completeness, here's the real development code. This should be right, no?
<TreeView ItemsSource="{Binding OrderTreeViewData.OrderTreeViewCategories}"
cal:Message.Attach="[Event SelectedItemChanged] = [Action OnSelectedItemChanged($this)]">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
<!-- set expanded -->
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<!-- dredge nodes -->
<HierarchicalDataTemplate DataType="{x:Type programs:DredgeRoot}"
ItemsSource="{Binding Dredgezones}">
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Add dredge zone" cal:Message.Attach="TreeViewAddDredgeZone($datacontext)"></MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<!-- omitted other templates -->
</TreeView.Resources>
</TreeView>
Unfortunately there is still one tricky part to deal with. Due to the specific Popup behavior it doesn't inherit DataContext. To access proper context you have to get the PlacementTarget:
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="test dosomething" cal:Message.Attach="DoSomething"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
I think you have the nearly same problem as me, maybe see this topic : Bind contextMenu to a different viewmodel from treeview
You can try to use a command :
Try to change you code to :
<ContextMenu x:Key="MyContextMenu">
<MenuItem Header="Add dredge zone" Command="{Binding PlacementTarget.Tag.DataContext.TreeViewAddDredgeZoneCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding}"></MenuItem>
</ContextMenu>
Then add to your hierarchicalDataTemplate a Tag and ContextMenu
<HierarchicalDataTemplate DataType="{x:Type programs:DredgeRoot}"
ItemsSource="{Binding Dredgezones}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}} ContextMenu="{StaticResource MyContextMenu}">
And in your viewmodel you can add your command with something like this :
public ICommand TreeViewAddDredgeZoneCommand
{
//your code here
}

Why is CommandParameter always null?

anybody an idea why CommandParameter is always null?
The class TransactionViewModel has the collection property of TransactionCommands to be displayed in the ItemsControl. The items are of type CommandViewModel.
TransactionBrowserViewModel has the command AddJobForSelectedTransactionCommand. The command to be passed as a parameter the CommandViewModel.
View Snipp:
<ItemsControl Grid.Row="4"
ItemsSource="{Binding TransactionCommands}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Codebehind of UserControl:
[Export]
public partial class TransactionBrowserView : UserControl, IView<TransactionBrowserViewModel>
{
[ImportingConstructor]
public TransactionBrowserView()
{
InitializeComponent();
}
[Import]
public TransactionBrowserViewModel ViewModel
{
get { return (TransactionBrowserViewModel)this.DataContext; }
set { this.DataContext = value; }
}
}
OK, sorry I have found the error.
It is located on the RadButton by Telerik. I have tested the scenario with a default button. Here it works without any problems.
You have set the ComandParameter to the path of the DataContext of the RadButton, but I don't see that you have set anything to that DataContext anywhere.
Look into the Output window for information regarding your Binding errors... it should say something like 'There is no DataContext property on object XXX'.
What are you trying to bind to the CommandParameter property?
Try this binding
<ItemsControl x:Name="transactionList" Grid.Row="4" ItemsSource="{Binding TransactionCommands}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding SelectedItem, ElementName=transactionList}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Give your ItemsControl (like transactionList) and set the binding of the CommandParameter to the SelectedItem of your transactionList.
or does this not do what you want.
<telerik:RadButton Content="{Binding DisplayName}"
CommandParameter="{Binding}"
Command="{Binding ViewModel.AddJobForSelectedTransactionCommand, ElementName=userControl}"/>

ComboBox in a ListBox does not fire SelectionChanged event

I have a wpf, mvvm app, using the catel (http://catel.codeplex.com) framework\toolkit, C# .Net 4.0. The app has a ListBox with a TextBlock and ComboBox. The ListBox and ComboBox are populated from 2 different ObservableCollection from the ViewModel. I need to save (to a db), when the user clicks a button, each row in the ListBox where the user has selected an item from the ComboBox. The SelectionChanged event does not fire for any of the ComboBoxes in the ListBox. The idea being that I add to a list (ArrayList or IList?), in the ViewModel, each time the user selects an item in a ComboBox and for what row the item has been selected.
Or am I going about this the wrong way by trying to use the ComboBoxe SelectionChanged event? I also tried iterating thru the ListBox.Items but this seems like a hak and I want to avoid ui element logic in the ViewModel if possible.
The xaml:
<Grid>
<StackPanel Orientation="Horizontal">
<Label Width="180">Field1</Label>
<ListBox Height="200"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding List1, Mode=OneWay}"
Name="listBox1"
SelectionMode="Single"
Width="300">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="290">
<TextBlock Width="90" Text="{Binding}"></TextBlock>
<ComboBox Width="180" ItemsSource="{Binding DataContext.List2, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" DisplayMemberPath="Field1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<catel:EventToCommand Command="{Binding SelectionChangedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" DisableAssociatedObjectOnCannotExecute="False" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
ViewModel code:
//in the ViewModel constructor
SelectionChangedCommand = new Command<SelectionChangedEventArgs>(OnSelectionChangedCommandExecute, OnSelectionChangedCommandCanExecute);
public Command<SelectionChangedEventArgs> SelectionChangedCommand { get; private set; }
private bool OnSelectionChangedCommandCanExecute()
{
return true;
}
private void OnSelectionChangedCommandExecute(SelectionChangedEventArgs e)
{
// add or update list....
}
In Command binding you have used binding which has relative source binding...
consider making these changes in binding
1) using list box as Ancestortype
2) While binding use Path=DataContext.SelectionChangedCommand otherwise it will take list box as datacontext.
<catel:EventToCommand Command="{Binding Path=DataContext.SelectionChangedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" DisableAssociatedObjectOnCannotExecute="False" PassEventArgsToCommand="True" />

Categories