WPF: Displaying a Context Menu for a GridView'

2019-01-13 16:15发布

I have the following GridView:

<ListView Name="TrackListView" ItemContainerStyle="{StaticResource itemstyle}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Title" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Name}"/>
            <GridViewColumn Header="Artist" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Album.Artist.Name}" />
            <GridViewColumn Header="Album" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Album.Name}"/>
            <GridViewColumn Header="Length" Width="100" HeaderTemplate="{StaticResource BlueHeader}"/>
        </GridView>
     </ListView.View>
</ListView>

Now I would like to display a context menu on a right click on a bounded item that will allow me to retrieve the item selected when I handle the event in the code behind.

In what possible way can I accomplish this?


[Update]

Following Dennis Roche's code, I now have this:

    <ListView Name="TrackListView" ItemContainerStyle="{StaticResource itemstyle}">
        <ListView.ItemContainerStyle>
            <Style TargetType="{x:Type ListViewItem}">
                <EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnListViewItem_PreviewMouseLeftButtonDown" />
                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu>
                            <MenuItem Header="Add to Playlist"></MenuItem>
                        </ContextMenu>
                     </Setter.Value>
                </Setter>
            </Style>
        </ListView.ItemContainerStyle>

        <ListView.View>
            <GridView>
                <GridViewColumn Header="Title" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Name}"/>
                <GridViewColumn Header="Artist" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Album.Artist.Name}" />
                <GridViewColumn Header="Album" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Album.Name}"/>
                <GridViewColumn Header="Length" Width="100" HeaderTemplate="{StaticResource BlueHeader}"/>
            </GridView>
         </ListView.View>
    </ListView>

But upon running, I am receiving this exception:

Cannot add content of type 'System.Windows.Controls.ContextMenu' to an object of type 'System.Object'. Error at object 'System.Windows.Controls.ContextMenu' in markup file 'MusicRepo_Importer;component/controls/trackgridcontrol.xaml'.

What is the problem?

3条回答
虎瘦雄心在
2楼-- · 2019-01-13 17:08

Yes, add a ListView.ItemContainerStyle with the Context Menu.

<ListView>
  <ListView.Resources>
    <ContextMenu x:Key="ItemContextMenu">
      ...
    </ContextMenu>
  </ListView.Resources>
  <ListView.ItemContainerStyle>
    <Style TargetType="{x:Type ListViewItem}">
      <EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnListViewItem_PreviewMouseLeftButtonDown" />
      <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}"/>
    </Style>
  </ListView.ItemContainerStyle>
</ListView>

NOTE: You need to reference the ContextMenu as a resource and cannot define it locally.

This will enable the context menu for the entire row. :)

Also see that I handle the PreviewMouseLeftButtonDown event so I can ensure the item is focused (and is the currently selected item when you query the ListView). I found that I had to this when changing focus between applications, this may not be true in your case.

Updated

In the code behind file you need to walk-up the visual tree to find the list container item as the original source of the event can be an element of the item template (e.g. a stackpanel).

void OnListViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
  if (e.Handled)
    return;

  ListViewItem item = MyVisualTreeHelper.FindParent<ListViewItem>((DependencyObject)e.OriginalSource);
  if (item == null)
    return;

  if (item.Focusable && !item.IsFocused)
    item.Focus();
}

The MyVisualTreeHelper that is use a wrapper that I've written to quickly walk the visual tree. A subset is posted below.

public static class MyVisualTreeHelper
{
  static bool AlwaysTrue<T>(T obj) { return true; }

  /// <summary>
  /// Finds a parent of a given item on the visual tree. If the element is a ContentElement or FrameworkElement 
  /// it will use the logical tree to jump the gap.
  /// If not matching item can be found, a null reference is returned.
  /// </summary>
  /// <typeparam name="T">The type of the element to be found</typeparam>
  /// <param name="child">A direct or indirect child of the wanted item.</param>
  /// <returns>The first parent item that matches the submitted type parameter. If not matching item can be found, a null reference is returned.</returns>
  public static T FindParent<T>(DependencyObject child) where T : DependencyObject
  {
    return FindParent<T>(child, AlwaysTrue<T>);
  }

  public static T FindParent<T>(DependencyObject child, Predicate<T> predicate) where T : DependencyObject
  {
    DependencyObject parent = GetParent(child);
    if (parent == null)
      return null;

    // check if the parent matches the type and predicate we're looking for
    if ((parent is T) && (predicate((T)parent)))
      return parent as T;
    else
      return FindParent<T>(parent);
  }

  static DependencyObject GetParent(DependencyObject child)
  {
    DependencyObject parent = null;
    if (child is Visual || child is Visual3D)
      parent = VisualTreeHelper.GetParent(child);

    // if fails to find a parent via the visual tree, try to logical tree.
    return parent ?? LogicalTreeHelper.GetParent(child);
  }
}

I hope this additional information helps.

Dennis

查看更多
一夜七次
3楼-- · 2019-01-13 17:10

You might be interested in the answers for this SO question - I had the same question but wasn't satisfied with using the mousedown event to capture the item that was clicked upon. Several people has responded with simple and easy to comprehend solutions that you might be interested in.

Summary : You can use the data context to pass the item through to the handler, or a command + command parameter setup.

查看更多
劫难
4楼-- · 2019-01-13 17:15

Dennis,

Love the example, however I did not find any need for your Visual Tree Helper...

   <ListView.Resources>
    <ContextMenu x:Key="ItemContextMenu">
        <MenuItem x:Name="menuItem_CopyUsername"
                  Click="menuItem_CopyUsername_Click"
                  Header="Copy Username">
            <MenuItem.Icon>
                <Image Source="/mypgm;component/Images/Copy.png" />
            </MenuItem.Icon>
        </MenuItem>
        <MenuItem x:Name="menuItem_CopyPassword"
                  Click="menuItem_CopyPassword_Click"
                  Header="Copy Password">
            <MenuItem.Icon>
                <Image Source="/mypgm;component/Images/addclip.png" />
            </MenuItem.Icon>
        </MenuItem>
        <Separator />
        <MenuItem x:Name="menuItem_DeleteCreds"
                  Click="menuItem_DeleteCreds_Click"
                  Header="Delete">
            <MenuItem.Icon>
                <Image Source="/mypgm;component/Images/Delete.png" />
            </MenuItem.Icon>
        </MenuItem>
    </ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
    <Style TargetType="{x:Type ListViewItem}">
        <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}" />
    </Style>
</ListView.ItemContainerStyle>

Then inside the MenuItem_Click events I added code that looks like this:

private void menuItem_CopyUsername_Click(object sender, RoutedEventArgs e)
{
    Clipboard.SetText(mySelectedItem.Username);
}

mySelectedItem is used on the ListView.SelectedItem:

 <ListView x:Name="ListViewCreds" SelectedItem="{Binding mySelectedItem, UpdateSourceTrigger=PropertyChanged}" ....

Please tick me if it helps...

查看更多
登录 后发表回答