TreeViewItem.Expanded - wpf

Im using the latest version of mvvm light toolkit, however i'm not clear how I can use EventToCommand for the event TreeViewItem.Expanded.
THis dosent work... what am I doing wrong?
<TreeView Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Path= MonitoredDatabases}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Queues}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ServerName}" />
<TextBlock Text="\" />
<TextBlock Text="{Binding DatabaseName}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding QueueName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TreeViewItem.Expanded">
<cmd:EventToCommand Command="{Binding Path=NodeExpanded}"
CommandParameter="Expanded" />
</i:EventTrigger>
<i:EventTrigger EventName="TreeViewItem.Collapsed">
<cmd:EventToCommand Command="{Binding Path=NodeCollapsed}"
CommandParameter="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
help much appreciated.
Regards.
Gary

I was able to do this by creating a custom ItemContainerStyle for the TreeView. You should be able to put this together with the following code snippets.
1. A ViewModel for the TreeView
public class TreeViewModelView
{
public IEnumerable<object> Values
{
get { /* return hierarchical data source here ... */ }
}
/// <summary>
/// Command executed when the TreeViewItem expanding event is raised. The data item is passed in as a parameter.
/// </summary>
public RelayCommand<object> ExpandedCommand
{
get { return new RelayCommand<object>( o => MessageBox.Show( o.GetType().Name ) ); }
}
/// <summary>
/// Command executed when the TreeViewItem collapsing event is raised.
/// </summary>
public RelayCommand CollapsedCommand
{
get { return new RelayCommand( () => MessageBox.Show( "Collapsed" ) ); }
}
}
2. Define the TreeView and setup the required data binding:
<TreeView x:Name="lstItems" HorizontalAlignment="Left" Margin="21,19,0,80" Width="283"
DataContext="{DynamicResource TreeViewModelView}"
ItemsSource="{Binding Values, Mode=OneWay}"
ItemTemplate="{DynamicResource TreeViewDataTemplate}"
ItemContainerStyle="{DynamicResource TreeViewItemStyle}">
<TreeView.Resources>
<mv:TreeViewModelView x:Key="TreeViewModelView" />
<HierarchicalDataTemplate x:Key="TreeViewDataTemplate" ItemsSource="{Binding Items}">
<Grid>
<TextBlock Text="{Binding Name}" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
3. In Expression Blend you can create edit a copy of the ItemContainerStyle template
<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
...
</Style>
4. Then replace the ToggleButton with:
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}">
<i:Interaction.Triggers>
<!-- When the Checked event is raised execute the ExpandedCommand with the data item as a parameter. -->
<i:EventTrigger EventName="Checked">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Path=DataContext.ExpandedCommand, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, Mode=OneWay}"
CommandParameter="{Binding}" />
</i:EventTrigger>
<!-- When the Unchecked event is raised execute the CollapsedCommand. -->
<i:EventTrigger EventName="Unchecked">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Path=DataContext.CollapsedCommand, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ToggleButton>

The event to command behavior is implemented as an attached property, which must be attached to a FrameworkElement. Your sample has the Triggers attached property attached to the TreeView which doesn't have an Expanded or Collapsed event. You attempted to use "TreeViewItem.Expanded" as the event name, but it doesn't work that way.
If you were creating your TreeViewItems statically in XAML, or manually in code-behind, you could attach to each TreeViewItem. Unfortunately, I'm not aware of any way to attach to the TreeViewItem from the HierarchicalDataTemplate. You can bind to the TemplatedParent RelativeSource, but you can't attach to it. Your only solution is to iterate through the TreeViewItems in code-behind, and manually handle the events, but even then you'd have to do so using the VisualTreeHelper only after the TreeView control has been data bound and rendered, which is a huge hack.

Related

WPF (mvvm, caliburn.micro) button click inside a listview item

I have a simple listview and listviewitem structure.
The Heart of the ListView.xaml is like the following
<StackPanel>
<ListBox x:Name="Movies" SelectedItem="{Binding SelectedMovie}">
<ListBox.ItemTemplate>
<DataTemplate>
<v:ListItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
and the heart of the ListTtemView.xaml is the following:
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding Title}"/>
<Button cal:Message.Attach="RunOperation" cal:Action.TargetWithoutContext="{Binding ElementName=UserControl, Path=DataContext}" Content="Run"/>
</StackPanel>
in the ListViewItemViewModel, I have a method called RunOperation and the scenario is when the user clicks the button in a listviewitem, the method RunOperation should be called. However, I get an exception: 'No target found for method RunOperation.'
I have read that the caliburn micro doesn't work in a case like this and if this is the case, I realised that I still don't know how to make it work the simple WPF way.
Apparently, RunOperation method cannot be found so I tried few combinations of cal:Action.TargetWithoutContext="{Binding ...}" but no help.
Thanks
If someone is still looking for a solution, here is what I did, With reference to caliburn.micro and interactivity in the xaml like below,
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro.Platform"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<Button Content="Run">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="RunOperation">
<cal:Parameter Value="{Binding}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
The view model button click method will be
public void RunOperation(object obj)
{
//Do your things
}
Since your xaml involving button is not available in question, am not sure if you have already tried this, but following should help you hit the function on Button's click event.
<Button cal:Message.Attach="[Event Click] = [Action RunOperation]"/>
I don't know anything about caliburn.micro.
But the normal WPF approach would be this
Step 1:
Create a Command in your ViewModel
class ViewModel
{
...
public ICommand RunOperationCommand { get; }
...
}
Step 2: Bind to it
<Button Command="{Binding RunOperationCommand }" />
That's not the caliburn.micro solution. And I don't know if there is a valid caliburn.micro solution for situations like this.
But the following works:
<Button Command="{Binding DataContext.RunOperationCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" />
I was having the same issue, plus I was trying to get around it using a normal WPF approach.
I had Buttons being created dynamically inside a List Control which a source of was Bindable Collection of Sensors (my class).
You need to make sure you add a reference to the "xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" and
"xmlns:cal="http://www.caliburnproject.org" on your UserControl or Window.
Can find all the information and examples on Caliburn Micro Docs:
CaliburnActions Docs
<UserControl x:Class="BeckerGasApp.Views.MapView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BeckerGasApp.Views"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cal="http://www.caliburnproject.org">
<Grid>
<Grid>
<ItemsControl x:Name="Sensors" Background="Transparent" ItemsSource="{Binding Path=Sensors, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Background="Transparent" Content="{Binding SensorName}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Sensor">
<cal:Parameter Value="{Binding SensorName}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
</UserControl>
To do it inside a listview I had the following extra tag cal:Action.Target to be able to access ÀctionMessage for an item in listview.
<ListView
x:Name="LV"
Background="Transparent"
BorderBrush="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<Button x:Name="StartPage">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:Action.Target>
<cal:ActionMessage MethodName="StartPage">
<cal:Parameter Value="{Binding}" />
</cal:ActionMessage>
</cal:Action.Target>
</i:EventTrigger>
</i:Interaction.Triggers>
<Button.Template>
<ControlTemplate>
<StackPanel Width="210" Orientation="Horizontal">
<Image Source="../Assets/img_home.png" Stretch="None" />
<TextBlock Style="{StaticResource font_style}" Text="Home" />
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
</ListViewItem>

Why is the click event of a button inside a DataGrid is not fired?

I am using MVVM pattern and MVVM Light to convert an event into a command, in my XAML I have this code:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="+" Padding="0,0,0,0" Height="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
In my view model I have this code:
private RelayCommand<SelectionChangedEventArgs> _myCommand;
public RelayCommand<SelectionChangedEventArgs> MyCommandCommand
{
get { return _myCommand ?? (_myCommand = new RelayCommand<SelectionChangedEventArgs>(myCommandCommand)); }
}
private void myCommand(SelectionChangedEventArgs e)
{
// code
}
But the click event is not fired. However, I am using this way for every button in my application and when the button is into a user control or window, the event is fired.
Thanks.
Change your binding, the DataContext that it's trying to look for is the DataContext for the Template which might be different depending on your structure.
Change it to this
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.MyCommand}" /
Besides the above answer, I found that it was also necessary to specify ClickMode="Press" in Xaml.
<Button Content="î…¯" Focusable="True" FontFamily="Segoe UI Symbol" FontSize="16" Background="{StaticResource HeroLightGray}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"
ClickMode="Press"
Command="{Binding DataContext.CopyMetadataSourceAsync, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" CommandParameter="{Binding .}" />
I do not recall having to do this in the past, but after spending hours troubleshooting, converting RelayCommand to IAsyncCommand, etc. this is the only thing that worked. In fact, I couldn't even get a regular code-behind "Click event" method to fire unless I included that ClickMode="Press"!

Binding only works using ElementName, why?

Here is some code snippets of my scenario. On the .xaml side I have an ItemsControl:
<ItemsControl ItemsSource="{Binding StatList}" ItemTemplate="{StaticResource statBox}"></ItemsControl>
The StatList is simply a List of objects.
The DataTemplate contains this TextBlock
<TextBlock x:Name="DataTextBlock" Background="Transparent" DockPanel.Dock="Top" HorizontalAlignment="Center"
Foreground="White" FontSize="11">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<iBehaviors:InvokeMethodTrigger Method="UpdateBinding"
Target="{Binding DataContext, RelativeSource={RelativeSource TemplatedParent}}"
Parameter="{Binding ., RelativeSource={RelativeSource AncestorType=TextBlock}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
This works the first time I set my StatList on my ViewModel, the TextBlock itself is passed as the Parameter to the InvokeMethodTrigger. However, if I change the StatList on my viewmodel, the next time the Loaded event occurs, the Parameter is null when the InvokeMethodTrigger is triggered.
Oddly enough, when I change the Parameter binding to:
Parameter="{Binding ElementName=DataTextBlock}"
it works the second time when I change my StatList, with the TextBlock being passed as the parameter. I for my life can't figure out why! Any explanations?
You can do it like this in code behind:
SlatList slat = new SlatList();
ItemControl.ItemSource = slat;
ItemControl.DataContext = slat;
make sure to specify a name for the itemcontrol and replace "ItemControl" with the name.
In XAML:
<ItemsControl ItemsSource="{Binding StatList}" DataContext="{Binding SlatList} ItemTemplate="{StaticResource statBox}"> </ItemsControl>

TreeView ContextMenu MVVM Binding

I currently have a UserControl that uses the MVVM model.
In that control there is a TreeView, which displays some items. I have added a HierarchicalDataTemplate for this TreeView and in that template is a ContextMenu for the Items.
In the ViewModel, which is DataContext of the control (named RestoresTreeViewControl) is a command I want to bind one of the menu items to. However what I have done doesn't seem to be working. I am getting the usual can't find source for binding reference.
Here is the bit of code for the datatemplate that tried to bind the EditDatabaseCommand to one of the menu items.
<HierarchicalDataTemplate DataType="{x:Type model:Database}" >
<StackPanel>
<TextBlock Text="{Binding Name}" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" Command="{Binding ElementName=RestoresTreeViewControl, Path=DataContext.EditDatabaseCommand}" />
<MenuItem Header="Delete"/>
<Separator/>
<MenuItem Header="Test Connection"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
Here is a section of the ViewModel where the command is.
public ICommand EditDatabaseCommand { get; private set; }
Unfortunately the ContextMenu is not in the VisualTree, so it's not going to see your DataContext. What you can do is something like this (copied from here: MVVM binding command to contextmenu item)
<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}"
Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}}}" Command = "{Binding
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}},
Path=DataContext.ConnectCommand}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
CommandParameter="{Binding Name}"
Command="{Binding Path=PlacementTarget.Tag.DataContext.RemoveCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
So simply use PlacementTarget.Tag to find your ViewModel.
You can try tracing the binding:
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
...
{binding ... diag:PresentationTraceSources.TraceLevel="High"}
However requiring the users (even if it is just yourself) of your control to name each instance of "RestoresTreeViewControl" rather burdensome.
Try:
{Binding Path=... RelativeSource={ FindAncestor, AncestorType={x:TheRestoresTreeViewControlType}} }
That probably has to do with the inheritance context.
See: Binding WPF ContextMenu MenuItem to UserControl Property vs ViewModel Property

ContextMenu.PlacementTarget is not getting set, no idea why

<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource ContentTemplateB}" Grid.Row="0" />
<ContentControl Name="uiContentPresenter" Content="{Binding ContentView}" Grid.Row="1" Height="0" />
<ContentControl DataContext="{Binding IsContentDisplayed}" DataContextChanged="IsDisplayed_Changed" Visibility="Collapsed" />
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Text"
Command="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
The above data template is applied to an ItemsControl. The issue is that for the ContextMenu that is specified for the Grid, the PlacementTarget property is never actually getting set to anything so I cannot get to the Tag property of the Grid which is necessary for passing the Command that should execute on the parent UserControl down to the context menu. I've based this approach off of similar examples such as this: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0244fbb0-fd5f-4a03-bd7b-978d7cbe1be3/
I've not been able to identify any other good way to pass this command down. This is setup this way because we are using an MVVM approach so the command we have to execute lives in the View Model of the user control this template is applied in. I've tried explicitly setting the PlacementTarget in a few different ways but it still always shows up as not set.
I realise that this is old and answered, but it doesn't seem properly answered. I came across a similar post and left a full answer. You might like to take a look as you can get it working with just a few adjustments to your code.
First, name your view UserControl... I generally name all of mine This for simplicity. Then remembering that our view model is bound to the DataContext of the UserControl, we can bind to the view model using {Binding DataContext, ElementName=This}.
So now we can bind to the view model, we have to connect that with the ContextMenu.DataContext. I use the Tag property of the object with the ContextMenu (the PlacementTarget) as that connection, in this example, a Grid:
<DataTemplate x:Key="YourTemplate" DataType="{x:Type DataTypes:YourDataType}">
<Grid ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext,
ElementName=This}">
...
</Grid>
</DataTemplate>
We can then access the view model properties and commands in the ContextMenu by binding the ContextMenu.DataContext property to the PlacementTarget.Tag property (of the Grid in our example):
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.Tag, RelativeSource=
{RelativeSource Self}}">
<MenuItem Header="Delete" Command="{Binding DeleteFile}" CommandParameter=
"{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource
AncestorType=ContextMenu}}" CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
Note the binding on the MenuItem.CommandTarget property. Setting this ensures that the target element on which the specified command is raised is the PlacementTarget, or the Grid in this case.
Also note the CommandParameter binding. This binds to the DataContext of the PlacementTarget, or the Grid in this case. The DataContext of the Grid will be inherited from the DataTemplate and so your data item is now bound to the object parameter in your Command if you're using some implementation of the ICommand interface:
public bool CanExecuteDeleteFileCommand(object parameter)
{
return ((YourDataType)parameter).IsInvalid;
}
public void ExecuteDeleteFileCommand(object parameter)
{
Delete((YourDataType)parameter);
}
Or if you are using some kind of RelayCommand delegates directly in your view model:
public ICommand Remove
{
get
{
return new ActionCommand(execute => Delete((YourDataType)execute),
canExecute => return ((YourDataType)canExecute).IsInvalid);
}
}
We have the same problem, but it works randomly. A contextmenu inside the controltemplate in a style for a listbox. We have tried to move the contextmenu to different levels inside the template but the same error occurs.
We think it might be connected to the refreshing of our ICollectionView that is the itemssource of the ListBox.
It seems that when the view refreshes the relative source binding inside the contextmenu is being evaluated before the PlacementTarget is being set.
It feels like a bug in either collectionviewsource or the ContextMenu of WPF...
Here is a working standalone XAML-only example based on your test case: a ContextMenu that retrieves a Command from the DataContext of its PlacementTarget using a Tag. You can reintroduce portions of your code until it stops working to try to find where the problem is:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point X="10" Y="20"/>
<Point X="30" Y="40"/>
</PointCollection>
<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}}">
<TextBlock Text="{Binding X}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</Grid.Resources>
<DockPanel DataContext="{x:Static ApplicationCommands.Open}">
<ListBox ItemTemplate="{StaticResource _ItemTemplateA}" ItemsSource="{StaticResource sampleData}"/>
</DockPanel>
</Grid>
In this post,
ContextMenu.PlacementTarget is filled up from ContextMenuService.PlacementTarget when you do right click with mouse on button.
It means ContextMenu.PlacementTarget is filled up when the menu is shown up. You can check that by snoop.
EDIT 1
This code works fine.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowBaseStyle}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Style="{StaticResource ContextMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}">
<MenuItem Header="Enable" Command="{Binding Path=PlacementTarget.Tag.(viewModels:PrinterListPageViewModel.EnableCommand), RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>

Resources