Передача источника ContextMenu в команду WPF
Вопрос
Интересная проблема, связанная с запуском команд из пунктов контекстного меню...
Я хочу запустить команду для вставки строки в мой элемент управления InsertRowCmd.Эта команда должна знать, куда вставить строку.
Я мог бы использовать мышь.GetPosition() , но это дало бы мне текущее положение мыши, которое было бы над пунктом меню.Вместо этого я хочу получить источник контекстного меню.
Есть ли у кого-нибудь какие-либо предложения о том, как передать начало контекстного меню в качестве параметра команде?
Пример кода:
<UserControl x:Name="MyControl">
<!--...-->
<ContextMenu x:Name="menu">
<MenuItem Header="Insert Row" Command="{x:Static customCommands:MyCommands.InsertRowCmd}" CommandParameter="?"/>
</ContextMenu>
</UserControl>
Мои текущие идеи заключаются в следующем:
-Вместо этого используйте обработчик щелчков, чтобы я мог найти источник в коде.Проблема в том, что тогда мне пришлось бы обрабатывать включение / выключение.
-Обработайте событие щелчка и сохраните источник контекстного меню.Передайте эту сохраненную информацию в команду.Я проверил, что события щелчка срабатывают перед выполнением команды.
Есть какие-нибудь идеи?
Редактировать:
Я пользуюсь услугами Джоша Смита Командная привязка чтобы перенаправить обработку команд в мой класс ViewModel.Таким образом, код, который обрабатывает выполнение команды, ничего не знает о представлении.
Решение
Вам нужно будет использовать TranslatePoint
чтобы перевести верхний левый (0, 0) ContextMenu
к координате в содержащей сетке.Вы могли бы сделать это, связав CommandParameter
к тому ContextMenu
и используйте конвертер:
CommandParameter="{Binding IsOpen, ElementName=_menu, Converter={StaticResource PointConverter}}"
Другим подходом было бы прикрепленное поведение, которое автоматически обновляет прикрепленное свойство типа только для чтения Point
всякий раз, когда ContextMenu
открывается.Использование выглядело бы примерно так:
<ContextMenu x:Name="_menu" local:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="..." CommandParameter="{Binding Path=(local:TrackBehavior.OpenLocation), ElementName=_menu}"/>
</ContextMenu>
Таким образом , TrackOpenLocation
прикрепленное свойство выполняет работу по присоединению к ContextMenu
и обновление второго присоединенного свойства (OpenLocation
) всякий раз , когда ContextMenu
открывается.Затем в MenuItem
можно просто привязать к OpenLocation
чтобы получить местоположение, в котором ContextMenu
был открыт последним.
Другие советы
Следуя ответу Кента, я использовал его приложенное предложение о свойствах и в итоге получил это (используя Джоша Смита пример для привязанных поведений):
public static class TrackBehavior
{
public static readonly DependencyProperty TrackOpenLocationProperty = DependencyProperty.RegisterAttached("TrackOpenLocation", typeof(bool), typeof(TrackBehavior), new UIPropertyMetadata(false, OnTrackOpenLocationChanged));
public static bool GetTrackOpenLocation(ContextMenu item)
{
return (bool)item.GetValue(TrackOpenLocationProperty);
}
public static void SetTrackOpenLocation(ContextMenu item, bool value)
{
item.SetValue(TrackOpenLocationProperty, value);
}
public static readonly DependencyProperty OpenLocationProperty = DependencyProperty.RegisterAttached("OpenLocation", typeof(Point), typeof(TrackBehavior), new UIPropertyMetadata(new Point()));
public static Point GetOpenLocation(ContextMenu item)
{
return (Point)item.GetValue(OpenLocationProperty);
}
public static void SetOpenLocation(ContextMenu item, Point value)
{
item.SetValue(OpenLocationProperty, value);
}
static void OnTrackOpenLocationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var menu = dependencyObject as ContextMenu;
if (menu == null)
{
return;
}
if (!(e.NewValue is bool))
{
return;
}
if ((bool)e.NewValue)
{
menu.Opened += menu_Opened;
}
else
{
menu.Opened -= menu_Opened;
}
}
static void menu_Opened(object sender, RoutedEventArgs e)
{
if (!ReferenceEquals(sender, e.OriginalSource))
{
return;
}
var menu = e.OriginalSource as ContextMenu;
if (menu != null)
{
SetOpenLocation(menu, Mouse.GetPosition(menu.PlacementTarget));
}
}
}
и затем, чтобы использовать в Xaml, вам просто нужно:
<ContextMenu x:Name="menu" Common:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="{Binding SomeCommand}" CommandParameter="{Binding Path=(Common:TrackBehavior.OpenLocation), ElementName=menu}" Header="Menu Text"/>
</ContextMenu>
Однако мне также нужно было добавить:
NameScope.SetNameScope(menu, NameScope.GetNameScope(this));
к конструктору моего представления, в противном случае привязка для CommandParameter
не удалось выполнить поиск ElementName=menu
.
В дополнение к ответу Кента подумайте о "стандартном способе".Ф.е.когда в ListBox есть ContextMenu, вам не нужна позиция меню, потому что выбранный элемент задается до появления меню.Итак, если в вашем элементе управления будет что-то, что будет "выбрано" при щелчке правой кнопкой мыши...