How to get bind to base viewmodel from contextmenu inside treeview? - c#

I'm having trouble getting the selection in a contextMenu to bind to my command EditCommand. The buttons in my tree-view bind to it fine, but in the menu it fails. I have read this is most likely due to the contextMenu being in a different UI tree, but solutions using findAncestor and tags have not worked for me. Is there anyway to do bind and still be able to pass the treeViewItem to the method?
My XAML:
<TreeView Background="Transparent"
Margin="10"
Grid.Column="0" Grid.Row="1"
ItemsSource="{Binding Path=TreeViewItems}">
<TreeView.ItemTemplate >
<HierarchicalDataTemplate DataType="{x:Type model:TreeViewSelection}" ItemsSource="{Binding Configs}" >
<DockPanel HorizontalAlignment="Stretch" Background="Transparent"><!--Transparency allows context click on whole row-->
<DockPanel.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem
Header="Edit"
Command="{Binding ElementName=userControl, Path=DataContext.EditCommand}"<!--Doesn't work-->
CommandParameter="{Binding}">
<MenuItem.Icon>
<Image Source="../Images/edit.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DockPanel.ContextMenu>
<TextBlock DockPanel.Dock="Left" Text="{Binding Title}" />
<StackPanel DockPanel.Dock="Right"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Height="23" Width="23"
Command="{Binding ElementName=userControl, Path=DataContext.EditCommand}"<!--Works-->
CommandParameter="{Binding}"
Style="{DynamicResource ImageNoTextButton}"
inf:AttachedProperties.Image="../Images/edit.png"
inf:AttachedProperties.ImageMouseOver="../Images/editMouseOver.png" />
</StackPanel>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Try this:
<DockPanel HorizontalAlignment="Stretch" Background="Transparent"
Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem
Header="Edit"
Command="{Binding Path=PlacementTarget.Tag.DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding}">
<MenuItem.Icon>
<Image Source="../Images/edit.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DockPanel.ContextMenu>
...
</DockPanel>

Related

Adding Static Button to MenuItem when binded to ItemSouce

i have an observable collection as ItemSource for my menuitems.
i want a simple button (see commented part) at end of menu item list that can add new item to this collection.
it seem simple But it throw a System.Windows.Markup.XamlParseException in my code telling the collection is already in use.
what is the correct way to achieve this?
<Menu Background="Transparent">
<MenuItem Header="WorkSpace" Background="Transparent" ItemsSource="{Binding NosWorkSpaces}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Original.Title}"></Label>
<Button Content="Select" Tag="WorkSpace_Load" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=mah:MetroWindow}, Path=DataContext.SelectWorkspaceCommand}" CommandParameter="{Binding }" />
<Button Content="Load" Tag="WorkSpace_Load" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=mah:MetroWindow}, Path=DataContext.LoadBinaryWorkspace}" CommandParameter="{Binding }" />
<Button Content="Save" Tag="WorkSpace_Save" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=mah:MetroWindow}, Path=DataContext.SaveWorkspaceCommand}" CommandParameter="{Binding }" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- <Button Content="New " /> this wont work -->
</MenuItem>
</Menu>
Well i found a solution mixing some answer, even i find it not so easy for a simple menu.
<Menu Height="24" VerticalAlignment="Top">
<Menu.Resources>
<CollectionViewSource Source="{Binding NosWorkSpaces}" x:Key="YourMenuItems"/>
</Menu.Resources>
<MenuItem Header="WorkSpaces" >
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource YourMenuItems}}" />
<Separator></Separator>
<MenuItem Header="Add Worspace" />
</CompositeCollection>
</MenuItem.ItemsSource>
<MenuItem.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Original.Title}"></Label>
<Button Content="Select" Tag="WorkSpace_Load" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=mah:MetroWindow}, Path=DataContext.SelectWorkspaceCommand}" CommandParameter="{Binding }" />
<Button Content="Load" Tag="WorkSpace_Load" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=mah:MetroWindow}, Path=DataContext.LoadBinaryWorkspace}" CommandParameter="{Binding }" />
<Button Content="Save" Tag="WorkSpace_Save" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=mah:MetroWindow}, Path=DataContext.SaveWorkspaceCommand}" CommandParameter="{Binding }" />
</StackPanel>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
giving a mixed menu with itemSource and datatemplate.

Open context menu by click on the whole line in listbox

In my code I have:
<ListBox Name="Playlists_ListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2" >
<Grid.ContextMenu>
<ContextMenu Name="cm" StaysOpen="true" >
<MenuItem Header="Delete"/>
</ContextMenu>
</Grid.ContextMenu>
<TextBlock Name="Name" Text="{Binding Title}" Foreground="White"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When I want now open context menu, I must click only on TextBlock. How can I do it to open context menu by click on any part of listbox item - one line in listbox?
Add Context menu for your ListBox so that you'll get context menu wherever you click on Listbox
<ListBox Name="Playlists_ListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2" >
<TextBlock Name="Name" Text="{Binding Title}" Foreground="White"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ContextMenu>
<ContextMenu StaysOpen="True">
<MenuItem Header="Delete"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>

WPF Command Binding of ContextMenu Item inside ItemsControl

My Application consists of a MainWindow with a ContentControl and I change the ViewModel depending on the selected menu.
One of the UserControls I display as content contains the following WrapPanel:
<UserControl ...>
<Grid>
<WrapPanel>
<ItemsControl ItemsSource="{Binding Connections}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.ConnectionSelectCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
FocusManager.FocusedElement="{Binding ElementName=InstanceName}"
Style="{DynamicResource DashboardButton}">
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Name}" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding ConnectionRemoveCommand}"
CommandParameter="{Binding}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</WrapPanel>
</Grid>
</UserControl>
The Command on the ContextMenu doesn't work because it tries to call ConnectionRemoveCommand on the Connection object instead of the ConnectionViewModel which is the DataContext of the UserControl.
How do I bind the Command to the ConnectionViewModel with the CommandParameter being the Connection object?
If you bind the Tag property of the Button to the DataContext of the ItemsControl, you could then bind to it using the PlacementTarget of the ContextMenu:
<Button Command="{Binding DataContext.ConnectionSelectCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
FocusManager.FocusedElement="{Binding ElementName=InstanceName}"
Style="{DynamicResource DashboardButton}"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ItemsControl}}">
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Name}" />
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding PlacementTarget.Tag.ConnectionRemoveCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Button.ContextMenu>
</Button>

wpf mvvm treeview contexmenu

I'm learning WPF/MVVM and got stuck on following...
The code below is working
<ListBox x:Name="listbox" DockPanel.Dock="Top" ItemsSource="{Binding Items}" DisplayMemberPath="Name" >
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show Selected"
Command="{Binding Path=DataContext.ShowSelectedCommand}"
CommandParameter="{Binding Path=SelectedItems}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
but when I've replaced listbox on treeview, such as
<TreeView x:Name="tview" DockPanel.Dock="Top" DisplayMemberPath="Name" Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}" ItemsSource="{Binding Items}" >
<TreeView.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" StaysOpen="true">
<MenuItem Header="Show Selected" Command="{Binding DataContext.ShowSelectedCommand}"
CommandParameter="{Binding Path=SelectedItems}">
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
The ShowSelectedCommand doesn't invoked. Could you please explain what's wrong there and how can I make it work. Thanks a lot.

Can't bind a ContextMenu action to a Command

I've searched and read anything I can about ContextMenus and binding, and how it's not in the tree... etc. So searching feels like I've exhausted it and just don't understand it.
I'm trying to get my ContextMenu AddTournamentCommand to work, but I simply can't get it to command. I recently found out the easy way through Data Sources to bind to objects, so if there's an easy way other than coding it by hand to wire it up, please let me know. This is what I have so far:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Models="clr-namespace:FumbblApiClient.Models" mc:Ignorable="d" x:Name="FumbblMainWindow" x:Class="FumbblApiClient.MainWindow"
Title="MainWindow" Height="499.45" Width="639" Loaded="Window_Loaded">
<Window.Resources>
<CollectionViewSource x:Key="groupViewSource" d:DesignSource="{d:DesignInstance {x:Type Models:Group}, CreateList=True}"/>
<CollectionViewSource x:Key="groupTournamentsViewSource" Source="{Binding Tournaments, Source={StaticResource groupViewSource}}"/>
</Window.Resources>
<Grid Margin="0,0,2,0">
<TabControl Margin="10">
<TabItem Header="Groups">
<Grid Background="#FFE5E5E5" DataContext="{StaticResource groupViewSource}">
<TextBox x:Name="GroupIdTextBox" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="Group ID" VerticalAlignment="Top" Width="100" Grid.Column="1"/>
<Button Content="Fetch" HorizontalAlignment="Left" Margin="115,11,0,0" VerticalAlignment="Top" Width="61" Click="GroupFetch_Click" Grid.Column="1" Height="22"/>
<ListBox x:Name="groupListView" ItemsSource="{Binding}" Margin="10,38,0,10" SelectionMode="Single" HorizontalAlignment="Left" Width="166" SelectionChanged="GroupList_SelectionChanged">
</ListBox>
<Grid x:Name="grid1" Margin="181,38,10,0" VerticalAlignment="Top" Height="369">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="Id:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="0" VerticalAlignment="Center"/>
<TextBox x:Name="idTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="23" Margin="3" Grid.Row="0" Text="{Binding Id, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" VerticalAlignment="Center" Width="120"/>
<Label Content="Name:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="1" VerticalAlignment="Center"/>
<TextBox x:Name="nameTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="23" Margin="3" Grid.Row="1" Text="{Binding Name, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" VerticalAlignment="Center" Width="120"/>
<Label Content="Tournaments:" HorizontalAlignment="Left" Margin="3" Grid.Row="2" VerticalAlignment="Center"/>
<ListBox x:Name="tournamentsListView" ItemsSource="{Binding Source={StaticResource groupTournamentsViewSource}}" Margin="3,3,-182,-260" SelectionMode="Multiple" Grid.Row="2" Grid.Column="1">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="UIElement.PreviewMouseRightButtonDown" Handler="EmptyHandler"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Add To Selected Tournaments" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=PlacementTarget.DataContext.AddTournamentCommand}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</Grid>
</Grid>
</TabItem>
<TabItem Header="Tournaments">
<Grid Background="#FFE5E5E5" Margin="0,0,0,-2">
<ListBox HorizontalAlignment="Left" Margin="10,10,0,10" Width="166"/>
</Grid>
</TabItem>
<TabItem Header="Teams">
</TabItem>
<Grid Margin="0,0,-10,10"/>
</TabControl>
</Grid>
</Window>
and in the Code Behind:
public partial class MainWindow : Window
{
[removed]
private ICommand addTournamentCommand;
public ICommand AddTournamentCommand
{
get
{
if(addTournamentCommand == null)
{
addTournamentCommand = new RelayCommand(OnTournamentAdded);
}
return addTournamentCommand;
}
}
private void OnTournamentAdded(object state)
{
}
}
PlacementTarget property is on Context Menu and not on window. Travel to ContextMenu and not to Window. Window anyhow doesn't lies in Visual Tree of ContextMenu so you can't reach to it using RelativeSource.
<ContextMenu>
<MenuItem Header="Add To Selected Tournaments"
Command="{Binding RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}},
Path=PlacementTarget.DataContext.AddTournamentCommand}"/>
</ContextMenu>
With above code you will get PlacementTarget's dataContext which will be ListBox's DataContext and if you haven't set explicitly DataContext on ListBox, it will inherit it from Window and your code will work fine.
UPDATE
You can store the Window DataContext in Tag of ListBox and bind with it.
<ListBox Tag="{Binding DataContext,
RelativeSource={RealtiveSource Mode=FindAncestor,
AncestorType=Window}}"/>
and in ContextMenu bind using Tag:
<ContextMenu>
<MenuItem Header="Add To Selected Tournaments"
Command="{Binding RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}},
Path=PlacementTarget.Tag.AddTournamentCommand}"/>
</ContextMenu>
Change like this,
<ContextMenu>
<MenuItem Header="Add To Selected Tournaments" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=AddTournamentCommand}"/>
</ContextMenu>

Categories

Resources