Как ссылаться на объект, щелкаемый правой кнопкой мыши, в обработчике событий щелчка пункта контекстного меню WPF? - PullRequest
8 голосов
/ 09 января 2010

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

   <Grid.ContextMenu>
     <ContextMenu>
       <MenuItem  Name="EditStatusCm" Header="Change status" Click="EditStatusCm_Click"/>
     </ContextMenu>                   
   </Grid.ContextMenu> 

Но в обработчике событий я не могу узнать, по какому объекту щелкнули правой кнопкой мыши:

    private void EditStatusCm_Click(object sender, RoutedEventArgs e)
    {
        MyCustControl SCurrent = new MyCustControl();
        MenuItem menu = sender as MenuItem;
        SCurrent = menu.DataContext as MyCustControl; // here I get a run-time error
        SCurrent.Status = MyCustControl.Status.Sixth;
    }

В этой закомментированной строке отладчик говорит: Ссылка на объект не установлена ​​на экземпляр объекта.

Пожалуйста, помогите, что не так в моем коде?

Отредактировано (добавлено):

Я пытался сделать то же самое, используя Command подхода:

Я объявил DataCommands класс с RoutedUICommand Requery, а затем использовал Window.CommandBindings

<Window.CommandBindings>
  <CommandBinding Command="MyNamespace:DataCommands.Requery" Executed="RequeryCommand_Executed"></CommandBinding>
</Window.CommandBindings>

XAML из MenuItem теперь выглядит так:

<Grid.ContextMenu>
 <ContextMenu>
  <MenuItem  Name="EditStatusCm" Header="Change status"  Command="MyNamespace:DataCommands.Requery"/>
 </ContextMenu>                   
</Grid.ContextMenu>

А обработчик событий выглядит так:

    private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)sender);
        MyCustControl SCurrent = new MyCustControl();
        SCurrent = (MuCustControl)parent;
        string str = SCurrent.Name.ToString();// here I get the same error
        MessageBox.Show(str);
    }

Но отладчик показывает ту же ошибку во время выполнения: Ссылка на объект не установлена ​​на экземпляр объекта.

Чего не хватает в обоих моих подходах?

Как мне ссылаться на объект, щелкаемый правой кнопкой мыши в обработчике события щелчка пункта контекстного меню WPF?

Ответы [ 8 ]

23 голосов
/ 13 января 2010

обратите внимание на CommandParameter

<Grid Background="Red" Height="100" Width="100">
    <Grid.ContextMenu>
        <ContextMenu>
            <MenuItem 
                Header="Change status" 
                Click="EditStatusCm_Click"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
        </ContextMenu>
    </Grid.ContextMenu>
</Grid>

и используйте его в обработчике, чтобы выяснить, какая это сетка

    private void EditStatusCm_Click(object sender, RoutedEventArgs e)
    {
        MenuItem mi = sender as MenuItem;
        if (mi != null)
        {
            ContextMenu cm = mi.CommandParameter as ContextMenu;
            if (cm != null)
            {
                Grid g = cm.PlacementTarget as Grid;
                if (g != null)
                {
                    Console.WriteLine(g.Background); // Will print red
                }
            }
        }
    }

Обновление:
Если вы хотите, чтобы обработчик menuitem получал дочерние элементы Grid вместо самой Grid, используйте этот подход

<Grid Background="Red" Height="100" Width="100">
    <Grid.Resources>
        <ContextMenu x:Key="TextBlockContextMenu">
            <MenuItem 
                Header="Change status" 
                Click="EditStatusCm_Click"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
        </ContextMenu>

        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="ContextMenu" Value="{StaticResource TextBlockContextMenu}" />
        </Style>
    </Grid.Resources>

    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>

    <TextBlock Text="Row0" Grid.Row="0" />
    <TextBlock Text="Row1" Grid.Row="1" />
</Grid>

Просто замените TextBlocks любым вашим типом объекта. Затем в обработчике событий замените Grid g = cm.PlacementTarget as Grid на TextBlock t = cm.PlacementTarget as TextBlock (или любой другой тип вашего пользовательского объекта).

5 голосов
/ 12 августа 2013

Привязывая контекст данных, как в xaml:

ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource=    {RelativeSource Self}}">

Затем вы можете сделать это:

private void Context_MenuClick(object sender, RoutedEventArgs e)
{
   var menuItem = e.Source as MenuItem;

   MyDoStuffFunction(menuItem.DataContext);
}

Контекст данных будет привязан к объекту, по которому щелкнули, чтобы открыть ContextMenu.

Я получил его из статьи codeproject по этой ссылке:

http://www.codeproject.com/Articles/162784/WPF-ContextMenu-Strikes-Again-DataContext-Not-Upda

2 голосов
/ 13 января 2010

Для RoutedEventArgs

  • RoutedEventArgs.source является ссылкой на объект, вызвавший событие
  • RoutedEventArgs.originalSource - это источник отчетов, который определяется с помощью простого тестирования на попадание до любой возможной корректировки источника родительским классом.

Итак. Отправитель должен быть ответом. Но это зависит от того, как элементы меню добавляются и связываются

Посмотрите эту коллекцию ответов и выберите метод, который будет работать в вашей ситуации!

2 голосов
/ 09 января 2010

menu = sender as MenuItem будет нулевым, если отправитель не является MenuItem или его производным классом. Впоследствии попытка разыменования меню будет взорвана.

Вполне вероятно, что ваш отправитель является Menu или ContextMenu или ToolStripMenuItem или какой-либо другой формой пункта меню, а не определенно является объектом MenuItem. Используйте точку останова отладчика, чтобы остановить здесь код и проверить объект-отправитель, чтобы точно узнать, какой это класс.

1 голос
/ 13 января 2010

У вас были две разные проблемы. Обе проблемы привели к одному и тому же исключению, но в остальном не были связаны:

Первая проблема

При первом подходе ваш код был верным и работал хорошо, за исключением проблемы здесь:

SCurrent.Status = MyCustControl.Status.Sixth;

Имя «Состояние» используется как статический элемент, так и как элемент экземпляра. Я думаю, что вы неправильно вставили код в свой вопрос.

Может также потребоваться добавить следующее после MenuItem menu = sender as MenuItem;, в зависимости от вашей конкретной ситуации:

  if(menu==null) return;

Вторая проблема

Во втором подходе вы использовали «отправитель» вместо «e.Source». Следующий код работает по желанию:

private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)    
{    
    IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)e.Source);
      // Changed "sender" to "e.Source" in the line above
    MyCustControl SCurrent = new MyCustControl();    
    SCurrent = (MuCustControl)parent;    
    string str = SCurrent.Name.ToString();// Error gone
    MessageBox.Show(str);    
}

Финальная нота

Примечание: нет никакой причины связывать CommandParameter для этого, если вы используете командный подход. Это значительно медленнее и занимает больше кода. e.Source всегда будет исходным объектом, поэтому нет необходимости использовать CommandParameter, поэтому используйте его вместо этого.

1 голос
/ 12 января 2010

Разве вы не должны проверять RoutedEventArgs.Source вместо sender?

0 голосов
/ 19 апреля 2018

В моем случае я смог использовать:

private void MenuItem_Click(object sender, RoutedEventArgs e)
{    
    MenuItem menuItem        = e.Source as MenuItem;
    ContextMenu parent       = menuItem.Parent as ContextMenu;
    ListBoxItem selectedItem = parent.PlacementTarget as ListBoxItem;
}
0 голосов
/ 30 июля 2015

Это работает для меня: -

XAML: -

<DataGrid.ContextMenu>
<ContextMenu x:Name="AddColumnsContextMenu" MenuItem.Click="AddColumnsContextMenu_Click">
</ContextMenu>

Для добавления пунктов меню: -

foreach (String s in columnNames)
{
var item = new MenuItem { IsCheckable = true, IsChecked = true ,Header=s};
AddColumnsContextMenu.Items.Add(item);
}

И здесьприходит слушатель: -

private void AddColumnsContextMenu_Click(object sender, RoutedEventArgs e)
{
    MenuItem mi = e.Source as MenuItem;
    string title = mi.Header.ToString();
    MessageBox.Show("Selected"+title);
}

Спасибо ...

...