WPF: отображение контекстного меню для элементов GridView
У меня есть следующий 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>
Теперь я хотел бы отобразить контекстное меню по правому щелчку на ограниченном элементе, который позволит мне получить элемент, выбранный, когда я обрабатываю событие в коде.
Каким возможным способом я могу это сделать?
[Обновление]
Следуя Dennis Roche, у меня теперь есть это:
<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>
Но при запуске я получаю это исключение:
Невозможно добавить содержимое типа 'System.Windows.Controls.ContextMenu' к объекту типа "System.Object". Ошибка в объекте 'System.Windows.Controls.ContextMenu' в файле разметки. 'MusicRepo_Importer; компонент/контроль/trackgridcontrol.xaml'
В чем проблема?
Ответы
Ответ 1
Да, добавьте ListView.ItemContainerStyle в контекстное меню.
<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>
ПРИМЕЧАНИЕ. Вам необходимо ссылаться на ContextMenu как на ресурс и не может определить его локально.
Это позволит контекстное меню для всей строки.:)
Также см., что я обрабатываю событие PreviewMouseLeftButtonDown
, поэтому я могу убедиться, что элемент сфокусирован (и является выбранным в данный момент элементом при запросе ListView). Я обнаружил, что мне приходилось это делать при изменении фокуса между приложениями, это может быть неверно в вашем случае.
Обновление
В коде, находящемся за файлом, вам нужно подобрать визуальное дерево, чтобы найти элемент контейнера списка, поскольку исходный источник события может быть элементом шаблона элемента (например, стекальной панели).
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();
}
MyVisualTreeHelper
, который использует обертку, которую я написал, чтобы быстро пройти визуальное дерево. Подмножество опубликовано ниже.
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);
}
}
Я надеюсь, что эта дополнительная информация поможет.
Dennis
Ответ 2
Деннис,
Полюбуйте пример, однако я не нашел нужды в вашем помощнике Visual Tree...
<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>
Затем внутри событий MenuItem_Click я добавил код, который выглядит так:
private void menuItem_CopyUsername_Click(object sender, RoutedEventArgs e)
{
Clipboard.SetText(mySelectedItem.Username);
}
mySelectedItem используется в ListView.SelectedItem:
<ListView x:Name="ListViewCreds" SelectedItem="{Binding mySelectedItem, UpdateSourceTrigger=PropertyChanged}" ....
Пожалуйста, отметьте меня, если это поможет...
Ответ 3
Возможно, вас заинтересуют ответы this SO question - у меня был тот же вопрос, но я не был удовлетворен использованием события mousedown для захвата элемента, который был нажал. Несколько человек ответили простым и легким для понимания решениями, которые могут вас заинтересовать.
Сводная информация. Вы можете использовать контекст данных, чтобы передать элемент через обработчик или настройку параметров команды +.