Getting access to listviews (selected) items from context menu - c#

In my WPF MVVM (using Prism) application I have a listview. In this listview I have bound a Ctrl-C key input to a copy function with an IList parameter. (see CopyChannelChangeNotificationToClipboardCommand)
This works well.
Now I want to add a context menu that has a MenuItem with "Copy Ctrl-C" functionality.
What I do not understand is how to bind the CommandParameter from the MenuItem so that it provides the ListView's Selected items? The idea is to use the same function as the input bound key, i.e. get the IList from the listview to be attached as a command parameter.
I've tried to read quite a few posts here, tried even more but all result in the objList parameter to be null.
If anyone has suggestions on how to accomplish this I'd be grateful.
My view (partial)
<ListView Grid.Row="1" ItemTemplate="{StaticResource ChannelChangeDataTemplateKey}" ItemsSource="{Binding ChannelChangeNotifications}" lb:ListBoxBehavior.ScrollOnNewItem="true">
<ListView.InputBindings>
<KeyBinding Key="C" Modifiers="Control" Command="{Binding CopyChannelChangeNotificationToClipboardCommand}" CommandParameter="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
</ListView.InputBindings>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.UICopySelectedItems}" Command="{Binding CopySelectedItemsClickCommand}" CommandParameter="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
and my view model code
public Prism.Commands.DelegateCommand<IList> CopyChannelChangeNotificationToClipboardCommand => new Prism.Commands.DelegateCommand<IList>(CopyChannelChangeNotificationToClipboard);
public Prism.Commands.DelegateCommand<IList> CopySelectedItemsClickCommand => new Prism.Commands.DelegateCommand<IList>(CopyChannelChangeNotificationToClipboard);
private void CopyChannelChangeNotificationToClipboard(IList objList)
{
if (objList != null)
{
// Copy selected list view objects to clipboard
// Works well when coming from CopyChannelChangeNotificationToClipboardCommand
// but not when coming from CopySelectedItemsClickCommand
// since objList is always null
}
}

Related

Why is my CommandParameter null when I have the Path as SelectedItem?

So I am trying to pass the SelectedItem as a parameter so that I can make use of the data that is bound to it.
Essentially I want to open a MessageBox and display the Name property of the User that is bound that item.
This is my xaml
<ItemsControl ItemsSource="{Binding CardViewModel.Users}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultEffectDataTemplate="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserCard>
<controls:UserCard.ContextMenu>
<!-- Bind the DataContext of the CM to the DataContext that's bound to the RootObject-->
<ContextMenu DataContext="{Binding DataContext, Source={local:RootObject}}">
<MenuItem Header="Edit"
Command="{Binding CardViewModel.EditUser}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}},
Path=PlacementTarget.SelectedItem}"/>
</ContextMenu>
</controls:UserCard.ContextMenu>
</controls:UserCard>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The command works fine everything is bound just fine besides that when I click the MenuItem and it fires the command, I put a breakpoint where the action is and it shows the parameter as null I am suspecting that it's me who is binding it wrong.
public void DisplayEditUser(object user)
{
if (user != null)
{
MessageBox.Show("Not null");
}
}
The problem is that ContextMenu.PlacementTarget wasn't the ItemsControl but the UserCard, so binding source resolving will absolutely fail. To solve it, you need to bind ItemsControl.SelectedItem to one property of UserCard such as Tag as a relay.
<controls:UserCard Tag="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=ListBox}}">
CommandParameter="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType=ContextMenu}}"

How do I create a "Window" menu in XAML?

I am creating an MVVM Wpf client application. I want create menu in the main View for the application that his a menu item called "Window" on it. That menu item will dynamically update itself with a submenu of menuitems who are made up of the list of active windows running in the application. I created a ViewManager whom each View registers itself with to compile a list of active windows.
I am trying to do this in XAML but getting an error when I click on "Window"
<MenuItem Header="Window">
<ItemsControl ItemsSource="{Binding ViewMgr.Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd ,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</MenuItem>
How do I create a dynamically updated list of menuitems on my menu in XAML using a MVVM style of data bindings and commands?
You are adding a new ItemsControl as a single child of the menu item, instead of adding each view as one child of the menu item itself. You probably get the error because the styles TargetType doesn't match. MenuItem inherits from ItemsControl itself and exposes a property ItemsSource. Try the following:
<MenuItem ItemsSource="{Binding ViewMgr.Views}" DisplayMemberPath="Title">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

ContextMenu in MVVM

I want to bind a contextmenu to a list of commands.
<Grid.ContextMenu>
<ContextMenu ItemsSource="{Binding ItemContextCommands, Converter={StaticResource commandToStringConverter}}">
<ContextMenu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Command="{Binding}"></MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Grid.ContextMenu>
The commandToStringConverter simply converts a list of commands to a list of strings calling the ToString() on each command in the list.
How can I achieve that the Command in each MenuItem is called?
I would use a small "view model" to hold the informations for such a command.
class ContextAction : INotifyPropertyChanged
{
public string Name;
public ICommand Action;
public Brush Icon;
}
make a collection inside your view model which should get the context actions like
ObservableCollection<ContextAction> Actions {get;set;}
and simply bind this collection to your ContextMenu.
<Grid.ContextMenu>
<ContextMenu ItemsSource="{Binding Actions}" />
The ItemTemplate for the contextmenu items can now access the name, the command and whatever else you might need. It might be useful to change the CommandParameter as well so that it will call the command with the actions owning element, not with the action itself.
i use something like this:
public class ContextMenuVM
{
public string Displayname {get;set;}
public ICommand MyContextMenuCommand {get;set;}
}
in your contextmenu datacontext:
public ObservableCollection<ContextMenuVM> MyCommandList {get;set;}
in your xaml
<ContextMenu ItemsSource="{Binding MyCommandList}">
<ContextMenu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Header="{Binding Displayname}" Command="{Binding MyContextMenuCommand}"></MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
its written without ide, so maybe some syntax errors in there
An improved XAML version of #blindmils solution below:
<ContextMenu ItemsSource="{Binding MyCommandList}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Displayname}" />
<Setter Property="Command" Value="{Binding MyContextMenuCommand }" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>

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" />

WPF: Binding a ContextMenu to an MVVM Command

Let's say I have a Window with a property returning a Command (in fact, it's a UserControl with a Command in a ViewModel class, but let's keep things as simple as possible to reproduce the problem).
The following works:
<Window x:Class="Window1" ... x:Name="myWindow">
<Menu>
<MenuItem Command="{Binding MyCommand, ElementName=myWindow}" Header="Test" />
</Menu>
</Window>
But the following does not work.
<Window x:Class="Window1" ... x:Name="myWindow">
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding MyCommand, ElementName=myWindow}" Header="Test" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Window>
The error message I get is
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myWindow'. BindingExpression:Path=MyCommand; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
Why? And how do I fix this? Using the DataContext is not an option, since this problem occurs way down the visual tree where the DataContext already contains the actual data being displayed. I already tried using {RelativeSource FindAncestor, ...} instead, but that yields a similar error message.
The problem is that the ContextMenu it not in the visual tree, so you basically have to tell the Context menu about which data context to use.
Check out this blogpost with a very nice solution of Thomas Levesque.
He creates a class Proxy that inherits Freezable and declares a Data dependency property.
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Then it can be declared in the XAML (on a place in the visual tree where the correct DataContext is known):
<Grid.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</Grid.Resources>
And used in the context menu outside the visual tree:
<ContextMenu>
<MenuItem Header="Test" Command="{Binding Source={StaticResource Proxy}, Path=Data.MyCommand}"/>
</ContextMenu>
Hurray for web.archive.org! Here is the missing blog post:
Binding to a MenuItem in a WPF Context Menu
Wednesday, October 29, 2008 — jtango18
Because a ContextMenu in WPF does not exist within the visual tree of
your page/window/control per se, data binding can be a little tricky.
I have searched high and low across the web for this, and the most
common answer seems to be “just do it in the code behind”. WRONG! I
didn’t come in to the wonderful world of XAML to be going back to
doing things in the code behind.
Here is my example to that will allow you to bind to a string that
exists as a property of your window.
public partial class Window1 : Window
{
public Window1()
{
MyString = "Here is my string";
}
public string MyString
{
get;
set;
}
}
<Button Content="Test Button" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" >
<MenuItem Header="{Binding MyString}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
The important part is the Tag on the button(although you could just as
easily set the DataContext of the button). This stores a reference to
the parent window. The ContextMenu is capable of accessing this
through it’s PlacementTarget property. You can then pass this context
down through your menu items.
I’ll admit this is not the most elegant solution in the world.
However, it beats setting stuff in the code behind. If anyone has an
even better way to do this I’d love to hear it.
I found out it wasn't working for me due to the menu item being nested, which mean I had to traverse up an extra "Parent" to find the PlacementTarget.
A better way is to find the ContextMenu itself as the RelativeSource and then just bind to the placement target of that. Also since the tag is the window itself, and your command is in the viewmodel, you need to have the DataContext set as well.
I ended up with something like this
<Window x:Class="Window1" ... x:Name="myWindow">
...
<Grid Tag="{Binding ElementName=myWindow}">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding PlacementTarget.Tag.DataContext.MyCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ContextMenu}}"
Header="Test" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Window>
What this means is that if you end up with a complicated context menu with submenus etc.. you don't need to keep adding "Parent" to each levels Commands.
-- EDIT --
Also came up with this alternative to set a tag on every ListBoxItem that binds to the Window/Usercontrol. I ended up doing this because each ListBoxItem was represented by their own ViewModel but I needed the menu commands to execute via the top level ViewModel for the control, but pass their the list ViewModel as a parameter.
<ContextMenu x:Key="BookItemContextMenu"
Style="{StaticResource ContextMenuStyle1}">
<MenuItem Command="{Binding Parent.PlacementTarget.Tag.DataContext.DoSomethingWithBookCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ContextMenu}}"
CommandParameter="{Binding}"
Header="Do Something With Book" />
</MenuItem>>
</ContextMenu>
...
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu" Value="{StaticResource BookItemContextMenu}" />
<Setter Property="Tag" Value="{Binding ElementName=thisUserControl}" />
</Style>
</ListView.ItemContainerStyle>
Based on HCLs answer, this is what I ended up using:
<Window x:Class="Window1" ... x:Name="myWindow">
...
<Grid Tag="{Binding ElementName=myWindow}">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding Parent.PlacementTarget.Tag.MyCommand,
RelativeSource={RelativeSource Self}}"
Header="Test" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Window>
If (like me) you have an aversion to ugly complex binding expressions, here is a simple code-behind solution to this problem. This approach still allows you to keep clean command declarations in your XAML.
XAML:
<ContextMenu ContextMenuOpening="ContextMenu_ContextMenuOpening">
<MenuItem Command="Save"/>
<Separator></Separator>
<MenuItem Command="Close"/>
...
Code behind:
private void ContextMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
foreach (var item in (sender as ContextMenu).Items)
{
if(item is MenuItem)
{
//set the command target to whatever you like here
(item as MenuItem).CommandTarget = this;
}
}
}
Answer in 2020:
I'm leaving this answer here for anyone else who googled this question, as this is the first search result that shows up.
This worked for me and is simpler than the other suggested solutions:
<MenuItem Command="{Binding YourCommand}" CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
As described here:
https://wpf.2000things.com/2014/06/19/1097-getting-items-in-context-menu-to-correctly-use-command-binding/

Categories