2010-01-09 4 views
8

In WPF-Anwendung gibt es eine Grid mit einer Anzahl von Objekten (sie sind von einem benutzerdefinierten Steuerelement abgeleitet). Ich möchte einige Aktionen auf jedem von ihnen Kontextmenü ausführen:Wie verweist man mit der rechten Maustaste auf das Objekt im WPF-Kontextmenü? Click event handler?

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

Aber in der Event-Handler kann ich nicht wissen lassen, welche der Objekte war Rechtsklick:

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

Auf dieser Kommentarzeile Debugger sagt: Objektverweis nicht auf eine Instanz eines Objekts festgelegt.

Bitte helfen, was ist falsch in meinem Code?

Edited (hinzugefügt):

Ich habe versucht, das gleiche zu tun, Befehl Ansatz:

ich mit RoutedUICommand RequeryDataCommands Klasse deklariert und verwendet dann Window.CommandBindings

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

XAML von MenuItem sieht jetzt wie folgt aus:

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

Und Event-Handler wie folgt aussieht:

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

Aber Debugger zeigt die gleiche Laufzeitfehler: Objektverweis nicht auf eine Instanz eines Objekts festgelegt.

Was fehlt in meinen beiden Ansätzen?

Wie sollte ich Rechtsklick Objekt in WPF Kontext Menüelement klicken Ereignishandler?

+0

Ich habe versucht, mithilfe der Anwendung Command Ansatz als mehr WPF-ish, bekam aber den gleichen Fehler. Ich habe meine Frage bearbeitet und Schritte meines Befehlsansatzes hinzugefügt. Mein Verständnis davon, wie man die angeklickte Objektreferenz bekommt, fehlt in beiden Fällen etwas. – rem

Antwort

21

Notiz der Command

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

und es in der Prozedur verwenden, um herauszufinden, welche Grid ist 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 
       } 
      } 
     } 
    } 

Update:
Wenn Sie wollen, dass die menuitem-Handler die bekommen Grid Kinder anstelle des Grid selbst, verwenden Sie diesen Ansatz

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

Ersetzen Sie einfach die Textblocks mit dem, was Ihre benutzerdefinierten Objekttyp ist. Dann in den Event-Handler, ersetzen Grid g = cm.PlacementTarget as Grid mit TextBlock t = cm.PlacementTarget as TextBlock (oder was auch immer Ihrem individuellen Objekttyp).

+0

Danke! Das Problem ist, dass am Ende (ich meine dein Codebeispiel) wir "g" bekommen - der Verweis auf Grid (wo meine XAML-Deklaration im Kontext-Menü platziert ist), aber ich brauche den Verweis auf angeklickt Objekt, das innerhalb des Grid ist Das Grid Ich habe Hunderte von ähnlichen Objekten, von denen jedes mit der rechten Maustaste geklickt werden kann, um ein Kontextmenü zu erhalten. – rem

+0

Anstatt das Kontextmenü auf das Raster selbst zu legen, legen Sie es auf die untergeordneten Elemente des Rasters. – kenwarner

+0

Ja, es funktioniert. Vielen Dank! – rem

2

menu = sender as MenuItem ist null, wenn der Absender kein MenuItem oder eine davon abgeleitete Klasse ist. Anschließend wird versucht, das Menü zu dereferenzieren.

Es ist wahrscheinlich, dass Ihr Absender ein Menü oder ContextMenu oder ein ToolStripMenuItem oder eine andere Form eines Menüelements ist, und nicht speziell ein MenuItem-Objekt. Verwenden Sie einen Debugger-Haltepunkt, um den Code hier zu stoppen, und untersuchen Sie das Absenderobjekt, um genau zu sehen, um welche Klasse es sich handelt.

+0

Ich benutzte einen Debugger-Haltepunkt in dieser Zeile und es heißt "sender" Typ wie folgt: "sender {System.Windows.Controls.MenuItem Header: Change status Items.Count: 0} \t Objekt {System.Windows.Controls.MenuItem} " – rem

+0

Es ist möglich, dass Sie dieses Ereignis von mehreren Elementen erhalten, von denen einige MenuItems sind (wie das, das Sie im Debugger abgefangen haben) und einige von denen nicht (wie der, der deinen Unfall verursacht). Wenn Sie if (menu! = Null) um Ihren Verarbeitungscode verwenden, können Sie die Verarbeitung von Ereignissen aus Nicht-MenuItem-Objekten stoppen, was hilfreich sein kann. Oder es stürzt tatsächlich in der nächsten Zeile ab, und es ist menu.DataContext, das kein MyCustControl-Objekt ist. Gehen Sie einfach mit dem Debugger durch und schauen Sie sich jeden Wert an, bis Sie herausfinden, welcher Nullwert ist. –

+0

menu = sender als MenuItem funktioniert nicht, da MenuItem standardmäßig eine Klasse in System.Windows.Forms, ist, funktioniert aber wie Absender als System.Windows.Controls.MenuItem; – horiatu

1

Sollten Sie nicht RoutedEventArgs.Source anstelle von sender überprüfen?

2

Für RoutedEventArgs

  • RoutedEventArgs.source ist die Referenz auf das Objekt, das das Ereignis ausgelöst
  • RoutedEventArgs.originalSource wird die Berichtsquelle durch reine Treffertests bestimmt, bevor eine mögliche Quelle Anpassung durch eine Elternklasse.

So sollte .Sender die Antwort sein. Aber das hängt davon ab, wie die Menüpunkte hinzugefügt und gebunden werden

Sehen Sie diese answer collection und wählen Sie die Methode, die für Sie Situation funktioniert!

+0

Danke für den Link "Antwort Sammlung". Certianly viele Möglichkeiten, die Katze zu häuten. Sie denken, Microsoft hätte das jetzt sauberer gemacht! – user73993

1

Sie hatten zwei verschiedene Probleme. Beide Probleme resultierten in der gleichen Ausnahme, waren aber sonst in keinem Zusammenhang:

Erstes Problem

In Ihrem ersten Ansatz der Code richtig war und lief gut, außer für das Problem hier:

SCurrent.Status = MyCustControl.Status.Sixth; 

Die Name "Status" wird sowohl als statisches Mitglied als auch als Instanzmitglied verwendet. Ich denke, Sie haben den Code falsch in Ihre Frage eingefügt.

Es kann auch erforderlich sein, den folgenden nach MenuItem menu = sender as MenuItem;, je nach Ihrer genauen Situation hinzuzufügen:

if(menu==null) return; 

Zweites Problem verwendet, um Sie "Sender" statt „e

In Ihrem zweiten Ansatz .Quelle". Der folgende Code funktioniert wie gewünscht:

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

Abschließender Hinweis

Hinweis: Es gibt keinen Grund, überhaupt CommandParameter dafür zu binden, wenn Sie den kommandierenden Ansatz. Es ist deutlich langsamer und benötigt mehr Code. e.Source wird immer das Quellobjekt sein, so dass keine Notwendigkeit besteht, CommandParameter, zu verwenden, so dass stattdessen zu verwenden.

+0

Ray, bei der Verwendung des letzten Stück Code bekomme ich einen Debugger-Fehler: Kann nicht Objekt des Typs 'System.Windows.Controls.Grid' zu Typ 'MyCustControl' zu werfen. Es scheint, dass e.sourse nicht auf das angeklickte Objekt zeigt, sondern auf das Grid (wo meine XAML-Deklaration im Kontext-Menü platziert ist). – rem

+0

Interessant. Ich erinnere mich, dass ich Ihren Code tatsächlich in ein Projekt eingefügt und ausprobiert habe. Ich denke, ich habe das ContextMenu in eine Vorlage eingefügt, ohne darüber nachzudenken, anstatt es an das Grid anzuhängen, da es offensichtlich nicht möglich wäre, dass es wie gewünscht funktioniert, wenn das ContextMenu an das Grid angehängt wäre. –

5

den Datenkontext wie so in der XAML Durch die Bindung:

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

Sie können dies dann tun:

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

    MyDoStuffFunction(menuItem.DataContext); 
} 

Der Datenkontext wird an das Objekt gebunden sein, auf den geklickt wurde die öffnen Kontextmenü.

Ich habe es von einem Codeproject Artikel unter diesem Link:

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

0

Dies funktioniert für mich: -

XAML: -

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

Zum Hinzufügen Menüpunkte: -

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

Und hier kommt der Hörer: -

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

Dank ...