Swing TriPane

jf

Bekanntes Mitglied
Hallo, ich bin -nach ein wenig Abstinenz- wie an etwas Java-Code dran.
Ich versuche eine Komponente zu bauen, welche 3 vertikale Bereiche enthält.
Diese Bereiche realisiere ich durch zwei ineinander verschachtelte JSplitPanes.
Die 2 äußeren Bereiche sollen collapsible sein. (über 2 Schaltflächen in den Ecken)
Das ganze funktioniert soweit recht gut - es gibt aber ein sehr unschönes Problem:

Wenn ich die innere SplitPane ändere (den äußeren Bereich dieser SplitPane aus- bzw. ein-klappe), dann springt der äußere Bereich der äußeren SplitPane auf den PreferredSize-Wert der enthaltenen Komponenten.

Ich habe bereits setEnabled() ausprobiert - dies verhindert aber nur die Veränderung der Dividerposition durch den Anwender.

Auch setResizeWeight() hat auf dieses Verhalten leider keinen Einfluss.

Sind meine Ausführungen verständlich? - Wenn Bedarf besteht, dann mache ich gerne ein kleines ausführbares Beispiel. :)

Gruß und vielen Dank für eure Hilfe!
 

jf

Bekanntes Mitglied
Sowas hilft meistens, speziell bei eigenen Components...
Ok, gerne:

Java:
package tripane;


import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
import javax.swing.OverlayLayout;


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

	private enum SideEnum {
		Left,
		Right,
	}
	
	private final int DIVIDER_WIDTH      = 2;
	private final int COLLAPSED_WIDTH    = 1;
	private final int EXPAND_WIDTH       = 250;
	private final int TOGGLE_BUTTON_SIZE = 20;
	private final int MAX_WINDOW_WIDTH   = 10000; 			// use some high value
	
	private static final boolean USE_ANIMATION  = true;
	
	private JSplitPane m_jSplitPane_left  = null;
	private JSplitPane m_jSplitPane_right = null;
	private JComponent m_holder           = null;
	private JButton m_jButton_toggleLeft  = null;
	private JButton m_jButton_toggleRight = null;

	private boolean m_initialized = false;

	
	//###########################################################################
	
	
	/**
	 * Constructor
	 */
	public TriPane() {
		m_jSplitPane_left = new JSplitPane();
		m_jSplitPane_left.setDividerSize(DIVIDER_WIDTH);
		m_jSplitPane_left.setResizeWeight(0);
		
		m_jSplitPane_right = new JSplitPane();
		m_jSplitPane_right.setDividerSize(DIVIDER_WIDTH);
		m_jSplitPane_right.setResizeWeight(1);
		
		m_jButton_toggleLeft = createToggleButton(SideEnum.Left);
		m_jButton_toggleRight = createToggleButton(SideEnum.Right);
		
		m_jSplitPane_left.addPropertyChangeListener("dividerLocation", new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if(Integer.parseInt( evt.getNewValue().toString() ) == COLLAPSED_WIDTH) {
					m_jButton_toggleLeft.setText(TO_THE_RIGHT);
				} else {
					m_jButton_toggleLeft.setText(TO_THE_LEFT);
				}
			}
		});
		
		m_jSplitPane_right.addPropertyChangeListener("dividerLocation", new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if( isAlmostEqual( Integer.parseInt(evt.getNewValue().toString()), m_jSplitPane_right.getSize().width) ) {
					m_jButton_toggleRight.setText(TO_THE_LEFT);
				} else {
					m_jButton_toggleRight.setText(TO_THE_RIGHT);
				}
			}
		});
		
		this.addComponentListener(new ComponentListener() {
			public void componentMoved(ComponentEvent arg0) {}
			public void componentHidden(ComponentEvent arg0) {}
			public void componentShown(ComponentEvent arg0) {}
			public void componentResized(ComponentEvent arg0) {
				// because componentShown() event did not occur in my tests
				if(!m_initialized) {
					setLeftToggleState(true);
					setRightToggleState(true);
					m_initialized = true;
				} else {
					/*
					if(m_jSplitPane_right.getWidth() < 2*EXPAND_WIDTH) {
						setLeftToggleState(false);
					} else {
						if(m_jSplitPane_right.getWidth() < 3*EXPAND_WIDTH) {
							setRightToggleState(false);
						} else {
							setRightToggleState(getRightToggleState());
						}
					}
					*/
				}
			}
		});

		// +----------------------------------------+------------+
		// | +------------+-----------------------+ |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | |            |                       | |            |
		// | +------------+-----------------------+ |            |
		// +----------------------------------------+------------+
		m_jSplitPane_right.setLeftComponent(m_jSplitPane_left);
		
		this.setLayout( new BorderLayout() );
		this.add(m_jSplitPane_right, BorderLayout.CENTER);
	}
	
	
	//###########################################################################
	
	
	public void setLeftComponent(Component leftComponent) {
		m_jSplitPane_left.setLeftComponent(leftComponent);
	}
	
	public Component getLeftComponent() {
		return m_jSplitPane_left.getLeftComponent();
	}
	
	//---------------------------------------------------------------------------

	public void setCenterComponent(Component centerComponent) {
		m_holder = (JComponent)centerComponent; 
		
		//important! - otherwise project preview cannot be placed correctly in overlay mode
		m_holder.setAlignmentX(1.0f);
		m_holder.setAlignmentY(1.0f);
		
		m_holder.setLayout( new OverlayLayout(m_holder) );
		addToggleButtons(m_holder);

		m_jSplitPane_left.setRightComponent(m_holder);
	}
	
	public Component getCenterComponent() {
		return m_jSplitPane_right.getLeftComponent();
	}
	
	//---------------------------------------------------------------------------

	public void setRightComponent(Component rightComponent) {
		m_jSplitPane_right.setRightComponent(rightComponent);
	}
	
	public Component getRightComponent() {
		return m_jSplitPane_right.getRightComponent();
	}
	
	
	//###########################################################################
	
	
	final String TO_THE_RIGHT = ">";
	final String TO_THE_LEFT = "<";

	public boolean getLeftToggleState() {
		return m_jButton_toggleLeft.getText().equals(TO_THE_LEFT);
	}
	public boolean getRightToggleState() {
		return m_jButton_toggleRight.getText().equals(TO_THE_RIGHT);
	}
	
	public void setLeftToggleState(boolean enable) {
		if(enable) {
			//slide(m_jSplitPane_left, COLLAPSED_WIDTH, EXPAND_WIDTH);
			m_jSplitPane_left.setDividerLocation(EXPAND_WIDTH);
		} else {
			m_jSplitPane_left.setDividerLocation(COLLAPSED_WIDTH);
		}
	}
	public void setRightToggleState(boolean enable) {
		if(enable) {
			m_jSplitPane_right.setDividerLocation(m_jSplitPane_right.getSize().width - EXPAND_WIDTH);
		} else {
			m_jSplitPane_right.setDividerLocation(m_jSplitPane_right.getSize().width);
		}
	}
	
	public void toggleLeftPane() {
		setLeftToggleState( !getLeftToggleState() );
	}
	public void toggleRightPane() {
		setRightToggleState( !getRightToggleState() );
	}
	
	public void addToggleButtons(JComponent toPlaceOn) {
		JComponent holder = new JLabel();

		// use MaximumSize in OverlayLayout!
		holder.setMaximumSize( new Dimension(MAX_WINDOW_WIDTH, TOGGLE_BUTTON_SIZE) );
		holder.setAlignmentX(0.0f);
		holder.setAlignmentY(0.0f);		
		holder.setLayout( new BorderLayout() );
		holder.add(m_jButton_toggleLeft, BorderLayout.WEST);
		holder.add(m_jButton_toggleRight, BorderLayout.EAST);
		
		toPlaceOn.setAlignmentX(1.0f);
		toPlaceOn.setAlignmentY(1.0f);
		toPlaceOn.setLayout( new OverlayLayout(toPlaceOn) );
		toPlaceOn.add(holder);
	}
	
	
	public JButton createToggleButton(final SideEnum side) {
		JButton button = new JButton();
		
		button.setAlignmentX(0.0f);
		button.setAlignmentY(0.0f);
		button.setPreferredSize( new Dimension(TOGGLE_BUTTON_SIZE, TOGGLE_BUTTON_SIZE) );

		button.setMargin( new Insets(0,0,0,0) );
		button.setFont( new Font("Dialog", Font.BOLD, 12) );
		button.setText(TO_THE_LEFT);
		button.setOpaque(false);
    	
		button.addMouseListener( new MouseListener() {
			@Override public void mouseClicked(MouseEvent e) {}
			@Override public void mousePressed(MouseEvent e) {}
			@Override
			public void mouseEntered(MouseEvent arg0) {
				((JButton)arg0.getSource()).setOpaque(true);
			}
			@Override
			public void mouseExited(MouseEvent arg0) {
				((JButton)arg0.getSource()).setOpaque(false);
			}
			@Override
			public void mouseReleased(MouseEvent e) {
				switch(side) {
					case Left:	((TriPane)m_jSplitPane_left.getParent().getParent()).toggleLeftPane(); break;
					case Right:	((TriPane)m_jSplitPane_right.getParent()).toggleRightPane();			 break;
				}
			}
		});
		
		return button;
	}

	
	//###########################################################################

	
	// not used - did not work as expected...
	static void slide(JSplitPane splitPane, int from, int to) {
		if(USE_ANIMATION) {
			for(int i=from; i<to; i+=(to-from)/10) {
				System.out.println(i);
				splitPane.setDividerLocation(i);
				try { Thread.sleep(10);	} catch (InterruptedException e) {}
			}
		}
		splitPane.setDividerLocation(to);
	}
	
	//---------------------------------------------------------------------------
	
	static boolean isAlmostEqual(int value1, int value2, int tolerance) {
		return (value1 >= value2-tolerance && value1 <= value2+tolerance);
	}

	static boolean isAlmostEqual(int value1, int value2) {
		return isAlmostEqual(value1, value2, 10);
	}
	
	
	
	
	
	
	/**
	 * Demo
	 * @param args
	 */
	public static void main(String[] args) {
		JLabel jl1 = new JLabel("Left");
		JLabel jl2 = new JLabel("Center");
		JLabel jl3 = new JLabel("Right");
		
		JButton jb1 = new JButton("Left");
		JButton jb2 = new JButton("Center");
		JButton jb3 = new JButton("Right");
		
		JFrame win = new JFrame();
		TriPane tri = new TriPane();
		
		if(true) {
			tri.setLeftComponent(jl1);
			tri.setCenterComponent(jl2);
			tri.setRightComponent(jl3);
		} else {		
			tri.setLeftComponent(jb1);
			tri.setCenterComponent(jb2);
			tri.setRightComponent(jb3);
		}
		
		win.add(tri);
		win.setSize(800, 600);
		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		win.setVisible(true);

	}

}

Der rechte Bereich funktioniert wunderbar - der linke klappt im collapsed-Zustand leider immer wieder ein Stück auf, sobald sich der Zustand des rechten Bereiches, oder die Größe des Fensters/der Komponete selber ändert.

Ihr seht auch, dass ich versucht habe, eine Art Verschiebe-Animation einzubauen, was auch nicht soll toll lief. Wenn hier jemand weiß, wie man so etwas besser macht, lass er es mich bitte wissen. (Das ist aber nicht ganz so wichtig, wie das erste Problem.)

Hoffe, das hilft auch weiter. :)
 

Marco13

Top Contributor
Nur schnell geschaut: Es liegt nicht an der Preferred- sondern an der MinimumSize: Mit
jl1.setMinimumSize(new Dimension(0,0));
vor dem Hinzufügen funktioniert's. Das SplitPane beachtet die MinimumSize eben... Man müßte sich mal überlegen, wie man das gewünschte Verhalten erreicht, OHNE dass man die hinzugefügte Component verändert (das sollte man IMHO vermeiden). Eine Möglichkeit KÖNNTE(!) sein, sowas zu machen wie
Java:
    public void setLeftComponent(Component leftComponent) {
        JPanel p = new JPanel(new GridLayout(1,1));
        p.setMinimumSize(new Dimension(0,0));
        p.add(leftComponent);
        m_jSplitPane_left.setLeftComponent(p);
    }

    public Component getLeftComponent() {
        // TODO: Würde das Panel zurückgeben! leftComponent oben irgendwo
        // speichern und dann hier zurückgeben!!!
        return m_jSplitPane_left.getLeftComponent(); 
    }

aber das ist nur ein erster Ansatz. Man könnte mal schauen, ob's was eleganteres gibt, wie z.B. das magische "splitPane.setIgnoreMinimumSize(true)" flag :D
 

jf

Bekanntes Mitglied
Hallo Marco,

vielen Dank für deinen Tipp! - Das mit dem MinimumSize hatte ich mir selber schon überlegt, aber es hatte in einem kurzen Test nicht funktioniert. Scheinbar hatte ich dabei irgedwas falsch gemacht... von daher wäre ich wohl von allein nicht so schnell zur Lösung gekommen (da ich annahm, dass MinimumsSize nichts bringt). Also danke! :)

Jetzt wollte ich aber in einer JDesktopPane einen JInternalFrame animiert bewegen. Dabei hatte ich das gleich Problem wie bei der TriPane-Animation: man sieht sie nicht! Es entsteht nur eine kurze Verzögerung und dann das Endergebnis. Ein .updateUI() in der Schleife bringt leider auch nichts.

Wie kann man in Java so etwas realisieren, ohne selber mit zeichnen anzufangen?
 

Marco13

Top Contributor
Die Animation selbst muss in einem eigenen Thread laufen. Das Setzen der Component-Eigenschaften, wie z.B. setPreferredSize dann aber wieder im EDT.

Pseudocode:
Java:
void animate()
{
    Thread t = new Thread(new Runnable()
    {
        public void run()
        {
 
            for (int i=0; i<100; i++)
            {
                setSomething(i); 
                try
                {
                    Thread.sleep(50);
                }
                catch (InterruptedException e)
                {
                     Thread.currentThread().interrupt();
                }
            }
        }
    });
    t.start();
}

private void setSomething(final int i)
{
    SwingUtilities.invokeLater(new Runnable()
    {
        public void run()
        {
            someComponent.setSomething(i);
        }
    });
}

Kann ein bißchen fummelig werden, man muss sich genau überlegen, WAS dort animiert werden soll, und wie ggf. getPreferredSize usw. überschrieben werden müssen. Da findet man aber sicher einiges dazu im Web.
 

Neue Themen


Oben