Swing Componenten übereinander platzieren.

  • Themenstarter Themenstarter jf
  • Beginndatum Beginndatum

jf

Bekanntes Mitglied
Hallo, gibe es eine einfachere Möglichkeit, einen Schaltfläche rechts in der Tab-Leiste einer JTabbedPane zu platzieren? - Order muss man hierfür doch wieder auf das Overlay-Layout zurückgreifen?
 
Hallo, ich muss das Thema doch noch mal aufmachen. An und für sich funktioniert das mit der JLayeredPane. Jetzt wollte ich mir aber eine kleine Helfer-Klasse bauen, welche mir die Positionierung der Schaltfläche vereinfacht. - Wenn ich es so mache, dann sehe ich leider keine Komponente.

Java:
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JTabbedPane;


@SuppressWarnings("serial")
public class JTabRowButton extends JComponent {

	private JLayeredPane m_layeredPane = new JLayeredPane();

	public JTabRowButton(JTabbedPane tabbedPane, JButton button) {

		tabbedPane.setBounds(0, 0, this.getWidth(), this.getHeight());
		button.setBounds(this.getWidth()-button.getWidth(), 0, button.getWidth(), 19);
		
		m_layeredPane.add(tabbedPane, new Integer(JLayeredPane.DEFAULT_LAYER.intValue() + 0));
		m_layeredPane.add(button,     new Integer(JLayeredPane.DEFAULT_LAYER.intValue() + 1));
		
		this.add(m_layeredPane);
	}
}

Ich sehe meinen Fehler leider überhaupt nicht... Was mache ich nur falsch? 🙁
 
[JAVA=14] tabbedPane.setBounds(0, 0, this.getWidth(), this.getHeight());
button.setBounds(this.getWidth()-button.getWidth(), 0, button.getWidth(), 19);[/code]
Lass Dir doch mal die Größe der beiden Komponenten ausgeben. Diese ist vermutlich 0x0.
z.B. liefert
Code:
this.getWidth()
0 solange die Komponente noch nicht angezeigt wird.
 
Lass Dir doch mal die Größe der beiden Komponenten ausgeben. Diese ist vermutlich 0x0.
z.B. liefert
Code:
this.getWidth()
0 solange die Komponente noch nicht angezeigt wird.
Um dieses Problem auszuschließen, habe ich es auch schon mit fixen Werten ausprobiert: auch hier wird nichts angezeigt...
 
Was mir gerade auffällt: Warum erbt Deine Klasse von JComponent und nicht von JPanel?
Bei JComponent ist imho z.B. kein LayoutManager gesetzt.
 
Falls ich das richtig verstanden habe, SOLL ja gerade kein LayoutManager verwendet werden - damit setBounds überhaupt funktioniert. Aber nebenbei: Ich finde, die Art der Verwendung dieser Klasse sieht etwas suspekt aus. Ich würde mal schauen, die der Code von JTabbedPane aussieht, und ob man da nicht "direkter" einen Button einbauen kann...
 
Ich finde, die Art der Verwendung dieser Klasse sieht etwas suspekt aus. Ich würde mal schauen, die der Code von JTabbedPane aussieht, und ob man da nicht "direkter" einen Button einbauen kann...
Ja, ich dachte auch erst daran, JTabbedPane zu erweitern - aber egal, welche Methode man verwendet (add() oder addTab()), es wird immer als neuer Tab hinzugefügt. Daher glaube ich, dass ich nicht umhingekommen wäre, eine neue Klasse zu bauen, welche durch Komposition ein JTabbedPane und einen JButton auf einer JLayeredPane zusammenführt. Um mir die dabei nötige Delegationsarbeit für die Methoden der JTabbedPane und des JButtons zu sparen, habe ich eben nur so eine kleine Klasse gebastelt: eine Art "Jacke", welche nur für die richtige Platzierung sorgt - gearbeitet wird anschließend weiter auf der JTabbedPane und dem JButton.
Für Anregungen, wie man so etwas eleganter lösen kann, bin ich natürlich sehr dankbar. Ich habe diesbezgl. nämlich noch nicht viel gemacht...

Würde der Component ein BorderLayout geben, damit der LayeredPane die gesamte Fläche einnimmt.
Das hat auch nichts gebracht.

Java:
package LayeredPane;


import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;


@SuppressWarnings("serial")
public class SimpleLayerTest extends JFrame {

	private static SimpleLayerTest m_instance = null;
	
	private SimpleLayerTest() {
		JLayeredPane layeredPane = new JLayeredPane();
		JTabbedPane tabs = new JTabbedPane();
		JButton but = new JButton("Drück mich!");
		
		tabs.addTab("Tab", new JPanel());
		
		this.setSize(400, 400);
		layeredPane = new JTabRowButton(tabs, but, this.getWidth(), this.getHeight());
		this.add(layeredPane);
	}
	
	public static SimpleLayerTest getInstance() {
		if(m_instance == null) {
			m_instance = new SimpleLayerTest();
		}
		return m_instance;
	}
	
	public static void main(String[] args) {
		SimpleLayerTest win = SimpleLayerTest.getInstance();
		win.setDefaultCloseOperation(EXIT_ON_CLOSE);
		win.setVisible(true);
	}

}

@SuppressWarnings("serial")
class JTabRowButton extends JLayeredPane {

	private JTabbedPane m_tabbedPane = null;
	private JButton m_button         = null;
	
	public JTabRowButton(JTabbedPane tabbedPane, JButton button, int width, int height) {
		m_tabbedPane = tabbedPane;
		m_button     = button;
		
		this.add(tabbedPane, new Integer(JLayeredPane.DEFAULT_LAYER.intValue() + 0));
		this.add(button,     new Integer(JLayeredPane.DEFAULT_LAYER.intValue() + 1));
		
		this.setSize(width, height);
	}
	
	public void setSize(int width, int height) {
		int tabW = width;
		int tabH = height;
		int butW = m_button.getPreferredSize().width;
		int butH = m_button.getPreferredSize().height;
		
		System.out.println(tabW + "  " + tabH);
		System.out.println(butW + "  " + butH);
		
		m_tabbedPane.setBounds(0, 0, tabW, tabH);
		m_button.setBounds(tabW-butW, 1, butW, 19);
	}
}

Ich bin jetzt aber schon etwas weiter: wenn meine Klasse direkt von JLayeredPane erbt anstatt von JComponent oder JPanel, dann funktioniert es komischerweise. Hat jemand eine Erklärung dafür?

Um die Sache richtig rund zu machen, wäre es aber gut, wenn ich die Größe des Client-Bereiches des Fensters ermitteln würde (also Fenstergröße - Randbreite). Wie kann ich das bewerkstelligen?
 
Nur überflogen, vorsicht: Du überschreibst anscheinend die setSize-Methode der JLayeredPane, da kann man sich ziemlich viel kaputt machen! Ggf. werde ich es später nochmal genauer ansehen.
 
Du überschreibst anscheinend die setSize-Methode der JLayeredPane
Ja, das war eigentlich auch der Plan: damit man mit der Klasse schneller zurecht kommt.

da kann man sich ziemlich viel kaputt machen!
Hmm. Gut, da habe ich keinerlei Erfahrung... - vlt. könnte man noch ein super.setSize() einbauen?

Ggf. werde ich es später nochmal genauer ansehen.
Au, das wäre nett! 🙂
 
Hmja, ist ein bißchen frickelig.

Hab' gerade mal geschaut, ob man den Button direkt in die JTabbedPane einbauen könnte, aber das wäre ziemlich fast unmöglich, wenn man auch andere Tab-Anordnungen usw. unterstützen wollte.

Das könnte man auch als Einschränkung bzgl. der aktuellen Lösung ansehen, aber ist vielleicht hier nicht so schlimm.

Das mit dem setSize ist auch so eine Sache: setSize ist nicht die einzige Methode, die die Größe ändert: Es gibt auch noch resize, setBounds, reshape usw... All diese Methoden ändern die Größe. Durch die überschriebene setSize würde jetzt der Effekt erreicht, dass diese Methode "für Swing" keine Auswirkungen mehr hätte. Wenn Swing also irgendwo
Java:
component.setSize(10,10);
if (component.getWidth() != 10) throw SomethingWrongException("Aaaahhh");
machen würde, dann würde dort die Exception geworfen. In der Tat ist es so, dass setSize intern praktisch nie verwendet wird, aber darauf kann man sich nicht verlassen.

Auch ein super.setSize würde nicht helfen:

Umgekehrt wäre es nämlich so, dass die Methoden, DIE von Swing verwendet werden (z.B. setBounds oder reshape) nicht den Effekt hätten, den du durch das Überschreiben von setSize erreichen willst. Das sieht man auch, wenn man die Framegröße ändert: Die TabbedPane/Button bleiben immer so groß, wie am Anfang, weil der LayoutManager zwar die JTabRowButton der Framegröße anpasst, dafür aber nicht setSize sondern reshape verwendet.

Die einzige einigermaßen "saubere" Lösung, die mir einfallen würde, wäre
Java:
@SuppressWarnings("serial")
class JTabRowButton extends JLayeredPane {
 
    private JTabbedPane m_tabbedPane = null;
    private JButton m_button         = null;
    
    public JTabRowButton(JTabbedPane tabbedPane, JButton button) {
        m_tabbedPane = tabbedPane;
        m_button     = button;
        
        this.add(tabbedPane, new Integer(JLayeredPane.DEFAULT_LAYER.intValue() + 0));
        this.add(button,     new Integer(JLayeredPane.DEFAULT_LAYER.intValue() + 1));
        this.addComponentListener(new ComponentAdapter() {
			
			@Override
			public void componentResized(ComponentEvent e) {
		        int butW = m_button.getPreferredSize().width;
		        m_tabbedPane.setBounds(0, 0, getWidth(), getHeight());
		        m_button.setBounds(getWidth()-butW, 1, butW, 19);
			}
		});
    }

}

@die üblichen Verdächtigen (Andre & Co) : Hat noch jemand eine "bessere" Idee?
 
Die einzige einigermaßen "saubere" Lösung, die mir einfallen würde, wäre
Ja, an einen ResizeListener hatte ich auch gedacht - nur bin ich davon ausgegangen, dass ich zunächst in Erfahrung bringen müsste, wie man die Client-Breite des Fensters ermittelt.
Wie ich sehe, kann ich in der ListenerRoutine aber mit this.getWidth() arbeiten, was im Konstruktor nicht der Fall war. Damit erübrigt sich das Ermitteln der Größe ja von ganz allein! 🙂

Dennoch würde mich interessieren, wie man die Client-Breich-Größe ermittelt (sofern dies schnell erklärt ist - bitte nicht für "Offtopic" groß Zeit verschwenden, so wichtig is es nämlich nun auch wieder nicht).

Was mir in diesem Zusammenhang jetzt aber neu ist, ist die Tatsache, dass man im Listener this verwenden kann. Ich nahm bisher stets an, dass mir this nur das Listener-Objekt selber liefert. - Ist das bei einem ComponentListener etwa anders?

@die üblichen Verdächtigen (Andre & Co) : Hat noch jemand eine "bessere" Idee?
Für weitere Anregungen wäre ich natürlich sehr dankbar. Dennoch bin ich mit der von Marco entwickelten Lösung schon sehr zufrieden. Danke! :toll:
 
Zuletzt bearbeitet:
Dennoch würde mich interessieren, wie man die Client-Breich-Größe ermittelt (sofern dies schnell erklärt ist - bitte nicht für "Offtopic" groß Zeit verschwenden, so wichtig is es nämlich nun auch wieder nicht).
Was genau meinst du mit Client-Bereich?

Was mir in diesem Zusammenhang jetzt aber neu ist, ist die Tatsache, dass man im Listener this verwenden kann. Ich nahm bisher stets an, dass mir this nur das Listener-Objekt selber liefert. - Ist das bei einem ComponentListener etwa anders?

Da steht ja kein 'this' 😀 Wenn man einfach nur 'getWidth()' aufruft, dann schaut er zuerst nach, ob es das in der anonymen Klasse gibt - wenn nicht, nimmt er das von der einschließenden Klasse. Mit 'this.getWidth()' würde es nicht direkt funktionieren, weil man damit explizit auf die innere Klasse verweisen würde, die ja kein getWidth hat.

Aber allgemein: Um in so einem Fall auf die Äußere Klasse (bzw. Instanz) zu verweisen, muss man
Java:
class JTabRowButton extends JLayeredPane {
... 
    public JTabRowButton(JTabbedPane tabbedPane, JButton button) {
...
        this.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent e) {
                int widthOfJTabRowButton = JTabRowButton.this.getWidth(); // <----- DAS HIER
            }
        });
    }
verwenden.
 
Zuletzt bearbeitet:
Was genau meinst du mit Client-Bereich?
Der Client-Bereich eines Fenster ist der Bereich des Fensters ohne Titelleiste und Rahmen.
Hierfür gibt es die Windows-API GetClientRect. Aber wenn du an dieser Stelle nachfragen musst, dann wird man das bei Java nur äußerst selten benötigen, was evtl. auch der Verwendung der LayoutManager geschuldet ist.


Da steht ja kein 'this' 😀 Wenn man einfach nur 'getWidth()' aufruft, dann schaut er zuerst nach, ob es das in der anonymen Klasse gibt - wenn nicht, nimmt er das von der einschließenden Klasse.
Mit 'this.getWidth()' würde es nicht direkt funktionieren, weil man damit explizit auf die innere Klasse verweisen würde, die ja kein getWidth hat.
Ah, ok. Sehr interessant. Dank für die Info.

Aber allgemein: Um in so einem Fall auf die Äußere Klasse (bzw. Instanz) zu verweisen, muss man
Code:
JTabRowButton.this.getWidth()
verwenden.
Auch das ist mir neu. - Ich habe bisher immer eine Member-Variable hierfür verwendet, welche ich im Konstruktor gesetzt habe. Bloß gut, dass ich nochmal nachgefragt habe. 😀
 
Der Client-Bereich eines Fenster ist der Bereich des Fensters ohne Titelleiste und Rahmen.
Hierfür gibt es die Windows-API GetClientRect. Aber wenn du an dieser Stelle nachfragen musst, dann wird man das bei Java nur äußerst selten benötigen, was evtl. auch der Verwendung der LayoutManager geschuldet ist.
Stichwort ContentPane, die wurde mit Java 1.5 leider ein bisschen "versteckt"
Java:
	JFrame frame = new JFrame();
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.setBounds(0, 0, 300, 300);
	frame.setVisible(true);
	System.out.println(frame.getBounds());
	System.out.println(frame.getContentPane().getBounds());
 
Hmja, aber die Größe braucht man eigentlich nicht... Üblicherweise liegt in der ContentPane irgendwas (ggf. auch mehrere Components, gelayoutet), und jede Component sollte bestenfalls IHRE eigene Größe wissen müssen...
 

Zurück
Oben