I currently have a treeview that displays data using a hierarchicaldatatemplate and when the user clicks on the Remove button, it removes the selected node(s). This works as intended.
However, Instead of having the user click a button I'm trying to have the command be executed by right clicking a node and selecting the appropriate menu item.
This is proving to be much more difficult because it is being picked up by the ViewModel of a node (which doesn't know anything about the View) instead of the View's corresponding ViewModel.
Is there someway to hand the control over to the ViewModel of the View instead?
Here is the code for the Remove Button:
View:
<Button Content="Remove" Grid.Row="2" Height="23" VerticalAlignment="Top" Name="removeButton"
Width="75" Margin="5,20,5,0" Command="{Binding Path=RemoveCommand}" />
ViewModel:
public RelayCommand RemoveCommand
{
get
{
if (_removeCommand == null)
{
_removeCommand = new RelayCommand(
() => this.Remove()
);
}
return _removeCommand;
}
}
public void Remove()
{
_organLocationTree2.RemoveOrganLocations(ProjectOrganLocationView.GetExtendedTreeView().SelectedItems);
ProjectOrganLocationView.GetExtendedTreeView().SelectedItems.Clear();
base.RaisePropertyChanged("DestOrganTree");
}
And the XAML for the menu item:
<local:ExtendedTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubOrganLocations}">
<TextBlock Text="{Binding OrganName}" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header ="Add SubNode" Command="{Binding Path=MenuItem_Add}"></MenuItem>
<MenuItem Header ="Remove Node" Command="{Binding Path=RemoveCommand}"></MenuItem>
<MenuItem Header ="Edit Node" Command="{Binding Path=ProjMenuItem_Edit}"
CommandParameter="{Binding DestOrganTree, Path=Selected.OrganName}"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</local:ExtendedTreeView.ItemTemplate>
</local:ExtendedTreeView>
I tried to implement a Remove command in the ViewModel of the node, but since it doesn't know anything about the View it got very messy very quickly.
Well, I found my mistake, I was binding the context menu to the nodes of the tree instead of the tree itself. I moved the context menu outside of the declaration and it now works as intended.
Here's my updated xaml for anyone else that has this issue:
<local:ExtendedTreeView.ContextMenu>
<ContextMenu>
<MenuItem Header ="Add SubNode" Command="{Binding Path=MenuItem_Add}"></MenuItem>
<MenuItem Header ="Remove Node" Command="{Binding Path=RemoveCommand}"></MenuItem>
<MenuItem Header ="Edit Node" Command="{Binding Path=ProjMenuItem_Edit}"
CommandParameter="{Binding DestOrganTree, Path=Selected.OrganName}"></MenuItem>
</ContextMenu>
</local:ExtendedTreeView.ContextMenu>
<local:ExtendedTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubOrganLocations}">
<TextBlock Text="{Binding OrganName}" >
</TextBlock>
</HierarchicalDataTemplate>
</local:ExtendedTreeView.ItemTemplate>
Related
In a WPF .Net 4 application have a master detail situation where a datagrid has rows which can have the detail information as found in the RowDetailsTemplate which has an internal datagrid.
Within the RowDetailsTemplate is a grid to hold the sub details which has a context menu. The problem found is when binding the CommandTarget of one of the details' MenuItem, I am unable to target that details datagrid as generated by the template. The below binding ends up getting the Master datagrid and not the containing datagrid which is holding the details information/contextmenu.
<DataGrid x:Name="dgEditScript" ItemsSource="{Binding CurrentScript}">
<DataGrid.CommandBindings>
<CommandBinding Command="commands:ScriptingCommands.SetChecked"
Executed="CheckAllAfter" />
</DataGrid.CommandBindings>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding SubCommands}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Check All From Selected"
Command="commands:ScriptingCommands.SetChecked"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
<MenuItem.Icon>
<Image Source="Images/checkboxes.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The problem when using the above Realtive source binding, it gives me the dgEditScript grid (top level) and not the parent of the context menu, the unnamed holding DataGrid which the context menu was launched from.
How do I get the sub grid in the binding; to target the parent of the context menu?
If I'm understanding your question right, you have a collections of items, and you want the context menu to be attached to the selected item (when you're right clicking on it ...)
Here's some similar code I'm using:
<ListBox x:Name="name_here"
ItemsSource="{Binding source_collection_name}"
SelectedItem="{Binding property_name_on_VM, UpdateSourceTrigger=PropertyChanged}"
>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header ="Edit Item" Command="{Binding EditItem_Command}"
CommandParameter="{Binding property_name_on_VM}"
/>
<MenuItem Header ="Delete Item" Command="{Binding DeleteItem_Command}"
CommandParameter="{Binding property_name_on_VM}"
/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
This way, whenever you click on an item (or right click), it's selected, and then you just send that item as a command parameter, so you have the item you need.
Hope this helps.
I have a ListView which displays a list of string values. I want to add a context menu entry for each item in the list to remove the selected item. My XAML looks like this:
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The problem is that the CommandParameter value is always null. I've added an additional button to remove the selected item to check if my command works. The button has exactly the same binding and removing items via the button works. The button looks like this:
<Button Content="Remove selected item"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}"/>
The command looks like this:
private ICommand _removeItem;
public ICommand RemoveItem
{
get { return _removeItem ?? (_removeItem = new RelayCommand(p => RemoveItemCommand((string)p))); }
}
private void RemoveItemCommand(string item)
{
if(!string.IsNullOrEmpty(item))
MyItems.Remove(item);
}
Any ideas why the selected item is null when opening the context menu? Maybe a focus problem of the listview?
H.B. is right. but you can also use RelativeSource Binding
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
ContextMenus are disconnected, you cannot use ElementName bindings. One workaround would be using Binding.Source and x:Reference which requires you to extract parts that use it to be in the resources (due to cyclical dependency errors). You can just put the whole context menu there.
An example:
<ListBox Name="lb" Height="200">
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="{Binding ActualHeight, Source={x:Reference lb}}" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ContextMenu>
<StaticResource ResourceKey="cm" />
</ListBox.ContextMenu>
</ListBox>
This work for me CommandParameter="{Binding}"
I have a treeview bound to a Observable collection of some property type. There is a HierarchicalDataTemplate that shows the data in treeview. Now i need to show specific context menu for each HierarchicalDataTemplate item.
I am using the following XAML to show context menu:
<HierarchicalDataTemplate ItemsSource="{Binding Collections}">
<TextBlock Text="{Binding Path=Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Create" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.AddCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
Here the AddCommand is written in the view model that is bound to this under control..
I am able to see the context menu, but event is not firing on click on menu item.
Please help..
Your command binding will not work because the ContextMenu is not on the same logical tree as your UserControl is, therefore it will not find the UserControl ancestor. However your ContextMenu should inherit its container's datacontext automatically.
So this should work -
<ContextMenu>
<MenuItem Header="Create" Command="{Binding AddCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
However the AddCommand property should exist on your HierarchicalDataTemplate bound item.
EDIT:
If your Command is not defined in your HierarchicalDataTemplate's bound item and instead in your UserControl. Then another think you may try is giving your UserControl a name, and then bind the command to it by ElementName. Like this
Updated again:
<ContextMenu>
<MenuItem Header="Create" Command="{Binding ElementName="MyUserControl" Path="DataContext.AddCommand"}" CommandParameter="{Binding}"/>
</ContextMenu>
I want to bind a command in my ViewModel to a menuItem which is in DataTemplate. I can do that with using Tag. Is there any method which can do the same task but without using tag.
<Window.Resources>
<DataTemplate x:Key="StudentListBoxItemTemplate">
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}">
<TextBlock Text="{Binding Name}"/>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Trigger" Command="{Binding PlacementTarget.Tag.TriggerCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox
ItemsSource="{Binding StudentList}"
ItemTemplate="{StaticResource StudentListBoxItemTemplate}">
</ListBox>
</StackPanel>
My ViewModel
public class MainViewModel {
public ICommand TriggerCommand { ... }
public ObservableList<Student> StudentList { ... }
}
With your current design, you need to get from the ContextMenu through the StackPanel and back to the DataContext of the containing ListBox. What makes this awkward is that the DataContext of the StackPanel is already narrowed down to a particular student.
There are at least two ways to make this easier:
Provide a TriggerCommand property in Student so the command is right there where you need it
Provide a Parent property in the Student to escape the narrowed scope
You can try to add click event to the menuItem like following
<Menu Style="{StaticResource bellRingersFontStyle}" Height="23" Name="menu1" Width="Auto" DockPanel.Dock="Top" VerticalAlignment="Top">
<MenuItem Header="_File">
<MenuItem Header="_New Member" Name="newMember" Click="newMember_Click" >
<MenuItem.Icon>
<Image Source="Face.bmp" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Save Member Details" Name="saveMember" IsEnabled="False" Click="saveMember_Click">
<MenuItem.Icon>
<Image Source="Note.bmp" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="E_xit" Name="exit" Click="exit_Click" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About Middleshire Bell Ringers" Name="about" Click="about_Click" >
<MenuItem.Icon>
<Image Source="Ring.bmp" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
Try binding the command to Click. My VS is down so can't check at this moment.
One way would be to get the context menu collection presentation defined in your viewmodel, which will contain the header string and command action (maybe with predicate).
The viewmodel creates an observable collection of the contextmenu items and the view binds that to ContextMenu itemssource and sets the displaymember path to header string.
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