kaaaxcreators
kaaaxcreators

Reputation: 133

WPF ListView ContextMenu

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 ListViewItems, 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

Answers (1)

thatguy
thatguy

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

Related Questions