TreeViewer Listener

darie17

Grünschnabel
Hallo zusammen,

bin ganz neu in der SWT-Programmierung, habe bis jetzt fast 4 Jahre Java programmiert (meistens Swing, kein Eclipse, nur Netbeans). So, jetzt muss ich mich in Eclipse einarbeiten und bin gerade bei der Entwicklung eines Plugins.

Die ganze Sache ist in einem MultiPageEditor strukturiert und in eine der "Seiten" habe ich einen TreeViewer, der mir irgend einen Baum aus einer Eingabe darstellt. Alles hat soweit gut geklappt. Jetzt möchte ich auch Elemente aus dem Baum per Rechtsklick löschen (also Rechtsklick -> Delete Element). Das habe ich nicht geschafft. Ich habe mir folgende Seiten angeguckt: http://wiki.eclipse.org/index.php/JFaceSnippets und http://www.developer.com/java/other/article.php/10936_3565006_3/The-JFace-of-Eclipse.htm aber keine von diesen hat mir konkret geholfen, ich finde die Dokumentation zu einer so einfachen Sache ganz schlecht! Tja, ich komme einfach nicht weiter.

In der klassischen Swing-Programmierung ging alles ganz einfach - auf einem Tree setzt man ein Model, den man ganz einfach bearbeiten kann. Durch kurze Listener Klassen, kann man verschiedene Events abfangen und dann die entsprechende Aktionen durchführen. Bei SWT/JFace scheint alles unnötig kompliziert zu sein und, noch schlechter, ist nicht mal gut dokumentiert.

Könnte mir bitte jemand helfen, die Listeners auf einem TreeViewer endlich zu verstehen? Wie würde ich dann einen Kontextmenü auf meinem TreeViewer machen, so dass ich dann auch verschiedene Kontextmenüs zeigen kann? z.B. für den Fall, dass ich 3 Arten von Knoten im Baum habe - für jeden soll das entsprechende Kontextmenü mit den Aktionen für den selektierten Knoten erscheinen.

Achso, ja, was ich auch nicht verstanden habe, wie kann ich denn überprüfen, mit welcher Maustaste ich auf einem Element des Baums geklickt habe? In Swing gab es so was wie if(e.getButton() == MouseEvent.BUTTON3) für Rechtsklick.

Vielen Dank im Voraus.
 
Hallo,

um ein Kontext-Menü an einen Baum anzuhängen, brauchst du keinen Listener, dafür gibt es elegantere Ansätze. Du könntest z.B. mit Hilfe von einem MenuManager ein Kontext-Menu erzeugen, das jedes mal, wenn der Baum geklickt wird, neu gebaut wird. So kannst du die Menü-Einträge abhängig von der aktuellen Selektion definieren.

Schaue dir mal mein Code-Beispiel an. Ich habe einfachheitshalber ein Tree statt TreeViewer verwendet. Du müsstest dann den Tree erstmal holen (Tree tree = treeViewer.getTree()).

Java:
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

public class TreeContextMenuTest
{
  public static void main( String[] args )
  {
    Display display = new Display();
    Shell shell = new Shell( display );
    shell.setSize( 400, 300 );
    shell.setLayout( new GridLayout() );

    final Tree tree = new Tree( shell, SWT.SINGLE );
    TreeItem obstItem = new TreeItem( tree, SWT.NONE );
    obstItem.setText( "Obst" );
    new TreeItem( obstItem, SWT.NONE ).setText( "Apfel" );
    new TreeItem( obstItem, SWT.NONE ).setText( "Birne" );
    new TreeItem( obstItem, SWT.NONE ).setText( "Orange" );
    TreeItem gemueseItem = new TreeItem( tree, SWT.NONE );
    gemueseItem.setText( "Gemüse" );
    new TreeItem( gemueseItem, SWT.NONE ).setText( "Gurke" );
    new TreeItem( gemueseItem, SWT.NONE ).setText( "Kartoffel" );
    new TreeItem( gemueseItem, SWT.NONE ).setText( "Tomate" );
    tree.setSelection( obstItem.getItem( 0 ) );
    tree.setSelection( gemueseItem.getItem( 0 ) );
    GridData gridData = new GridData( SWT.FILL, SWT.FILL, true, true );
    tree.setLayoutData( gridData );

    MenuManager menuManager = new MenuManager( "#PopupMenu" );
    menuManager.setRemoveAllWhenShown( true );
    menuManager.addMenuListener( new IMenuListener()
    {
      public void menuAboutToShow( IMenuManager manager )
      {
        final TreeItem[] selectedItems = tree.getSelection();
        if ( selectedItems != null && selectedItems.length > 0 )
        {
          Action action = new Action()
          {
            @Override
            public void run()
            {
              System.out.println( "delete tree item '" + selectedItems[ 0 ].getText() + "'" );
            }
          };
          action.setText( "Delete '" + selectedItems[ 0 ].getText() + "'" );
          manager.add( action );
        }
      }
    } );

    Menu contextMenu = menuManager.createContextMenu( tree );
    tree.setMenu( contextMenu );

    shell.open();
    while ( !shell.isDisposed() )
    {
      if ( !display.readAndDispatch() )
      {
        display.sleep();
      }
    }
    display.dispose();
  }
}


Btw. ob die rechte Maustaste gedrückt wurde, kannst du in SWT genauso gut abfragen:

Java:
    control.addListener( SWT.MouseDown, new Listener()
    {
      public void handleEvent( Event event )
      {
        if ( event.button == 3 ) // rechte maustaste
        {
        }
      }
    } );

Grüße
Vincent
 
Hi,

vielen Dank für deine Antwort.

Bis zuletzt habe ich es irgendwie hingekriegt, undzwar recht sauber. Ich habe jetzt eine Klasse, die die Schnittstelle Listener implementiert und somit für die Methode handleEvent(Event e) einen Verhalten definiert. Funktioniert ganz gut, meiner Meinung nach sauber getrennt. Das mit den Listeners als interne anonyme Klasse finde ich unschön, insbesondere bei Projekten wo die Anzahl der Zeilen Code groß ist. Das ist auch bei mir der Fall.

Gut, also es läuft jetzt. Ich habe aber ein anderes Problem jetzt - wenn ich mal auf einem Element aus dem Tree mit der rechten Maustaste klicke, erscheint, wie erwartet, das Kontextmenü. Wenn ich aber *danach* irgendwo anders mit der rechten Maustaste klicke, wo sich kein Tree-Element befindet (z.B. unter dem Baum), dann erscheint wieder dasselbe Kontextmenü. Das vorige Element ist zwar selektiert, aber ich verstehe nicht warum das Menü immer noch erzeugt wird.

Außerdem, wenn ich mit der linken Maustaste auf ein Element klicke und dann mit der rechten Maustaste unter dem Baum klicke, erscheint mir immer noch das Kontextmenü für das zuletzt mit der rechten Maustaste angeklicktes Element aus dem Tree. Komisches Verhalte. Habe alles überprüft - durch System.outs usw. Die Methode, wo ich das Kontextmenü erzeuge wird gar nicht aufgerufen, aber das Menü erscheint trotzdem. Wieso?

Ich habe mir gedacht, dass ich vielleicht die Methode removeAll() auf dem MenuManager aufrufen muss, um alle MenuItems bei jedem Klick erst wegzuschmeißen. Hat auch nicht geklappt. Ich ahne aber dass diese auch die Lösung ist, nur dass ich es nicht richtig anwende.

Um die Sachen klar zu machen, bei jedem Klick (sei es linker oder rechter Klick), mache ich folgendes:

Code:
@Override
	public void handleEvent(Event event) {
		
		Point point = new Point (event.x, event.y);
		TreeItem clickedItem = tree.getItem (point);
		if (clickedItem != null) 
		{
			// Rechtsklick
			if(event.button == MouseEvent.BUTTON3)
			{
				System.out.println("Right click (context menu) on element " + clickedItem.getText());
				showContextMenu(tree, clickedItem);
			}
			else
			{
				System.out.println("event.button != BUTTON3");
			}
		}
		else
		{
			System.out.println("selected item = null");
			this.mgr.removeAll();
		}
	}

Irgendwelche Vorschläge?

Vielen Dank!
 
Hallo,

zunächst einmal, ob die inneren anonymen Klassen unschön sind, ist sicherlich Geschmackssache. Jedenfalls bringen sie einen entscheidenden Vorteil: man kann aus einer inneren Klasse heraus auf die Methoden und Variablen (auch private und protected) der Hauptklasse zugreifen, was bei Listeners oft erforderlich ist. Daher sind die inneren Klassen an der Stelle durchaus angebracht und sinnvoll.

Ein Kontext-Menü wird immer zu einem Baum-Element angezeigt, das grade selektiert ist und zwar unabhängig davon, ob man mit dem Mauszeiger exakt das Element getroffen hat oder auch daneben geklickt hat. Das ist ein Standardverhalten, zumindest bei Eclipse-Bäumen (versuche es einfach in dem Package-Explorer in der Java-Perspektive).

Dass bei dir ein Kontext-Menü zu einem falschen Element angezeigt wird, liegt sicherlich an deiner Listener-Implementierung. In meinem Code-Beispiel von vorhin funktioniert es so, wie man es erwartet. Ich würde das Kontext-Menu Element nicht nach dem Event-Point ermitteln, sondern das aktuell selektierte Baum-Element nehmen.

Btw. mgr.removeAll(); in deinem Listener wird überflüssig, wenn du bei der Initialisierung des MenuManagers die Methode setRemoveAllWhenShown( true ); aufrufst.

Ich würde versuchen, die SWT und AWT Klassen nicht ohne Grund miteinander zu mischen. Ich sehe, du benutzt java.awt.event.MouseEvent. Es ist ein Zufall, dass das SWT-MouseEvent den selben Wert für die Konstante BUTTON3 hat. Sowas kann zu Fehlern führen, die dann viel Aufwand kosten können, um die Ursachen zu lokalisieren.

Grüße
Vincent
 
Hi Vincentius,

noch mal danke für deine Antwort. Ich habe es gerade hingekriegt, es funktioniert jetzt super. Hier einiges über was du in deinem letzten Beitrag geschrieben hast:

1. du hast Recht, die Methode mgr.removeAll(); ist überflüssig - habe ich weggelöscht
2. Was die Mischung von SWT und AWT betrifft, da hast du auch Recht - ich habe aus Versehen was falsches importiert (aus den Vorschlägen von dem Eclipse-Autocomplete) - habe ich gefixt

Das Problem lag bei mir an mehreren Stellen. Ich wusste was los ist, konnte es aber irgendwie nicht lösen. Es ging um die falsche Implementierung der Listeners (wie du auch gesagt hast). Undzwar musste ich an bestimmten Stellen den Listener von dem MenuManager durch den Aufruf this.mgr.removeMenuListener(listener); entfernen. Das hat eine Weile gedauert, aber Hauptsache ist, es funktioniert jetzt.

Wenn es weitere Leute gibt, die dasselbe Problem haben, kann ich euch gerne Kodebeispiele geben.

Viele Grüße,

darie17
 
Zurück