Handling TreeViews context menu with WPF and MVVM - wpf

I have a application that shows a TreeView. The TreeView has a context menu. I use Caliburn.Micro as MVVM framework.
The line cal:Message.Attach="[Event Click]=[Action Remove()]" is required so that something happens if the ContextMenu-Item is clicked. This line makes that Caliburn.Micro searches in the ViewModel of the TreeItem for a method with the name Remove.
But I want that the click on the ContextMenu Item will call the method Remove of the ViewModel of the screen. How to do that?
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem cal:Message.Attach="[Event Click]=[Action Remove()]" Name="Remove" Header="Remove item" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>

Try this:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Tag="{Binding RelativeSource={RelativeSource AncestorType=TreeView}}"
cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag.DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
cal:Message.Attach="[Event Click]=[Action RemoveResource()]" Name="Remove" Header="Remove item" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Please refer to my answer here for more information:
Caliburn Micro Action inside ItemContainerStyle - No target found for method

Somebody had the similar problem “Bubbling” events from bound viewmodel goes to parent.
And according to author himself Bind a Command to a Button inside a ListView with Caliburn.Micro.
So just call it by convention or like this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="{Binding Name}" />
</i:EventTrigger>
</i:Interaction.Triggers>

Related

How can I track which element of ItemsControl caused ContexMenu to open?

In my book editor app I need to display themes and subthemes in an hierarchical order, so I made such an markup:
<StackPanel>
<ScrollViewer>
<Grid ColumnDefinitions="auto, *" RowDefinitions="auto">
<ItemsControl Grid.Column="0" Grid.Row="0" Items="{Binding Book.Themes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Name}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.UpdateSubthemesVisibilityCommand}" CommandParameter="{Binding Name}"/>
<ItemsControl Items="{Binding Subthemes}" IsVisible="{Binding AreSubthemesVisible}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" />
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenameThemeCommand}" CommandParameter="...How to bind it to button which cause menu to open"/>
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</Grid>
</ScrollViewer>
</StackPanel>
Maybe markup isn't quite clear, but the point is that I have external ItemsControl and internal one. External one's items have structure: button (theme name) + internal ItemsControl which presents subthemes' names as buttons too. Here is Screenshot:
Each ItemsControl has its own ContextMenu, but I can't bind them properly. I want "Rename" MenuItems to be bind to Button.Content of Button which right click caused menu to open. How should I do this here?
I am using MVVM Avalonia, but I hope in WPF it's working the same way so that I could add WPF hashtag to this question.
You don't need to add ContextMenu to ItemsControl. It needs to be added to the items ItemsControl.
This is done in ItemContainerStyle.
In such a case, in ContextMenu DataContext will contain the element on which the ContextMenu is called.
Example:
<Window.Resources>
<spec:StringCollection x:Key="strings">
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
<sys:String>Third</sys:String>
</spec:StringCollection>
<Style x:Key="ItemStyle" TargetType="ContentPresenter">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<TextBlock Text="{Binding}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource strings}"
ItemContainerStyle="{StaticResource ItemStyle}"/>

pass contextmenu parent as CommandParameter

i have a HierarchicalDataTemplate for a TreeViewItem, in the template i have a contextmenu and i want to pass as CommandParameter the ContextMenu parent - i.e the TreeViewItem owner that was right clicked at the moment, is there a way to do that?
here is my template:
<HierarchicalDataTemplate
x:Key="ServerTemplate"
DataType="{x:Type models:Server}"
ItemsSource="{Binding Channels}"
ItemTemplate="{StaticResource ChannelTemplate}">
<StackPanel
Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"
Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu
FontSize="14"
FontFamily="Arial">
<MenuItem
Header="{x:Static p:Resources.ServerOperations_CommunicationSettings}"
Command="{Binding PlacementTarget.Tag.ServerCommunicationSettingCommand, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding Path=Parent, RelativeSource={RelativeSource Mode=Self}}">
</MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<Image
Source="{Binding ImageURL, Converter={StaticResource StringToImageConverter}}"
Margin="0,0,2,0"
Height="25"
Width="25"/>
<TextBlock
Text="{Binding ServerName}"
Foreground="White"/>
</StackPanel>
</HierarchicalDataTemplate>
thanks for the help
You can get the TreeViewItem by getting PlacementTarget of ContextMenu which will be StackPanel and its TemplatedParent will be ContentPresenter and its TemplatedParent will be TreeViewItem. So this will work:
CommandParameter="{Binding Path=PlacementTarget.TemplatedParent.TemplatedParent,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ContextMenu}}}"
PlacementTarget (StackPanel) --> TemplatedParent (ContentPresenter) --> TemplatedParent (TreeViewItem)
Ideally it's not a good idea to pass UI components to ViewModel. You should pass data i.e. DataContext of TreeViewItem as you can always play with that.
In case you want to pass Server instance i.e. DataContext of TreeviewItem, you can simply do "{Binding}" since MenuItem will inherit it from StackPanel.
CommandParameter="{Binding}"

EventToCommand in ItemControl in Windows Phone 8

I cannot understand why I cant call eventToCommand in my datatemplate inside ItemControl. According to this post I should implement it in the dataTemplate, but the command is never invoked. This is the eventToCommand im trying to insert in my code.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand Command="{Binding ItemSelectedCommand, Mode=OneWay}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
And this is the code im trying to instert it to. However, as the comments say, it is never invoked when put in the dataTemplete. The problem is not the viewModel, the command works in the panelTemplate.
<ItemsControl ItemsSource="{Binding GroupRow1}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<!-- COMMAND WORKS HERE, but cannot locate which item has been pressed -->
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="#FF1756C3" Height="173" Width="173" Margin="12,0,0,0" >
<!-- COMMAND DOES NOT WORK HERE?!?! -->
<StackPanel>
<TextBlock Text="{Binding LineOne}" /> <!-- These bindings work -->
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How do I find out which Item has been pressed?
Many answers, but none of them worked for me. I redesigned my solution and got rid of the eventtocommand which wasent working. Instead I made buttons with custom content to look the same as my border.
Simpler code and a better solution.
<ItemsControl ItemsSource="{Binding GroupRow1}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button CommandParameter="{Binding LineOne}" Height="195" Width="195" Margin="0,0,-10,0" Click="Button_Click_2" Background="#FF1756C3">
<StackPanel>
<TextBlock Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I had a similar problem before, what solved it for me was referencing the VM in the binding. Try to set PassEventArgsToCommand to false if you want to receive the item instead of the EventArgs
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand PassEventArgsToCommand="False"
CommandParameter="{Binding}"
Command="{Binding Path=VM_Name_Here.Command_Name_Here, Source={StaticResource Locator}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Edit- If you are using MVVM Light, in the app.xml you should have something like:
xmlns:vm="clr-namespace:PhoneApplication.ViewModel (namespace where your ViewModelLocator is under)
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="true" />
You are telling the binding to look in the ViewModelLocator for a particular VM.
Hope it helps,
Regards.
I think it has to do with your binding in the data template. How is the view created? I think the command is a property of the viewModel, not GroupRow1 which is the collection for your items control. You need to bind to the command in your ViewModel. SO if your view is a usercontrol, the following should work. (if its of another type then change the ancestortype)
<cmd:EventToCommand Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.ItemSelectedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBlock}},Path=Name}"/>
The command parameter adds the name of the textblock for example, you could change that to any property of the textblock.
I would personally have SelectedItem property in my viewmodel that can be accesses as an object from the ItemSelectCommand
Hope this helps.
The answer is pretty obvious. In ItemsPanelTemplate your binding source is still the ViewModel and your command stays at your ViewModel. But in DataTemplate you are iterating over GroupRow1 items and your binding source is individual item. If you want to use your command there, you have to bind from the relative source in example:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand Command="{Binding ViewModel.ItemSelectedCommand, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=OneWay}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>

Treeview ContextMenu with no Click Event Handler in WPF

I have a TreeView, and I want to be able to add children to it and to the Linq to SQL database that it's bound to.
The best way that I can think of (off the top of my head) would be to have the user right click on a parent node and have the option to add new item from a context menu.
I added a context menu, but when I try to program it in the back end, it says that there is no event handler associated with it.
<TreeView Name="TreeView1" Margin="3" ItemsSource="{Binding ElementName=ManufacturerWarranty, Path=ManufacturerQuery, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=WarrantyList}">
<TextBlock Name="txtManufacturerName" Text="{Binding Path=ManufacturerName}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Name="mnuAddRecord" Header="Add Year Record"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Years}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Name="mnuDelRecord" Header="Remove Year Record"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Whats the correct way to do this?
You forgot to add handler which will execute on click of menuItem
<ContextMenu>
<MenuItem Name="mnuAddRecord" Header="Add Year Record" Click="HandlerInClass"/>
</ContextMenu>

WPF contextmenu binding

Cant seem to bind a menuitem in my contextmenu to a command in my viewmodel. I know the contextmenu does not live in the visual tree. For testing purposes i have bound to the same command twice in a button. The first binding works but i can get the second binding in the contextmenu to bind. I can see the binding error in output. Someone has any idea?
<HierarchicalDataTemplate DataType="{x:Type inf:OSiteEquipment}" ItemsSource="{Binding Path=SubSystems, Converter={StaticResource subsystemConverter}}" >
<Button HorizontalContentAlignment="Left"
Command="{Binding DataContext.CommandOpenSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=PartData.Name}" TextTrimming="CharacterEllipsis" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Category" Command="{Binding Path=Parent.PlacementTarget.Tag.CommandOpenSelected, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</HierarchicalDataTemplate>
Just remove the "Parent" in the Databinding path :
<HierarchicalDataTemplate DataType="{x:Type inf:OSiteEquipment}" ItemsSource="{Binding Path=SubSystems, Converter={StaticResource subsystemConverter}}" >
<Button HorizontalContentAlignment="Left"
Command="{Binding DataContext.CommandOpenSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=PartData.Name}" TextTrimming="CharacterEllipsis" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Category" Command="{Binding Path=PlacementTarget.Command, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</HierarchicalDataTemplate>
You already find the Parent via the relative source -> Omit the Parent in the path
You did not even set the Tag on the PlacementTarget (Button) -> Set it respectively

Resources