Events unterdrücken

Status
Nicht offen für weitere Antworten.

Tobias

Top Contributor
Hallo,

ich möchte folgendes tun:

In meinem aktuellen Projekt gibt es einen Dialog, der dazu dient, Eingabehilfen für Touchscreens anzubieten. Dieser Dialog wird durch Anklicken einer Komponente mit der Maus (bzw eigentlich durch "Anpatschen" mit dem Finger) aktiviert. Dummerweise löst das Anklicken zumeist auch die eigentlich Aktion der Komponente aus (Buttons werden gedrückt, Listen klappen aus etc.). Dies soll unterdrückt werden.

Die einzige mir bislang eingefallene Methode ist die GlassPane des Fensters zu modifizieren, was mir jedoch nicht besonders gut gefällt, weil es a) viel Aufwand bedeutet und b) unnötig und bei Weiterentwicklungen fehlerträchtig in das Verhalten des eigentlich nicht mir unterstellten JFrames eingreift (die GlassPane müßte ja dauerhaft installiert werden und nicht nur solange der Dialog geöffnet ist). Am liebsten wäre mir die Installation einer Art GlassPane nur über den einzelnen Formular-Komponenten, die Eingabehilfe erhalten sollen, aber das scheint nicht so einfach machbar zu sein.

Aber vielleicht hat ja einer von euch einen anderen Vorschlag!?!

mpG
Tobias
 
Hm... nur damit das klarer wird: Ist dieser "Dialog" sowas wie ein "Aufgebohrter Tooltip, der durch Klicken aktiviert wird"??? ???:L
 
Nein, der Dialog ist ein echter Dialog abgeleitet von JDialog.

Es sind beim Kunden einige Touchscreens im Einsatz (hauptsächlich Point of Sale, also Kassencomputer). Die Dinger sind oft ein bißchen älter und nicht unbedingt zielgenau. Das Auswählen eines Items einer JList würde sich also mitunter schwierig gestalten (vor allem, wenn es schnell gehen muss). Deshalb soll, wenn die JList angeklickt wird, der Dialog aufgehen und die einzelnen Items der JList in Form ausreichend großer Buttons darstellen, die dann einfach angeklickt werden können.

Die Originalkomponente darf aber auf das Anpatschen nicht auch direkt reagieren, weshalb ich den Eingabeevent unterdrücken will.

Jetzt klarer *hoff*?

mpG
Tobias
 
Hmja, vom Prinzip her war das das, was ich dachte - es war nur nicht klar, unter welchen Bedinungen der Dialog aufgeht, (ich nehme an, dass man "mausKlick" und "fingerDatscher" als "gleich" ansehen kann...) und was man mit dem Dialog dann macht.

Die Bedenken gegen die GlassPane sind verständlich. Man verdeckt ja den kompletten Frame, und es ist (glaubich) nicht möglich, das Verhalten des Frames und seines Inhaltes vollkommen unbeeinflusst zu lassen. Hab trotzdem mal testweise die GlassPaneDemo von
http://java.sun.com/docs/books/tuto...DemoProject/src/components/GlassPaneDemo.java
ein bißchen aufgebohrt, aber vmtl. ist es nicht 100% das was du willst....

Code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.util.List;
import java.util.*;


class InputHelpProvider implements MouseListener, MouseMotionListener
{
    private List<Component> components = new ArrayList<Component>();
    private JFrame frame;
    private JPanel glassPane;

    public InputHelpProvider(JFrame frame)
    {
        this.frame = frame;

        glassPane = new JPanel();
        glassPane.setOpaque(false);
        glassPane.addMouseListener(this);
        glassPane.addMouseMotionListener(this);
        frame.setGlassPane(glassPane);
        glassPane.setVisible(true);
    }

    public void addComponent(Component component)
    {
        components.add(component);
    }

    public void removeComponent(Component component)
    {
        components.add(component);
    }

    public void mouseMoved(MouseEvent e)
    {
        redispatchMouseEvent(e);
    }

    public void mouseDragged(MouseEvent e)
    {
        redispatchMouseEvent(e);
    }

    public void mouseClicked(MouseEvent e)
    {
        redispatchMouseEvent(e);
    }

    public void mouseEntered(MouseEvent e)
    {
        redispatchMouseEvent(e);
    }

    public void mouseExited(MouseEvent e)
    {
        redispatchMouseEvent(e);
    }

    public void mousePressed(MouseEvent e)
    {
        redispatchMouseEvent(e);
    }

    public void mouseReleased(MouseEvent e)
    {
        redispatchMouseEvent(e);
    }

    private void redispatchMouseEvent(MouseEvent e)
    {
        Point glassPanePoint = e.getPoint();
        Container container = frame.getContentPane();
        Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, container);
        if (containerPoint.y >= 0)
        {
            Component component =
                SwingUtilities.getDeepestComponentAt(container, containerPoint.x, containerPoint.y);
            if (component != null)
            {
                if (components.contains(component))
                {
                    if (e.getID() == MouseEvent.MOUSE_CLICKED)
                    {
                        JOptionPane optionPane = new JOptionPane("Does it work?",
                            JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_OPTION);
                        JDialog dialog = optionPane.createDialog(frame, "Test");
                        dialog.setResizable(true);
                        Rectangle bounds = component.getBounds();
                        bounds = SwingUtilities.convertRectangle(frame.getContentPane(), bounds, glassPane);
                        bounds.x += frame.getX() + frame.getInsets().left;
                        bounds.y += frame.getY() + frame.getInsets().top;
                        dialog.setBounds(bounds);
                        dialog.setVisible(true);
                    }
                }
                else
                {
                    Point componentPoint =
                        SwingUtilities.convertPoint(glassPane, glassPanePoint, component);
                    component.dispatchEvent(
                        new MouseEvent(component, e.getID(), e.getWhen(), e.getModifiers(),
                                       componentPoint.x, componentPoint.y, e.getClickCount(),
                                       e.isPopupTrigger()));
                }
            }
        }
        frame.repaint();
    }

}




public class GlassPaneTest
{
    public static void main(String[] args)
    {
        new GlassPaneTest();
    }

    InputHelpProvider inputHelpProvider;

    public GlassPaneTest()
    {
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);
        frame.getContentPane().setLayout(null);

        JButton button = new JButton("No input helper");
        frame.getContentPane().add(button);
        button.setBounds(100,200,200,100);

        JTree tree = new JTree();
        frame.getContentPane().add(tree);
        tree.setBounds(400,50,200,200);

        String[] data = {"This", "has", "an", "input", "helper"};
        JList list = new JList(data);
        frame.getContentPane().add(list);
        list.setBounds(500,300,200,200);

        inputHelpProvider = new InputHelpProvider(frame);
        inputHelpProvider.addComponent(list);


        frame.setVisible(true);
    }

}


Wenn du vollkommene Kontrolle über diese Components hast, dann könnte man theoretisch(!) vielleicht (!) auch eine "Leere" JComponent basteln, die nur einen MouseListener zum Öffnen des Dialogs enthält, und ansonsten ein verstecktes Delegate, das "fake-mäßig" gezeichnet wird... grob sowas wie
Code:
class FakeComponent<Content extends Component> implements MouseListener
{
    Content content;
    
    void mouseClicked(...) { openDialog(); }

    public void paintComponent(Graphics g)
    {
        content.paintComponent(g);
    }
    ...
}
Hm. Das wäre aber auch Hack³, und aufwändig, weil man ja etliche Methoden (von getPreferredSize bis ... ???) "durchrouten" müßte... sorry, aber sonst hab ich jetzt spontan keine Idee mehr...
 
Ok,

ich habe jetzt mal folgendes Progrämmchen zu Testzwecken zusammengehackt. Funktioniert ganz gut. Es verfolgt den von mir ursprünglich angenommenen Ansatz, eine GlassPane über den einzelnen Komponenten zu installieren (über die habe ich ein wenig Macht). Das ganze ist ein simples, lauffähiges Beispiel.

Code:
// Uses InputEventConsumer
public class FAContainerFrame extends JFrame implements ActionListener {

	class FAContainer extends JPanel {

		private JRootPane root;

		public FAContainer() {
			super();
			setLayout(new BorderLayout());

			root = new JRootPane();
			super.addImpl(root, BorderLayout.CENTER, 0);
		}

		@Override
		protected void addImpl(Component comp, Object constraints, int index) {
			root.getContentPane().add(comp, constraints, index);
		}

		public void setGlassPane(Component gp) {
			root.setGlassPane(gp);
		}

		public Component getGlassPane() {
			return root.getGlassPane();
		}

	}

	private FAContainer faContainer;

	private InputEventConsumer iec = new InputEventConsumer();

	private JButton lock;

	private JButton unlock;

	public FAContainerFrame() {
		super("FAContainerFrame");
		setSize(400, 400);
		setLayout(new BorderLayout());

		faContainer = new FAContainer();
		faContainer.add(makeButton("In FAContainer"));
		lock = makeButton("Sperren");
		unlock = makeButton("Entsperren");
		unlock.setEnabled(false);

		add(faContainer, BorderLayout.CENTER);
		add(lock, BorderLayout.EAST);
		add(unlock, BorderLayout.WEST);

		setVisible(true);
	}

	private JButton makeButton(String text) {
		JButton butt = new JButton(text);
		butt.setActionCommand(text);
		butt.addActionListener(this);

		return butt;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new FAContainerFrame();
	}

	/**
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	public void actionPerformed(ActionEvent e) {
		String ac = e.getActionCommand();

		if (ac.equals("In FAContainer")) {
			JOptionPane.showMessageDialog(this, "In FAContainer gedrueckt!");
		} else if (ac.equals("Sperren")) {
			faContainer.getGlassPane().addMouseListener(iec);
			faContainer.getGlassPane().addKeyListener(iec);
			faContainer.getGlassPane().setVisible(true);
			faContainer.setCursor(new Cursor(Cursor.WAIT_CURSOR));

			lock.setEnabled(false);
			unlock.setEnabled(true);
		} else if (ac.equals("Entsperren")) {
			faContainer.getGlassPane().removeMouseListener(iec);
			faContainer.getGlassPane().removeKeyListener(iec);
			faContainer.getGlassPane().setVisible(false);
			faContainer.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));

			unlock.setEnabled(false);
			lock.setEnabled(true);
		}
	}

}

Und hier noch die Klasse InputEventConsumer aus meiner Commons-Bibliothek:

Code:
class InputEventConsumer implements KeyListener, MouseListener {

	/**
	 * Defines whether the computer should beep if the component
	 * InputEventConsumer is attached to receives mouse- or keyboard-input.
	 */
	// FIXME Must be configurable
	private boolean beep = true;

	/**
	 * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
	 */
	public void keyPressed(KeyEvent e) {
		consume(true, e);
	}

	/**
	 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
	 */
	public void keyReleased(KeyEvent e) {
		consume(false, e);
	}

	/**
	 * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
	 */
	public void keyTyped(KeyEvent e) {
		consume(false, e);
	}

	/**
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	public void mouseClicked(MouseEvent e) {
		consume(true, e);
	}

	/**
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	public void mouseEntered(MouseEvent e) {
		consume(false, e);
	}

	/**
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	public void mouseExited(MouseEvent e) {
		consume(false, e);
	}

	/**
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	public void mousePressed(MouseEvent e) {
		consume(false, e);
	}

	/**
	 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	public void mouseReleased(MouseEvent e) {
		consume(false, e);
	}

	/**
	 * Consumes the overgiven InputEvent.
	 * 
	 * @param beep
	 *            if this parameter and the instance-variable <code>beep</code>
	 *            are true, a beep will sound.
	 * @param e
	 *            the InputEvent to consume.
	 */
	private void consume(boolean beep, InputEvent e) {
		if (beep && this.beep) {
			Toolkit.getDefaultToolkit().beep();
		}
		e.consume();
	}

}

Vielleicht kann es ja jemand gebrauchen oder einer von euch hat noch ein paar Anmerkungen dazu.

Danke an Marco für deine Hilfsbereitschaft und das schöne Beispiel, welches ich aus den oben erörterten Sorgen von wegen Eingriff in fremde "Einflusssphären" nicht benutzen möchte.

mpG
Tobias
 
OK, sieht aus wie eine (einfacherer, aber besser durchdachte und weniger gehackte) Mischung aus meinen beiden Ansätzen... aber ... wenn ich das richtig sehe, leigen da ja jetzt ALLE "geblockten" Components jeweils(!) als EIN FAContainer im Frame - und FAContainer überschreibt z.B. NICHT "getPreferredSize" und dandere Methoden, die notwendig wären, damit er sich (abgesehen vom Abfangen der Eingaben) so verhält, wie die eingewickelte Component (er könnte wohl einige Methodenaufrufe an die eingewickelte Component weiterreichen...!?) ... hm, naja, aber wenn's für deine Zwecke OK ist, ist's ja gut...
 
Mh, ja, das mit dem Weiterreichen der Methodenaufrufe stimmt wohl. Ich weiß noch nicht, ob ich das wirklich brauche, oder ob es ausreicht, die Komponente vor dem Einwickeln vollständig zu konfigurieren. Naja, die Praxis wird es zeigen. Das da oben ist bloß der Prototyp, der spätere Produktivcode wird sich davon (bis auf das verfolgte Konzept) erheblich unterscheiden.

mpG
Tobias

Edit: Wo siehst du die Schwierigkeiten darin, das jede Komponente von einem eigenen Container umwickelt wird?
 
Wo siehst du die Schwierigkeiten darin, das jede Komponente von einem eigenen Container umwickelt wird?

Naja, die potentiellen(!) Probleme sehe ich hauptsächlich in den Methodenaufrufen, die nicht weitergereicht werden - das fängt beim Layout an (und ist da wohl am ehesten "sichtbar"), kann sich aber bis zu so "fiesen" Sachen wie Focus-Verhalten durchziehen... :?

OB es ein Problem sein kann, wenn ein heavyweight-container (d.h. ein JFrame) mehrere JRootPanes enthält, weiß ich nicht - diese Klasse nimmt ja schon eine ziemliche Sonderrolle ein, aber ich habe noch nicht konkret mit dieser Klasse gearbeitet, darum weiß ich nicht, WO da die Probleme liegen könnten. Etwas weniger Bauchschmerzen hätte ich, wenn es "nur" eine JLayeredPane wäre, aber um das fundiert begründen zu können, müßte man sich nochmal die Details des Zusammenspiels von JFrames, JRootPanes und JLayeredPanes ansehen...
 
Bisher habe ich diesbezüglich keine Probleme feststellen können - ich habe da aber auch noch keine größeren Tests zu gefahren. Ich werde meine Erfahrungen hier mitteilen.

mpG
Tobias
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben