Проходя происхождения в контекстное меню команды в WPF

интересная проблема, связанная с запуском команд из пунктов контекстного меню...

Я хочу запустить команду для вставки строки в мой элемент управления InsertRowCmd. Эта команда должна знать, куда вставить строку.

Я мог бы использовать мышь.GetPosition (), но это даст мне положение мыши в настоящее время, которое будет над пунктом меню. Вместо этого я хочу получить происхождение контекстного меню.

есть ли у кого-нибудь предложения о том, как передать происхождение контекстного меню как параметра команды?

пример кода:

<UserControl x:Name="MyControl">
<!--...-->
        <ContextMenu x:Name="menu">
            <MenuItem Header="Insert Row" Command="{x:Static customCommands:MyCommands.InsertRowCmd}" CommandParameter="?"/>
        </ContextMenu>
</UserControl>

мои текущие идеи таковы:

- вместо этого используйте обработчик click, чтобы я мог найти источник в коде. Проблема в том, что тогда мне придется обрабатывать включение/выключение.

- обработайте событие щелчка и сохраните начало контекстного меню. Передайте эту информацию в команду. Я проверил, что click events fire перед командой выполненный.

какие идеи?

EDIT:

Я использую CommandSinkBinding для маршрутизации обработки команд в мой класс ViewModel. Таким образом, код, обрабатывающий выполнение команды, ничего не знает о представлении.

3 ответов


вам понадобится TranslatePoint перевести верхний левый (0, 0) из ContextMenu к координате в содержащей сетке. Вы можете сделать это, связав CommandParameter до ContextMenu и использовать конвертер:

CommandParameter="{Binding IsOpen, ElementName=_menu, Converter={StaticResource PointConverter}}"

другим подходом было бы прикрепленное поведение, которое автоматически обновляет прикрепленное свойство readonly типа Point, когда - это. Использование будет выглядеть примерно так:

<ContextMenu x:Name="_menu" local:TrackBehavior.TrackOpenLocation="True">
    <MenuItem Command="..." CommandParameter="{Binding Path=(local:TrackBehavior.OpenLocation), ElementName=_menu}"/>
</ContextMenu>

так TrackOpenLocation прикрепленный свойство выполняет работу присоединения к ContextMenu и обновление второго арестованного имущества (OpenLocation) если - это. Тогда 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.


в дополнение к ответу Кента подумайте о "стандартном способе". F. e. когда список имеет ContextMenu, вам не нужно положение меню, потому что выбранный элемент установлен до того, как меню выскочило. Итак, если ваш элемент управления будет иметь что-то, что "выбрано" Правой Кнопкой Мыши...