# Mouse Events in einer sortierten JTable unterscheiden



## scar (20. Jun 2012)

Hallo zusammen,

ich benutze eine JTable mit einem DefaultSorter. Ohne das ich was implementieren muss werden die einzelnen Spalten durch einen einfachen Klick auf oder abwärts wunderbar sortiert. :toll:

Jetzt habe ich am TableHeader einen MouseListener hinzugefügt, der bei einem Doppelklick auf die Spaltengrenzen (es erscheint ein Doppelpfeil) die betreffende Spalte auf "optimierte Spaltenbreite" setzt. Funktioniert auch wunderbar, hat aber leider den Nebeneffekt, das von der betroffene Spalte dann auch die Sortierung geändert wird. 

Wie kann ich verhindern, das der Sorter bei einem Doppelklick reagiert? Geht das evtl. ohne das ich am Sorter was überschreiben muss? ???:L

Gruß
Arno


----------



## ssoul26 (20. Jun 2012)

Hast du irgendein Code-Beispiel?


----------



## scar (20. Jun 2012)

das ist alles was ich zur Zeit tue, wenn die Tabelle konstruiert wird und es funktioniert ja auch wunderbar mit Ausnahme des beschriebenen Nebeneffekts:


```
:
	JTable workspaceTable = new JTable();

	myWorkspaceTableModel = new WorkspaceTableModel(ParentContainer);
	workspaceTable.setModel(myWorkspaceTableModel);
	// Default Sorter aktivieren
	sorter = new TableRowSorter<WorkspaceTableModel>(myWorkspaceTableModel);
	workspaceTable.setRowSorter(sorter);
	// Listener für Spaltenbreiteoptimierung hinzufügen
	workspaceTable.getTableHeader().addMouseListener(new ColumnFitAdapter());
	// Mehrfachselektion möglich machen
	workspaceTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
	:
```

wobei der MouseListener folgendes tut:


```
:
public class ColumnFitAdapter extends MouseAdapter
{
	public void mouseClicked(MouseEvent e)
	{
		if ( e.getClickCount() == 2 )
		{
			// Stelle Spaltenbreite optimal ein
			:
		}
	}
	:
```

Was mir halt nicht klar ist, an welcher Stelle ich etwas tun muss, damit der erste Klick vom Doppelclick nicht vom Sorter ausgewertet wird. Geht der Mouseevent evtl. direkt paralell an alle Listener oder wird er eventuell hintereinander an die verschiedenen Listener geschickt und könnte man nach der Konsumierung den Event einfach unterdrücken?


----------



## ssoul26 (20. Jun 2012)

Das Problem ist, dass der EventHandler vorher nicht wissen kann, ob da jetzt noch ein Klick folgt oder nicht. Daher schiesst er das erste Event sofort ab.


----------



## ssoul26 (20. Jun 2012)

Was spricht gegen Rechtsklick? Links für sortieren, rechts für optimale Breite?


----------



## scar (20. Jun 2012)

eigentlich von mir aus nichts, nur ist es in allen bei uns bekannten anderen Anwendungen so, z.B. Excel, dass das so ist und der Benutzer es so gewöhnt ist. Deshalb hätte ich es schon sehr gerne, das es gleich funktioniert.
Was müsste ich den tun, um den 'einfach Click' vom 'Doppel Click' unterscheiden zu können? Könnte mir vorstellen, das das in anderen Kontexten auch schon mal gelöst wurde. Ich scheine aber nicht nach den richtigen Stichwörtern zu googeln, denn da find ich auch nichts.


----------



## ssoul26 (20. Jun 2012)

Du könntest bei Klick einen Thread starten, der paar Millisekunden wartet und dann eine aktion ausführt. Anschliessend nullst du den Thread. 

Ablauf 1 Klick:

1. Klick- > Thread startet und wartet erstmal ab
2. Nach ablauf der Zeit kein neuer Klick -> Aktion für einen Klick

Ablauf 2 Klick:

1. Klick- > Thread startet und wartet erstmal ab.
2. Klick -> Der Thread ist !=null also war da vorher einer -> Doppelklickaktion


----------



## KrokoDiehl (20. Jun 2012)

Schonmal mit 
	
	
	
	





```
event.consume()
```
 probiert? Vielleicht hilft das ja, wenn du es nach deiner Doppelklick-Behandlung durchführst.


----------



## scar (20. Jun 2012)

Hilft leider nicht, da der Doppelclick nach dem einfach Click kommt.

Aber was als Unterscheidungsmerkmal taugt, wäre wenn man feststellt, welchen Mauszeiger man beim Klicken hat, da in dem Fall, wenn man auf die Spaltengrenze zeigt aus dem Mauszeiger ein Doppelpfeil wird. Kriegt man diese Info aus dem Event heraus?


----------



## ssoul26 (21. Jun 2012)

Guter Ansatz 

```
public void mouseClicked(MouseEvent e) {

        if(e.getSource()instanceof JTableHeader){
           JTableHeader header= (JTableHeader) e.getSource();
           if(header.getCursor().getType()!= Cursor.DEFAULT_CURSOR){
              System.out.println("Resize?");
              
           }
           
        }

   }
```


----------



## scar (21. Jun 2012)

Obwohl der Event meines Erachtens in den relevanten Fällen konsumiert wird, springt der 'sorter' immer noch an. Kann es sein das der Sorter den Event zuerst bekommt?
Nach meinem Verständnis sollte der Event nur in meinem registrierten Listener erst mal bearbeitet werden. Oder muss man noch was anderes außer einem 'consum()' machen, damit er nicht weiterbenutzt werden kann?

Hier die Ausgabe, wenn ich zuerst Doppelclick auf die Spalte und danach auf die Spaltengrenze mache:
  :
No action at 'left button (clicks: 1 / cursortype: Standardcursor)'
No action at 'left button (clicks: 2 / cursortype: Standardcursor)'
Sorter should not consume this event! 'left button (clicks: 1 / cursortype: Skaliercursor nach rechts)'
No action at 'left button (clicks: 1 / cursortype: Skaliercursor nach rechts)'
Sorter should not consume this event! 'left button (clicks: 2 / cursortype: Skaliercursor nach rechts)'
Consuming column optimization event now 'left button (clicks: 2 / cursortype: Skaliercursor nach rechts)'...
  :

Noch eine Idee?

Hier ist noch die volständige Implementation des Listeners:

```
package util.tablesorting;

import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JTable;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;

/**
 * Ermöglicht es einer Tabelle durch klicken zwischen zwei Spalten die Größe automatisch
 * einzustellen.
 */
public class ColumnFitAdapter extends MouseAdapter
{
	public void mouseClicked(MouseEvent mouseEvt)
	{
		// Ist Listener an falschem Objekttyp registriert?
		if ( !(mouseEvt.getSource() instanceof JTableHeader) )
		{
			System.err.println("Listener registered at wrong object type '" + mouseEvt.getSource().getClass().getName() + "' instead of expected type 'JTableHeader'");
			return;
		}

		// Nur für Debugzwecke
		String whichKey;
		switch ( mouseEvt.getButton() )
		{
			case MouseEvent.NOBUTTON: whichKey = "no button";     break;
			case MouseEvent.BUTTON1:  whichKey = "left button";   break;
			case MouseEvent.BUTTON2:  whichKey = "middle button"; break;
			case MouseEvent.BUTTON3:  whichKey = "right button";  break;
			default:  				  whichKey = new Integer(mouseEvt.getButton()).toString() + " button";   break;
		}
		whichKey += " (clicks: " + mouseEvt.getClickCount();
		
		JTableHeader header = (JTableHeader)mouseEvt.getSource();
		whichKey += " / cursortype: " + header.getCursor().getName() + ")";

		// Spaltenoptimierung erfolgt nur bei Doppelklick mit Standardmaustaste (BUTTON1) und
		// wenn der Cursor ungleich Defaultcursor ist
		boolean optimizeColumn = (mouseEvt.getButton() == MouseEvent.BUTTON1) && (header.getCursor().getType() != Cursor.DEFAULT_CURSOR);

		// auch einfach Klick mit linker Maustaste consumieren
		if ( optimizeColumn )
		{
			// wenn der Cursor kein DefaultCursor ist auf jeden
			// Fall konsumieren durch Sorter verhindern
			System.out.println("Sorter should not consume this event! '" + whichKey + "'");
			mouseEvt.consume();
		}
		
		// Nur bei Doppelclick
		if ( optimizeColumn && (mouseEvt.getClickCount() == 2) )
		{
			System.out.println("Consuming column optimization event now '" + whichKey + "'...");
			TableColumn tableColumn = getResizingColumn(header, mouseEvt.getPoint());
			if ( tableColumn == null )
			{
				return;
			}
			int col = header.getColumnModel().getColumnIndex(tableColumn.getIdentifier());
			JTable table = header.getTable();
			int rowCount = table.getRowCount();
			int width = (int)header.getDefaultRenderer().getTableCellRendererComponent(table, tableColumn.getIdentifier(), false, false, -1, col)
				.getPreferredSize().getWidth();
			// Suche die breiteste Zelle dieser Spalte
			for ( int row = 0; row < rowCount; row++ )
			{
				int preferedWidth = (int)table.getCellRenderer(row, col)
					.getTableCellRendererComponent(table, table.getValueAt(row, col), false, false, row, col).getPreferredSize().getWidth();
				width = Math.max(width, preferedWidth);
			}
			// Setze die Spalte auf den ermittelten Wert
			header.setResizingColumn(tableColumn); // this line is very important
			tableColumn.setWidth(width + table.getIntercellSpacing().width);
		} else
		{
			System.out.println("No action at '" + whichKey + "'");
		}
	}

	private TableColumn getResizingColumn(JTableHeader header, Point p)
	{
		return getResizingColumn(header, p, header.columnAtPoint(p));
	}

	private TableColumn getResizingColumn(JTableHeader header, Point p, int column)
	{
		if ( column == -1 )
		{
			return null;
		}
		Rectangle r = header.getHeaderRect(column);
		r.grow(-3, 0);
		if ( r.contains(p) )
			return null;
		int midPoint = r.x + r.width / 2;
		int columnIndex;
		if ( header.getComponentOrientation().isLeftToRight() )
			columnIndex = (p.x < midPoint) ? column - 1 : column;
		else
			columnIndex = (p.x < midPoint) ? column : column - 1;
		if ( columnIndex == -1 )
			return null;
		return header.getColumnModel().getColumn(columnIndex);
	}
}
```


----------



## ssoul26 (21. Jun 2012)

Hast du schonmal versucht, den Sorter zu umschreiben? Der hat sich ein eigenes Handling, da hängst du dich dann ran.


----------



## ssoul26 (21. Jun 2012)

Der TableSorter hat eine private Klasse TableModelListener, da gibt es folgende private Klasse "private class MouseHandler extends MouseAdapter " dort umschreibst du die Funktion "public void mouseClicked(MouseEvent e)". Daher wird dein Event nicht richtig behandelt.


----------



## scar (21. Jun 2012)

Kannst Du ein Beispiel skizzieren, wie ich genau diese Methode in einer inneren privaten Klasse überschreiben kann? Bin gerade etwas überfordert und sehe vor lauter Klassen, Modellen, Tabellen und Views den Wald nicht mehr ???:L :bahnhof:


----------



## bERt0r (22. Jun 2012)

Ich hab dir was gebastelt, ist zwar ein bisschen dirty macht aber was du willst. Kann jetzt aber nicht garantieren ob das auch mit allen look and feels funktioniert.

```
import java.awt.BorderLayout;

public class TableRowSorterDemo extends JFrame
{
	
	private JPanel contentPane;
	private JScrollPane scrollPane;
	private JTable table;
	private MouseListener[] headerListeners;
	
	/**
	 * Launch the application.
	 */
	public static void main(String[] args)
	{
		EventQueue.invokeLater(new Runnable()
			{
				public void run()
				{
					try
					{
						TableRowSorterDemo frame = new TableRowSorterDemo();
						frame.setVisible(true);
					} catch (Exception e)
					{
						e.printStackTrace();
					}
				}
			});
	}
	
	/**
	 * Create the frame.
	 */
	public TableRowSorterDemo()
	{
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 450, 300);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		contentPane.setLayout(new BorderLayout(0, 0));
		setContentPane(contentPane);
		
		scrollPane = new JScrollPane();
		contentPane.add(scrollPane, BorderLayout.CENTER);
		
		table = new JTable();
		table.setModel(new DefaultTableModel(new Object[][] { { "Sepp", "11", "111" }, { "Fritz", "10", "120" },
				{ "Hansi", "5", "100" }, { null, null, null }, { null, null, null }, }, new String[] { "Name", "Alter",
				"Gr\u00F6\u00DFe" }));
		scrollPane.setViewportView(table);
		
		table.setAutoCreateRowSorter(true);
		
		headerListeners = table.getTableHeader().getMouseListeners();
		
		for (MouseListener l : headerListeners)
		{
			table.getTableHeader().removeMouseListener(l);
		}
		
		table.getTableHeader().addMouseListener(new MouseListener()
			{
				@Override
				public void mouseClicked(MouseEvent e)
				{
					JTableHeader header = (JTableHeader) e.getSource();
					if (header.getCursor().getType()!=Cursor.E_RESIZE_CURSOR)
					{
						for (MouseListener l : headerListeners)
						{
							l.mouseClicked(e);
						}
					} else
					{
						System.out.println("Resize Columns " + e.getSource());
						// resize Columns
					}
				}
				
				@Override
				public void mousePressed(MouseEvent e)
				{
					for (MouseListener l : headerListeners)
					{
						l.mousePressed(e);
					}
				}
				
				@Override
				public void mouseReleased(MouseEvent e)
				{
					for (MouseListener l : headerListeners)
					{
						l.mouseReleased(e);
					}
				}
				
				@Override
				public void mouseEntered(MouseEvent e)
				{
					for (MouseListener l : headerListeners)
					{
						l.mouseEntered(e);
					}
				}
				
				@Override
				public void mouseExited(MouseEvent e)
				{
					for (MouseListener l : headerListeners)
					{
						l.mouseExited(e);
					}
				}
				
			});
		
	}
	
}
```


----------



## scar (22. Jun 2012)

Vielen Dank für das Beispiel, ich werd es einbauen, werde aber wahrscheinlich erst nächste Woche dazu kommen. Sag aber dann Bescheid, ob's funktioniert hat.


----------



## scar (22. Jun 2012)

Ja, funktioniert!!! :toll: 
War dann ja doch nicht so schlimm mit Überschreiben irgendwelcher internen Methoden im TableRowSorter. 

Vielen Dank noch mal für euer Engagement und euer Ineresse.


----------



## ssoul26 (22. Jun 2012)

Schreib doch mal bitte, was du konkret gemacht hast Damit vllt. später andere schneller auf eine Lösung kommen.


----------



## scar (25. Jun 2012)

Ich hab das gemacht, was bERt0r vorgeschlagen hat, ich hab seinen Code von Zeile 55 bis 117 ans Ende meiner Initialisierung gestellt, nachdem sichergestellt ist, das alle gewünschten TableHeaderListener registriert sind. Die Liste der registrierten Listener merke ich mir dann in einer eigenen Variablen und lösche die Registrierungseinträge an dem betroffenen TableHeader. Dann trage ich nur noch einen Listener ein, der den Event und den Mauszeiger überprüft und der ruft dann in dem einen Fall nur den einen gewünschten Listener auf, in jedem anderen Fall bleibt das Verhalten unverändert.

Hat man noch mehr Listener, die etwas mit dem selben Event machen müssen, muss man die Logik entsprechend anpassen. Für meine Zwecke reicht die Vorgehensweise jedoch voll und ganz.

So sieht es dann mit meinem Code ab Zeile 68 aus:

```
if (header.getCursor().getType() != Cursor.E_RESIZE_CURSOR)
                    {
                        for (MouseListener l : headerListeners)
                        {
                            l.mouseClicked(e);
                        }
                    } else
                    {
                        System.out.println("Columns best fit " + e.getSource());
                        for (MouseListener l : headerListeners)
                        {
                            if ( l instanceof ColumnFitAdapter )
                            {
                                l.mouseClicked(e);
                            }
                        }
                    }
```

Damit kann ich mir dann in diesem Code auch wieder alle Überprüfungen sparen.


----------

