2010-01-09 13 views
8

En la aplicación WPF hay un Grid con varios objetos (se derivan de un control personalizado). Quiero realizar algunas acciones en cada una de ellas mediante el menú de contexto:Cómo hacer referencia al objeto con el botón derecho en el elemento de menú contextual de WPF, haga clic en el controlador de eventos.

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

Pero en el controlador de eventos que no me puedo saber cuál de los objetos era derecho de clics:

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; 
    } 

En esa línea comentada El depurador dice: Referencia de objeto no establecida en una instancia de un objeto.

Por favor, ayuda, ¿qué hay de malo en mi código?

Editado (añade):

Me trataron de hacer lo mismo, usando Comando enfoque:

I declaró una Clase DataCommands con RoutedUICommand Requery y luego se usa Window.CommandBindings

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

XAML de MenuItem ahora se ve así:

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

Y controlador de eventos se parece a:

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); 
    } 

Pero depurador muestra el mismo error en tiempo de ejecución: referencia a objeto no establecida como instancia de un objeto.

¿Qué falta en mis dos enfoques?

Cómo debo hacer referencia al objeto que ha hecho clic derecho en el elemento de menú contextual de WPF, haga clic en el controlador de eventos?

+0

He intentado utilizar el enfoque del sistema como más WPF-ish, pero tiene el mismo error. Edité mi pregunta y agregué los pasos de mi intento de aproximación al comando. Mi comprensión de cómo obtener la referencia del objeto cliqueado falta algo en ambos casos – rem

Respuesta

21

nota del 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> 

y utilizarlo en el controlador de averiguar lo que la rejilla es

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 
       } 
      } 
     } 
    } 

Actualización:
Si desea que el controlador de elemento de menú para llegar a la Los hijos de Grid en lugar de la Grid en sí, utilizan este enfoque

<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> 

Simplemente reemplace los TextBlocks con cualquier tipo de objeto personalizado. Luego, en el controlador de eventos, reemplace Grid g = cm.PlacementTarget as Grid con TextBlock t = cm.PlacementTarget as TextBlock (o cualquiera que sea su tipo de objeto personalizado).

+0

¡Gracias! El problema es que al final (me refiero a su ejemplo de código) obtenemos "g" -la referencia a Grid (donde se coloca mi declaración de menú contextual XAML), pero necesito la referencia al objeto cliqueado que está dentro de la cuadrícula (dentro The Grid Tengo cientos de objetos similares, cada uno de ellos se puede hacer clic con el botón derecho para obtener un menú contextual). – rem

+0

en lugar de poner el menú contextual en la cuadrícula, póngalo en los hijos de la Grilla. – kenwarner

+0

Sí, funciona. ¡Gracias! – rem

2

menu = sender as MenuItem será nula si el remitente no es un MenuItem o una clase derivada de la misma. Posteriormente, intentar desreferenciar el menú explotará.

Es probable que su remitente sea un Menú o ContextMenu o un ToolStripMenuItem o alguna otra forma de elemento de menú, en lugar de ser específicamente un objeto MenuItem. Utilice un punto de interrupción del depurador para detener el código aquí y examinar el objeto del remitente para ver exactamente qué clase es.

+0

Utilicé un punto de interrupción del depurador en esta línea y dice sobre "remitente" Escriba de la siguiente manera: "remitente {System.Windows.Controls.MenuItem Encabezado: cambie el estado Items.Count: 0} \t objeto {System.Windows.Controls.MenuItem} " – rem

+0

Es posible que obtenga ese evento de varios elementos, algunos de los cuales son MenuItems (como el que ha atrapado en el depurador) y algunos de los cuales no son (como el que está causando su bloqueo). Si usa if (menu! = Null) alrededor de su código de procesamiento, puede detenerlo tratando de procesar cualquier evento de objetos que no sean MenuItem, lo que puede ayudar. O se está bloqueando en la siguiente línea, y es menu.DataContext que no es un objeto MyCustControl. Solo haga un paso con el depurador y observe cada valor hasta que descubra cuál es nulo. –

+0

menu = sender como MenuItem no funciona porque de forma predeterminada MenuItem es una clase en System.Windows.Forms, pero funciona como sender como System.Windows.Controls.MenuItem; – horiatu

1

No deberías estar comprobando RoutedEventArgs.Source en lugar de sender?

2

Para RoutedEventArgs

  • RoutedEventArgs.source es la referencia al objeto que ha provocado el evento
  • RoutedEventArgs.originalSource es la fuente de notificación según lo determinado por las pruebas de golpe puro, antes de cualquier posible fuente ajuste por una clase de padres.

So .Sender debería ser la respuesta. Pero esto depende de cómo se agregan y enlazan los elementos de menú

¡Vea esto answer collection y elija el método que funcionará para su situación!

+0

Gracias por el enlace "recopilación de respuestas". Certianly muchas maneras de desollar al gato. ¡Crees que Microsoft habría hecho esto más limpio ahora! – user73993

1

Tuvo dos problemas diferentes. Ambos problemas dieron lugar a la misma excepción, pero eran de otra manera no relacionada:

Primer problema

En su primer acercamiento su código era correcta y corrieron bien, excepto por el problema aquí:

SCurrent.Status = MyCustControl.Status.Sixth; 

El el nombre "Estado" se usa como miembro estático y como miembro de instancia. Creo que cortó y pegó el código incorrectamente en su pregunta.

También puede ser necesario añadir lo siguiente después de MenuItem menu = sender as MenuItem;, dependiendo de su situación exacta:

if(menu==null) return; 

Segundo problema

En el segundo enfoque que utilizó "emisor" en lugar de "correo .Fuente". El siguiente código funciona como se desea:

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);  
} 

Nota final

Nota: No hay razón alguna para obligar CommandParameter para esto si se utiliza el enfoque de mando. Es significativamente más lento y requiere más código. e.Source siempre será el objeto de origen, por lo que no es necesario usar CommandParameter, así que úselo en su lugar.

+0

Ray, en caso de usar su último fragmento de código, recibo un error del depurador: no se puede convertir el objeto del tipo 'System.Windows.Controls.Grid' para escribir 'MyCustControl'. Parece que e.sourse apunta no al objeto cliqueado sino a la Grilla (donde se coloca mi declaración XAML del Menú de Contexto). – rem

+0

Interesante. Recuerdo que en realidad corté y pegué tu código en un proyecto y lo probé. Creo que puedo haber pegado el ContextMenu en una plantilla sin pensar en ello en lugar de adjuntarlo a la Grilla, ya que obviamente no sería posible que funcione como se desea si el ContextMenu estuviera unido a la Grilla. –

5

Al unirse el contexto de datos como tal en el XAML:

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

entonces usted puede hacer esto:

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

    MyDoStuffFunction(menuItem.DataContext); 
} 

El contexto de datos se enlaza con el objeto de que se ha hecho clic para abrir el Menú de contexto.

Me lo dio un artículo CodeProject en este enlace:

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

0

Esto funciona para mí: -

XAML: -

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

Para añadir elementos de menú: -

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

Y aquí viene el oyente: -

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

Gracias ...

Cuestiones relacionadas