Swing Fortschrittsbalken im Fenster: Menü deaktivieren / abfangen?

Status
Nicht offen für weitere Antworten.

Grizzly

Top Contributor
Hallo alle zusammen,

gut kryptischer Titel, ich weiß. Habe aber keine Ahnung, wie ich das anders formulieren soll.

Unsere Java Anwendung im Geschäft arbeitet mit JDesktopPane und JInternalFrame. Wenn das Programm bspw. auf der Datenbank werkelt, wird ein JDialog mit einem Fortschrittsdialog angezeigt. Das ist jedoch wenig sinnvoll, da damit die gesamte Oberfläche gesperrt ist und auch in anderen Fenstern des JDesktopPane nicht gearbeitet werden kann.

Nun kam mir vor kurzem die Idee, diese Fortschrittsanzeige direkt in das JInternalFrame zu stecken. Und zwar auf dem GlassPane. Ich habe mal einen Prototypen geschrieben und das funktioniert soweit auch: Ich setze die Eigenschaft opaque auf true und gebe als Hintergrundfarbe eine Farbe mit einem Alpha Wert an. In die Mitte des GlassPane klopfe ich einen JProgressBar mit der Eigenschaft indeterminate auf true. So weit, so gut. Sieht auch recht nett aus.

Nun kommt das Problem: Benutzereingaben. Solange der Fortschritt angezeigt wird solange mit den Komponenten hinter dem GlassPane keine Interaktion möglich sein. Die Maus ist schnell erschlagen, in dem man alle Ereignisse schon auf dem GlassPane abfängt und mit consume() im entsprechenden Event Objekt "unschädlich" macht. Das Problem sind die Menüs: Auch nachdem man alle Tastatur Ereignisse im GlassPane mit der selben Methodik erschlagen hat, reagiert das Menü noch immer. Die Eigenschaft enabled wird von JMenuBar nicht überschrieben und scheint auch sonst nicht berücksichtigt zu werden.

Hat jemand von Euch eine Idee, wie man das hinbekommen kann oder hat etwas ähnliches schon einmal probiert?

Eine Möglichkeit wäre u.U. JMenuBar zu überschreiben. Ich hätte aber lieber eine Lösung die ich innerhalb einer Unterklasse von JInternalFrame lösen kann. Da wäre die Hardcore Methode einfach das Menü zu entfernen und nach getaner Arbeit wieder zu setzen. Aber das gefällt mir nicht wirklich.
 
Mhm...
Eventuell in deinen JInternalFrames ein Status setzen und in den Action dann abfragen ob sich dieses Frame in diesem Status befindet dann nichts machen...
 
Also du könntest die einzlnen (Haupt-)Menüpunkte (JMenu) disablen. Das sollte klappen.
 
Vielleicht noch als Ergänzung:

Es soll das Menü in dem JInternalFrame während des Vorgangs nicht ansprechbar sein. Das Menü außerhalb des JDesktopPane (also im JFrame oder JDialog) ist uninteressant. Im Gegenteil: Dies soll weiterhin bedienbar bleiben.
 
Aso haha dann ist es auf jeden fall einfacher -.-...
dann disable die einzelenen JMenu's
 
Aso haha dann ist es auf jeden fall einfacher -.-...
dann disable die einzelenen JMenu's

Hm, Frage wäre hier natürlich gleich, ob es auch anders geht. Wenn jetzt der Thread, welcher im Hintergrund werkelt, einzelne Menüpunkt enabled und disabled, würde man sich mit der Methode leider ins Knie schiessen. Also ich hatten den Fall bisher zwar nicht. Aber man weiß ja nie, was der Kunde als nächstes will... ;-)
 
Hm, Frage wäre hier natürlich gleich, ob es auch anders geht. Wenn jetzt der Thread, welcher im Hintergrund werkelt, einzelne Menüpunkt enabled und disabled, würde man sich mit der Methode leider ins Knie schiessen. Also ich hatten den Fall bisher zwar nicht. Aber man weiß ja nie, was der Kunde als nächstes will... ;-)

Der Thread im Hintergrund soll auch nichts enablen disablen^^...Das machst du einmal bevor er startet und wenn er zu Ende ist... Eventuell mal die Klasse SwingWorker anschauen
 
sieh dir mal die Bibliothek https://jxlayer.dev.java.net/ an. Die ist für genau diese Art von Dingen entwickelt worden. Damit kannst du auf die GlassPane zeichnen, gleichzeitig allerdings auch alle Eingabeevents abfangen oder die ganze GUI mit einem optischen Effekt hinterlegen, der zeigt, dass sie nicht verwendbar ist. Ab Java 7 ist der JXLayer übrigens ein Bestandteil von Swing, also kannst du davon ausgehen, dass er stabil ist und weiterentwickelt wird.
 
Der Thread im Hintergrund soll auch nichts enablen disablen^^...Das machst du einmal bevor er startet und wenn er zu Ende ist... Eventuell mal die Klasse SwingWorker anschauen

Jepp, schon klar. In Deinem Fall würde beim Start der Arbeiten das Menü komplett deaktiviert und nach dem Ende komplett aktiviert. Was aber, wenn im Thread am Menü etwas geändert wird. Bspw. Menüpunkte aktiviert oder deaktiviert. Also nicht zum Schutz gegenüber dem Benutzer sondern aufgrund von Berechnungen, Daten aus der Datenbank oder ähnlichem. Wenn der Thread Menüpunkte deaktivieren würde, würde am Ende diese wieder aktiviert. Und wenn der Thread Menüpunkte aktiviert, wäre wieder eine Interaktion möglich.
 
sieh dir mal die Bibliothek https://jxlayer.dev.java.net/ an. Die ist für genau diese Art von Dingen entwickelt worden. Damit kannst du auf die GlassPane zeichnen, gleichzeitig allerdings auch alle Eingabeevents abfangen oder die ganze GUI mit einem optischen Effekt hinterlegen, der zeigt, dass sie nicht verwendbar ist. Ab Java 7 ist der JXLayer übrigens ein Bestandteil von Swing, also kannst du davon ausgehen, dass er stabil ist und weiterentwickelt wird.

Ah kannt ich noch nicht ist ja cool des Ding =)...
Aber wie ich in den Beispiele gesehen hab ist das Menü trotzdem noch aktiv, und er enabled/disabled das Menü wahrscheinlich auch selber.
 
Ah kannt ich noch nicht ist ja cool des Ding =)...
Aber wie ich in den Beispiele gesehen hab ist das Menü trotzdem noch aktiv, und er enabled/disabled das Menü wahrscheinlich auch selber.
Da ist sogar ein Beispiel dabei, in dem ein Menü verwendet wird. Allerdings liegt das Menü in dem Beispiel außerhalb des Bereichs, in dem die Technik gezeigt wird. So gesehen geschickt gemacht und es liegt der verdacht Nahe, dass auch hier das Menü nicht automatisch ausgeschaltet wird. Ist aber nur eine Vermutung und ich muss es noch validieren.

Genau so sah mein Code auch aus (also fast halt). Und das funktioniert mit dem Menüs leider nicht.
 
Ja ich denke die Menues musst du selber disablen... Es ist ja nur eine GlassPane bzw. ein Layer, welches düe über ein JPanel oder JFrame(ContentPane) legst, das hat ja nichts mit den Menüs zu tun. Aber solltes doch kein Problem sein von deinem JInternalFrame die MenuBar zu holen und alle JMenu disablen oder enablen?

So müsste es doch klappen
Java:
         public void setMenueEnable(final JFrame frame, boolean enable)
         {
	         for(int i = 0 ;i < frame.getJMenuBar().getMenuCount();i++)
	         {
	        	 JMenu menu = frame.getJMenuBar().getMenu(i);
	        	 menu.setEnabled(enable);
	         }
         }
 
Zuletzt bearbeitet von einem Moderator:
Ja ich denke die Menues musst du selber disablen... Es ist ja nur eine GlassPane bzw. ein Layer, welches düe über ein JPanel oder JFrame(ContentPane) legst, das hat ja nichts mit den Menüs zu tun. Aber solltes doch kein Problem sein von deinem JInternalFrame die MenuBar zu holen und alle JMenu disablen oder enablen?

Prinzipiell ist das kein Problem. Allerdings:
Jepp, schon klar. In Deinem Fall würde beim Start der Arbeiten das Menü komplett deaktiviert und nach dem Ende komplett aktiviert. Was aber, wenn im Thread am Menü etwas geändert wird. Bspw. Menüpunkte aktiviert oder deaktiviert. Also nicht zum Schutz gegenüber dem Benutzer sondern aufgrund von Berechnungen, Daten aus der Datenbank oder ähnlichem. Wenn der Thread Menüpunkte deaktivieren würde, würde am Ende diese wieder aktiviert. Und wenn der Thread Menüpunkte aktiviert, wäre wieder eine Interaktion möglich.

Was ich mir schon überlegt habe: Die JMenuBar ganz zu entfernen und danach erst wieder zu setzen. Das Ganze natürlich intern im JInternalFrame sprich in einer eigenen Unterklasse. Dann würde setJMenuBar(JMenuBar) und getJMenuBar() weiterhin ganz normal arbeiten. Sprich bei Aufrufen würde auch das Ergebnis zustande kommen, dass man vermuten würde. Wenn man es ganz toll machen will, könnte man das Menü auch klonen und das geklonte deaktiviertes in der Oberfläche setzen. Dann würde der Optik bestehen bleiben.
 
Was ich mir schon überlegt habe: Die JMenuBar ganz zu entfernen und danach erst wieder zu setzen. Das Ganze natürlich intern im JInternalFrame sprich in einer eigenen Unterklasse. Dann würde setJMenuBar(JMenuBar) und getJMenuBar() weiterhin ganz normal arbeiten. Sprich bei Aufrufen würde auch das Ergebnis zustande kommen, dass man vermuten würde. Wenn man es ganz toll machen will, könnte man das Menü auch klonen und das geklonte deaktiviertes in der Oberfläche setzen. Dann würde der Optik bestehen bleiben.

Würde ich komisch finden wenn auf einmal das Menü weg wäre...
Einfach disablen ist in anderen Programme ja auch so und gut is...
 
Du kannst dir ja auch die Zustände des Menüs speichern und dann wieder herstellen. (eine Bit-Map oder ähnliches würde dafür ja ausreichen)
Obwohl ich mal fragen darf, ob du Menüs während der Bedienung aktivierst oder nur die darunter liegenden Items? Weil falls du nur Items (de-)aktivierst, hat das ja keine Auswirkungen, wenn du die "Hauptmenüs" (de-)aktivierst.
 
Hallo,

du koenntest dich in die event dispaching queue reinhaengen und die entsprechenden events fuer die Menues abfangen.

In etwa so:
Java:
Toolkit.getDefaultToolkit().addAWTEventListener (new AWTEventListener() {
  public void eventDispatched(AWTEvent e) {
    if (event instanceof KeyEvent) ((KeyEvent)event).consume();
  }
},
AWTEvent.KEY_EVENT_MASK);

Allerdings bin ich mir nicht sicher, ob das mit InternalFrames auch hinhaut.
Aber vielleicht hilft dir der Ansatz ja weiter...
 
Das GlassPane ist wie ein unsichtbares Blatt, das über das RootPane eines Top-Level-Containers gemalt wird. Grundsätzlich deckt GlassPane sowohl die Menüleiste als auch den den Inhalt eines Frames ab. Die oben verlinkte Klasse funktioniert darum sehr wohl auch für die Menüleiste (bei mir jedenfalls)!
 
Das GlassPane ist wie ein unsichtbares Blatt, das über das RootPane eines Top-Level-Containers gemalt wird. Grundsätzlich deckt GlassPane sowohl die Menüleiste als auch den den Inhalt eines Frames ab. Die oben verlinkte Klasse funktioniert darum sehr wohl auch für die Menüleiste (bei mir jedenfalls)!
Damit wir nicht aneinander vorbeireden: Die Maus wird so auch abgedeckelt, aber die Tastatur nicht. Wenn ich [Alt] drücke, geht im Hintergrund das Menü auf. Bzw. er malt in meinem Fall einen leeren Kasten unter das Menü, da ich das GlassPane halbtransparent mache (new Color(224, 224, 244, 128)).
 
Damit wir nicht aneinander vorbeireden: Die Maus wird so auch abgedeckelt, aber die Tastatur nicht.
Bei mir wird auch die Tastatur abgedeckelt.
Grizzly hat gesagt.:
Wenn ich [Alt] drücke, geht im Hintergrund das Menü auf.
Bei mir nicht, Kürzel greifen auch nicht.
Grizzly hat gesagt.:
Bzw. er malt in meinem Fall einen leeren Kasten unter das Menü, da ich das GlassPane halbtransparent mache (new Color(224, 224, 244, 128)).
Bei malt er gar nichts, das GlassPane ist ebenfalls halbtransparent.

Diese Reaktion erscheint mir auch logisch, da das GlassPane durch [c]getRootPane().setGlassPane(glassPane)[/c] den gesamt RootPane abdeckt und der KeyListener bei
Code:
keyPressed(KeyEvent e)
und
Code:
keyReleased(KeyEvent e)
einen
Code:
e.consume()
macht: Consumes this event so that it will not be processed in the default manner by the source which originated it.
 
Zuletzt bearbeitet:
Das ist dann interessant. Wie gesagt: Bei mir ist das definitiv nicht der Fall. Ich werde mal schauen, dass ich meinen Prototyp morgen poste. Vielleicht habe ich auch irgendwie einen Denkfehler drin (der Teufel ist ja bekanntlich ein Eichhörnchen) oder wir haben irgendeinen gravierenden Unterschied im Aufbau der ganze Sache. 🙂
 
Also hier mein Code
Java:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ImageLibrary;

public class JTestFrame extends JFrame {
	/** Das Logging. */
	private static Log log = LogFactory.getLog(JTestFrame.class);
	
	private JPanel jGlassPane = null;
	private JProgressBar jProgressBar = null;
	
	private JMenu jFileMenu = null;
	private JMenuItem jCloseMenuItem = null;
	
	private JPanel jContentPane = null;
	
	private JToolBar jToolBar = null;
	private JButton jRefreshButton = null;
	
	private JPanel jDataPanel = null;
	private JScrollPane jScrollPane = null;
	private JTextArea jTextArea = null;
	
	private JPanel jButtonPanel = null;
	private JButton jCloseButton = null;
	
	/**
	 * Erstellt ein Fenster.
	 */
	public JTestFrame() {
		super();
		this.initialize();
	}
	
	private void refresh() {
		log.info("refresh()");
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				getJGlassPane().setVisible(true);
			}
		});
	}
	
	private Color createColor(final Color c, final int alpha) {
		return (new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha));
	}
	
	private void close() {
		log.info("close()");
		this.dispose();
	}
	
	/**
	 * Initialisiert das Objekt.
	 */
	private void initialize() {
		this.setTitle("Test");
		this.setSize(640, 480);
		this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(final WindowEvent e) {
				log.info("windowClosing(WindowEvent)");
				close();
			}
		});
		this.setGlassPane(this.getJGlassPane());
		this.setJMenuBar(new JMenuBar());
		this.getJMenuBar().add(this.getJFileMenu());
		this.setContentPane(this.getJContentPane());
	}
	
	private JPanel getJGlassPane() {
		GridBagConstraints constraints;
		
		if (this.jGlassPane == null) {
			this.jGlassPane = new JPanel();
			this.jGlassPane.setBackground(this.createColor(
					this.jGlassPane.getBackground(), 224));
			this.jGlassPane.setLayout(new GridBagLayout());
			constraints = new GridBagConstraints();
			constraints.anchor = GridBagConstraints.WEST;
			constraints.fill = GridBagConstraints.HORIZONTAL;
			constraints.gridx = 0;
			constraints.gridy = 0;
			constraints.weightx = 1;
			constraints.weighty = 1;
			constraints.insets = new Insets(4, 4, 4, 4);
			this.jGlassPane.add(this.getJProgressBar(), constraints);
			this.jGlassPane.addMouseListener(new MouseListener() {
				public void mouseClicked(final MouseEvent e) {
					if (getJGlassPane().isVisible()) {
						e.consume();
					}
				}
				
				public void mouseEntered(final MouseEvent e) {
					if (getJGlassPane().isVisible()) {
						e.consume();
					}
				}
				
				public void mouseExited(final MouseEvent e) {
					if (getJGlassPane().isVisible()) {
						e.consume();
					}
				}
				
				public void mousePressed(final MouseEvent e) {
					if (getJGlassPane().isVisible()) {
						e.consume();
					}
				}
				
				public void mouseReleased(final MouseEvent e) {
					if (getJGlassPane().isVisible()) {
						e.consume();
					}
				}
			});
		}
		return this.jGlassPane;
	}
	
	private JProgressBar getJProgressBar() {
		if (this.jProgressBar == null) {
			this.jProgressBar = new JProgressBar();
			this.jProgressBar.setIndeterminate(true);
		}
		return this.jProgressBar;
	}
	
	private JMenu getJFileMenu() {
		if (this.jFileMenu == null) {
			this.jFileMenu = new JMenu();
			this.jFileMenu.setText("Datei");
			this.jFileMenu.setMnemonic('D');
			this.jFileMenu.add(this.getJCloseMenuItem());
		}
		return this.jFileMenu;
	}
	
	private JMenuItem getJCloseMenuItem() {
		if (this.jCloseMenuItem == null) {
			this.jCloseMenuItem = new JMenuItem();
			this.jCloseMenuItem.setText("Beenden");
			this.jCloseMenuItem.setAccelerator(KeyStroke.getKeyStroke(
					KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK));
			this.jCloseMenuItem.setMnemonic('B');
			this.jCloseMenuItem.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent e) {
					log.info("actionPerformed(ActionEvent) -> jCloseMenuItem");
					close();
				}
			});
		}
		return this.jCloseMenuItem;
	}
	
	private JPanel getJContentPane() {
		if (this.jContentPane == null) {
			this.jContentPane = new JPanel();
			this.jContentPane.setLayout(new BorderLayout());
			this.jContentPane.add(this.getJToolBar(), BorderLayout.NORTH);
			this.jContentPane.add(this.getJDataPanel(), BorderLayout.CENTER);
			this.jContentPane.add(this.getJButtonPanel(), BorderLayout.SOUTH);
		}
		return this.jContentPane;
	}
	
	private JToolBar getJToolBar() {
		if (this.jToolBar == null) {
			this.jToolBar = new JToolBar();
			this.jToolBar.add(this.getJRefreshButton());
		}
		return this.jToolBar;
	}
	
	private JButton getJRefreshButton() {
		if (this.jRefreshButton == null) {
			this.jRefreshButton = new JButton();
			this.jRefreshButton.setIcon(ImageLibrary.getInstance().getIcon(
				"General.Refresh16"));
			this.jRefreshButton.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent e) {
					log.info("actionPerformed(ActionEvent) -> jRefreshButton");
					refresh();
				}
			});
		}
		return this.jRefreshButton;
	}
	
	private JPanel getJDataPanel() {
		GridBagConstraints constraints;
		
		if (this.jDataPanel == null) {
			this.jDataPanel = new JPanel();
			this.jDataPanel.setLayout(new GridBagLayout());
			
			constraints = new GridBagConstraints();
			constraints.anchor = GridBagConstraints.WEST;
			constraints.fill = GridBagConstraints.BOTH;
			constraints.gridx = 0;
			constraints.gridy = 0;
			constraints.weightx = 1;
			constraints.weighty = 1;
			constraints.insets = new Insets(4, 4, 4, 4);
			this.jDataPanel.add(this.getJScrollPane(), constraints);
		}
		return this.jDataPanel;
	}
	
	private JScrollPane getJScrollPane() {
		if (this.jScrollPane == null) {
			this.jScrollPane = new JScrollPane();
			this.jScrollPane.setHorizontalScrollBarPolicy(
				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
			this.jScrollPane.setVerticalScrollBarPolicy(
				ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
			this.jScrollPane.setViewportView(this.getJTextArea());
		}
		return this.jScrollPane;
	}
	
	private JTextArea getJTextArea() {
		if (this.jTextArea == null) {
			this.jTextArea = new JTextArea();
			this.jTextArea.setLineWrap(true);
			this.jTextArea.setWrapStyleWord(true);
		}
		return this.jTextArea;
	}
	
	private JPanel getJButtonPanel() {
		if (this.jButtonPanel == null) {
			this.jButtonPanel = new JPanel();
			this.jButtonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
			this.jButtonPanel.add(this.getJCloseButton());
		}
		return this.jButtonPanel;
	}
	
	private JButton getJCloseButton() {
		if (this.jCloseButton == null) {
			this.jCloseButton = new JButton();
			this.jCloseButton.setText("Schliessen");
			this.jCloseButton.setToolTipText("Schliesst das Fenster.");
			this.jCloseButton.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent e) {
					log.info("actionPerformed(ActionEvent) -> jCloseButton");
					close();
				}
			});
		}
		return this.jCloseButton;
	}
}
Der Mouse Listener ist notwendig weil sich sonst die JToolBar noch "herauslösen" lassen würde. Natürlich könnte man das auch über Floatable lösen. Ich möchte die Sache aber so unabhängig wie möglich halten.
Man nun den Vorgang über den Refresh Button startet, kann man mit [Alt]+[D] das Menü im Hintergrund noch aufklappen. Getestet mit JRE 1.6.0 Update 16.
Ich habe davon auch mal 3 Screenshots gemacht und an meinen Post angehangen. Wie man auf dem 2 und 3 Bild erkennen kann, ist das Menü aufgegangen.
 
Mit requestFocusInWindow aktivieren wir das GlassPane und mit einem KeyListener fangen wir die KeyEvents ab:
Java:
getJGlassPane().setVisible(true);
jGlassPane.requestFocusInWindow();
Java:
jGlassPane.addKeyListener(new KeyListener() {
    public void keyPressed(KeyEvent e) {
        e.consume();
    }
    public void keyTyped(KeyEvent e) {
    }
    public void keyReleased(KeyEvent e) {
        e.consume();
    }
});
 
Mit requestFocusInWindow aktivieren wir das GlassPane und mit einem KeyListener fangen wir die KeyEvents ab:
Java:
getJGlassPane().setVisible(true);
jGlassPane.requestFocusInWindow();
Java:
jGlassPane.addKeyListener(new KeyListener() {
    public void keyPressed(KeyEvent e) {
        e.consume();
    }
    public void keyTyped(KeyEvent e) {
    }
    public void keyReleased(KeyEvent e) {
        e.consume();
    }
});
Okay, der Focus war der springende Punkt. Den KeyListener hatte ich auch schon drin. Der hatte bei mir aber nichts gebracht. Wunderbar. Danke André. 🙂
 
Ein Disabled Glass Pane unter einem modalen JDialog, klingt so, als wolle man jemanden tot schiessen und dann noch erhängen, um sicher zu sein, daß er auch wirklich tot ist 😀
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben