Reputation: 133
Gitea Repo
I have a WPF C# Application with a ListView
and there inside is a GridView
.
<ListView Name="Entries">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Header="Type" Width="Auto" DisplayMemberBinding="{Binding Type}" />
<GridViewColumn Header="Title" Width="Auto" DisplayMemberBinding="{Binding Title}" />
<GridViewColumn Header="Date" Width="Auto" DisplayMemberBinding="{Binding Date}" />
<GridViewColumn Header="Tags" Width="Auto" DisplayMemberBinding="{Binding Tags}" />
<GridViewColumn Header="Categories" Width="Auto" DisplayMemberBinding="{Binding Categories}" />
</GridView>
</ListView.View>
</ListView>
When I right click on of the ListViewItem
s, a ContextMenu
should come up and when I click on it, it should execute a function.
Something like this:
private void EntryClick(<identifier of click>) {
MessageBox.Show("Clicked " + <maybe title to identify what i clicked>);
}
Everywhere I found this, but I don't know what to do with it:
<MenuItem ... CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor,ListBox,1}} />
Then I found this: WPF Get name of ListView item when ContextMenu clicked But I don't anything for item.Name
Edit:
I added
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
and
using GalaSoft.MvvmLight.Command;
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))
MessageBox.Show(item);
}
But now I get this error:
Delegate System.Action does not take 1 arguments
I installed the NuGet package GalaSoft.MvvmLight
because I didn't have RelayCommand
. Was this right or do I have to create a class?
Upvotes: 0
Views: 883
Reputation: 22079
Your original CommandParameter
binding does not work because a ContextMenu
is not part of the same visual tree as the control it is associated with and the ancestor type is wrong (ListBox
instead of ListView
). The original binding below translates to find the first parent of type ListBox
, get its SelectedItem
and assign it as CommandParameter
.
<MenuItem CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, ListBox, 1}} />
What you can do instead is leverage the fact that the PlacementTarget
property of the ContextMenu
contains a reference to the associated control (here ListView
). So the following binding translates to find the parent ContextMenu
, get the SelectedItem
of its associated control and assign it as CommandParameter
.
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
Delegate System.Action does not take 1 arguments
You get this error because you use the wrong command type. A RelayCommand
constructor takes an execute delegate of type Action
. This delelgate does not take any parameters.
public RelayCommand(Action execute, bool keepTargetAlive = false)
What you were looking for is the generic RelayCommand<T>
that takes an Action<T>
.
public RelayCommand(Action<T> execute, bool keepTargetAlive = false)
Adapt your property to use a RelayCommand<string>
, no need for casting to string
here.
public ICommand RemoveItem
{
get { return _removeItem ?? (_removeItem = new RelayCommand<string>(RemoveItemCommand)); }
}
Update for your comment. Your RemoveItem
command is defined in the code-behind of your window. Since you are doing code-behind anyway and access your controls directly to set ItemsSource
and so on you could set the DataContext
of the window to itsself in the constructor.
public MainWindow()
{
InitializeComponent();
// ...your other code.
DataContext = this;
}
The data context is inherited in controls, so to acces the RemoveItem
command on the data context, you can use the same approach as above with the PlacementTarget
.
<MenuItem Header="Remove"
Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext.RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
Please note that there are countless ways to fix this issue, using the DataContext
is only one, but since it is unclear which design approach you are following, it is hard to give a definitive answer. Consider looking into the MVVM pattern, which will make these kinds of issues much easier.
Update to your comment that you get an InvalidCastException
.
hexo_gui.Entries" cant be converted to "System.String".
That is because the items collection holds items of type Entries
, not string
, but in your command you expect a string
, hence the exception. Adapt your command to expect an Entries
instance.
public ICommand RemoveItem => _removeItem ?? (_removeItem = new RelayCommand<Entries>(RemoveItemCommand));
private void RemoveItemCommand(Entries item)
{
// No item was selected.
if (item == null)
return;
// Do something with the item.
}
Upvotes: 1