So basically if I put JPanels inside a JPanel that uses GridBagLayout and I restrict the size with setPreferredSize, eventually it reaches a point where it can't hold all of them, and it exhibits the behavior shown in the attached picture:
I'm making an accordion. This is just an example to showcase the problem I'm having. Each part of the accordion can open individually and they're of arbitrary size and get added on the fly. Its easy enough to get the heights of all the individual panels and compare them against the total height, but when too many are added it exhibits the crunching behavior I've shown. This also shrinks the heights so its much more difficult to determine when the crunching has happened. I would have to cache heights and somehow pre-calculate the heights of the new parts getting added. The end goal is to remove older panels when a new panel is added and there isn't enough room for it.
Is there an easy way to determine what height something would be if it weren't constrained, or maybe a supported way to detect when such crunching has is happening (so I can quickly thin it out before it gets painted again)? An option that makes GridBagLayout behave like some other layouts and overflow into hammerspace instead of compressing would work too.
Code for example:
import java.awt.*;
import java.awt.event.*;
import javaisms.out;
import javax.swing.*;
public class FoldDrag extends JLayeredPane {
public TexturedPanel backingPanel = new TexturedPanel(new GridBagLayout(),"data/gui/grayerbricks.png");
static JPanel windowbase=new JPanel();
static JPanel restrictedpanel=new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
public FoldDrag() {
JButton addpan = new JButton("Add things");
windowbase.add(addpan);
windowbase.add(restrictedpanel);
restrictedpanel.setBackground(Color.red);
restrictedpanel.setPreferredSize(new Dimension(200,200));
gbc.weighty=1;
gbc.weightx=1;
gbc.gridx=0;
gbc.gridy=0;
gbc.gridheight=1;
gbc.gridwidth=1;
gbc.fill=GridBagConstraints.HORIZONTAL;
addpan.addActionListener(new ActionListener() {
int number=0;
#Override
public void actionPerformed(ActionEvent e)
{
number++;
gbc.gridy=number;
JPanel tmppanel = new JPanel();
tmppanel.setPreferredSize(new Dimension(100,30));
if(number%3==0)
tmppanel.setBackground(Color.blue);
if(number%3==1)
tmppanel.setBackground(Color.yellow);
if(number%3==2)
tmppanel.setBackground(Color.green);
restrictedpanel.add(tmppanel,gbc);
restrictedpanel.validate();
}
});
windowbase.setVisible(true);
}
private static void createAndShowUI() {
JFrame frame = new JFrame("DragLabelOnLayeredPane");
frame.getContentPane().add(windowbase);
FoldDrag thedrag=new FoldDrag();
windowbase.add(thedrag);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(300,300));
frame.pack();
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
out.active=true;
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
EDIT: Seems I didn't describe my version of the accordion very well. Here's a link.
You have particular requirement which may be better served through the use of it's layout manager. This provides you the ability to control every aspect of the layout without the need to resort to hacks or "work arounds" which never quite work or have bizarre side effects
public class AccordionLayout implements LayoutManager {
// This "could" be controlled by constraints, but that would assume
// that more then one component could be expanded at a time
private Component expanded;
public void setExpanded(Component expanded) {
this.expanded = expanded;
}
public Component getExpanded() {
return expanded;
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
}
#Override
public Dimension preferredLayoutSize(Container parent) {
Dimension size = minimumLayoutSize(parent);
if (expanded != null) {
size.height -= expanded.getMinimumSize().height;
size.height += expanded.getPreferredSize().height;
}
return size;
}
#Override
public Dimension minimumLayoutSize(Container parent) {
int height = 0;
int width = 0;
for (Component comp : parent.getComponents()) {
width = Math.max(width, comp.getPreferredSize().width);
height += comp.getMinimumSize().height;
}
return new Dimension(width, height);
}
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int availableHeight = parent.getHeight() - (insets.top + insets.bottom);
int x = insets.left;
int y = insets.top;
int maxSize = 0;
Dimension minSize = minimumLayoutSize(parent);
if (expanded != null) {
minSize.height -= expanded.getMinimumSize().height;
// Try an honour the preferred size the expanded component...
maxSize = Math.max(expanded.getPreferredSize().height, availableHeight - minSize.height);
}
int width = parent.getWidth() - (insets.left + insets.right);
for (Component comp : parent.getComponents()) {
if (expanded != comp) {
comp.setSize(width, comp.getMinimumSize().height);
} else {
comp.setSize(width, maxSize);
}
comp.setLocation(x, y);
y += comp.getHeight();
}
}
}
And the runnable example...
This goes to the enth degree, creating a specialised component to act as each "fold", but this just reduces the complexity of the API from the outside, meaning, you just need to think about the title and the content and let the rest of the API take care of itself
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private AccordionLayout layout;
public TestPane() {
layout = new AccordionLayout();
setLayout(layout);
AccordionListener listener = new AccordionListener() {
#Override
public void accordionSelected(Component comp) {
layout.setExpanded(comp);
revalidate();
repaint();
}
};
Color colors[] = {Color.RED, Color.BLUE, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.YELLOW};
String titles[] = {"Red", "Blue", "Cyan", "Green", "Magenta", "Orange", "Pink", "Yellow"};
for (int index = 0; index < colors.length; index++) {
AccordionPanel panel = new AccordionPanel(titles[index], new ContentPane(colors[index]));
panel.setAccordionListener(listener);
add(panel);
}
}
}
public class ContentPane extends JPanel {
public ContentPane(Color background) {
setBackground(background);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
}
public interface AccordionListener {
public void accordionSelected(Component comp);
}
public class AccordionPanel extends JPanel {
private JLabel title;
private JPanel header;
private Component content;
private AccordionListener accordionListener;
public AccordionPanel() {
setLayout(new BorderLayout());
title = new JLabel("Title");
header = new JPanel(new FlowLayout(FlowLayout.LEADING));
header.setBackground(Color.GRAY);
header.setBorder(new LineBorder(Color.BLACK));
header.add(title);
add(header, BorderLayout.NORTH);
header.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
AccordionListener listener = getAccordionListener();
if (listener != null) {
listener.accordionSelected(AccordionPanel.this);
}
}
});
}
public AccordionPanel(String title) {
this();
setTitle(title);
}
public AccordionPanel(String title, Component content) {
this(title);
setContentPane(content);
}
public void setAccordionListener(AccordionListener accordionListener) {
this.accordionListener = accordionListener;
}
public AccordionListener getAccordionListener() {
return accordionListener;
}
public void setTitle(String text) {
title.setText(text);
revalidate();
}
public String getText() {
return title.getText();
}
public void setContentPane(Component content) {
if (this.content != null) {
remove(this.content);
}
this.content = content;
if (this.content != null) {
add(this.content);
}
revalidate();
}
public Component getContent() {
return content;
}
#Override
public Dimension getMinimumSize() {
return header.getPreferredSize();
}
#Override
public Dimension getPreferredSize() {
Dimension size = content != null ? content.getPreferredSize() : super.getPreferredSize();
Dimension min = getMinimumSize();
size.width = Math.max(min.width, size.width);
size.height += min.height;
return size;
}
}
public class AccordionLayout implements LayoutManager {
// This "could" be controled by constraints, but that would assume
// that more then one component could be expanded at a time
private Component expanded;
public void setExpanded(Component expanded) {
this.expanded = expanded;
}
public Component getExpanded() {
return expanded;
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
}
#Override
public Dimension preferredLayoutSize(Container parent) {
Dimension size = minimumLayoutSize(parent);
if (expanded != null) {
size.height -= expanded.getMinimumSize().height;
size.height += expanded.getPreferredSize().height;
}
return size;
}
#Override
public Dimension minimumLayoutSize(Container parent) {
int height = 0;
int width = 0;
for (Component comp : parent.getComponents()) {
width = Math.max(width, comp.getPreferredSize().width);
height += comp.getMinimumSize().height;
}
return new Dimension(width, height);
}
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int availableHeight = parent.getHeight() - (insets.top + insets.bottom);
int x = insets.left;
int y = insets.top;
int maxSize = 0;
Dimension minSize = minimumLayoutSize(parent);
if (expanded != null) {
minSize.height -= expanded.getMinimumSize().height;
// Try an honour the preferred size the expanded component...
maxSize = Math.max(expanded.getPreferredSize().height, availableHeight - minSize.height);
}
int width = parent.getWidth() - (insets.left + insets.right);
for (Component comp : parent.getComponents()) {
if (expanded != comp) {
comp.setSize(width, comp.getMinimumSize().height);
} else {
comp.setSize(width, maxSize);
}
comp.setLocation(x, y);
y += comp.getHeight();
}
}
}
}
Now, if you're really up for a challenge, you could use something a animated layout proxy and do something like...
The end goal is to remove older panels when a new panel is added and there isn't enough room for it
I would guess that after you add a panel you compare the preferred height with the actual height. When the preferred height is greater you have a problem and you remove components as required.
So then the next problem is to use a layout manager that doesn't change the heights of the panels. This can still be done with the GridBagLayout. You just need to override the getMinimumSize() method to return the getPreferredSize() Dimension.
Each part of the accordion can open individually and they're of arbitrary size and get added on the fly
You might want to consider using the Relative Layout. You can add components whose preferred size will be respected. So you will be able to check when the preferred height is greater than the actual height.
Then you can also add components that will be sized based on the amount of space left in the panel. These would be your expanding panels.
So in your example you example when you expand an item you could configure that component to take up the entire space available. If you expand two items then they would each get half the space available.
Maybe something like this:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ExpandingPanel extends JPanel
{
private JPanel expanding;
public ExpandingPanel(String text, Color color)
{
setLayout( new BorderLayout() );
JButton button = new JButton( text );
add(button, BorderLayout.NORTH);
expanding = new JPanel();
expanding.setBackground( color );
expanding.setVisible( false );
add(expanding, BorderLayout.CENTER);
button.addActionListener( new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
expanding.setVisible( !expanding.isVisible() );
Container parent = ExpandingPanel.this.getParent();
LayoutManager2 layout = (LayoutManager2)parent.getLayout();
if (expanding.isVisible())
layout.addLayoutComponent(ExpandingPanel.this, new Float(1));
else
layout.addLayoutComponent(ExpandingPanel.this, null);
revalidate();
repaint();
}
});
}
private static void createAndShowGUI()
{
RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS);
rl.setFill( true );
JPanel content = new JPanel( rl );
content.add( new ExpandingPanel("Red", Color.RED) );
content.add( new ExpandingPanel("Blue", Color.BLUE) );
content.add( new ExpandingPanel("Green", Color.GREEN) );
JFrame frame = new JFrame("Expanding Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( content);
frame.setLocationByPlatform( true );
frame.setSize(200, 300);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
You can tell something is "crunched" when panel.getPreferredSize().height != panel.getHeight() and panel.getPreferredSize().width != panel.getWidth()
Related
I'm trying out the JSplitPane with a couple of scrollable side-by-side JTables.
However I'm experiment a behavior where the JScrollPane gets shrinked too much, as per gif.
Notice the left component, that besides having a minimum width of 250px, continues shrinking.
The relevant code is
final var objetsTable = new JTable();
final var objectsScrollPane = new JScrollPane(objetsTable);
objectsScrollPane.setMinimumSize(new Dimension(250, 0));
objectsScrollPane.setPreferredSize(new Dimension(400, 300));
final var stepsTable = new JTable();
final var stepsScrollPane = new JScrollPane(stepsTable);
stepsScrollPane.setMinimumSize(new Dimension(150, 0));
stepsScrollPane.setPreferredSize(new Dimension(200, 300));
final var splitPane = new JSplitPane();
splitPane.setLeftComponent(objectsScrollPane);
splitPane.setRightComponent(stepsScrollPane);
splitPane.setResizeWeight(1.0);
How can I avoid the JScrollPanes getting shrinked too much in this case?
The getMinimumSize called on a JSplitPane returns a size which is actually taking into account the minimum size of its left and right Components, plus the divider size. So one way to maybe solve your problem would be to make your JSplitPane implement Scrollable (in order to make it respect the minimum size of itself) and add it to a JScrollPane. This way you can ensure that the minimum size is respected and when the user continues shrinking the Scrollable JSplitPane past its minimum size, then the scroll bars will show up.
Here is some working code:
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Rectangle;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class Main {
private static class MyScrollableSplitPane extends JSplitPane implements Scrollable {
private int maxUnitIncrement = 10;
public void setMaxUnitIncrement(final int pixels) {
maxUnitIncrement = pixels;
}
public int getMaxUnitIncrement() {
return maxUnitIncrement;
}
/**
* This is being constantly checked by the scroll pane instead of the
* getPreferredScrollableViewportSize...
*/
#Override
public Dimension getPreferredSize() {
final Dimension minSz = getMinimumSize(),
curSz = getSize();
curSz.width = Math.max(curSz.width, minSz.width);
curSz.height = Math.max(curSz.height, minSz.height);
return curSz;
}
/**
* This is only checked once (at the beginning).
*/
#Override
public Dimension getPreferredScrollableViewportSize() {
return super.getPreferredSize();
}
/**
* Source: https://docs.oracle.com/javase/tutorial/uiswing/components/scrollpane.html .
*/
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation,
int direction) {
//Get the current position.
int currentPosition;
if (orientation == SwingConstants.HORIZONTAL) {
currentPosition = visibleRect.x;
} else {
currentPosition = visibleRect.y;
}
//Return the number of pixels between currentPosition
//and the nearest tick mark in the indicated direction.
if (direction < 0) {
int newPosition = currentPosition -
(currentPosition / maxUnitIncrement)
* maxUnitIncrement;
return (newPosition == 0) ? maxUnitIncrement : newPosition;
} else {
return ((currentPosition / maxUnitIncrement) + 1)
* maxUnitIncrement
- currentPosition;
}
}
/**
* Source: https://docs.oracle.com/javase/tutorial/uiswing/components/scrollpane.html .
*/
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation,
int direction) {
if (orientation == SwingConstants.HORIZONTAL) {
return visibleRect.width - maxUnitIncrement;
} else {
return visibleRect.height - maxUnitIncrement;
}
}
#Override
public boolean getScrollableTracksViewportWidth() {
final Container parent = getParent();
return (parent instanceof JViewport) && (getMinimumSize().width < ((JViewport) parent).getWidth());
}
#Override
public boolean getScrollableTracksViewportHeight() {
final Container parent = getParent();
return (parent instanceof JViewport) && (getMinimumSize().height < ((JViewport) parent).getHeight());
}
}
private static void createAndShowGUI() {
/*Since I don't add any Components to the 'left' and 'right' panels, I am going to set the
preferred size of them. This is only for demonstrating the concept. Setting the minimum size
though is somewhat required by the JSplitPane itself.*/
final JPanel left = new JPanel();
left.setMinimumSize(new Dimension(150, 100));
left.setPreferredSize(new Dimension(200, 200));
final JPanel right = new JPanel();
right.setMinimumSize(new Dimension(300, 100));
right.setPreferredSize(new Dimension(400, 200));
final JSplitPane split = new MyScrollableSplitPane();
split.setBorder(BorderFactory.createLineBorder(Color.CYAN.darker(), 3));
split.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
split.setLeftComponent(left);
split.setRightComponent(right);
final JFrame frame = new JFrame("MyScrollableSplitPane demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(split));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(Main::createAndShowGUI);
}
}
I am having trouble getting a JSplitPane to maintain the same relative position when its container is resized. If the split pane is assumed to have a fixed position everything works fine. I can get the divider location to stay close to the same relative position using the code shown below but the divider shifts to the left when the JFrame is resized. The shift is larger if the JFrame is resized slowly. The shift is to the left if the JFrame is made smaller or larger.
How do I get the divider to stay in the exact same proportional location?
This is what the GUI initially looks like.
This is what it looks like after several resizings.
And this is what the code looks like. This attempt is based on the information provided here:
JSplitPane splitting 50% precisely
JSplitPane SetDividerLocation Problem
https://docs.oracle.com/javase/tutorial/uiswing/components/splitpane.html
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
public class Example {
public static void main(String[] args) {
new Example().showGui();
}
private void showGui() {
// create the jframe
JFrame jFrame = new JFrame();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(400, 200);
// create the left and right panels
JPanel left = new JPanel();
left.setBackground(Color.yellow);
JPanel right = new JPanel();
right.setBackground(Color.orange);
ResizableSplitPane split = new ResizableSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right, jFrame);
jFrame.getContentPane().add(split);
// show the gui
jFrame.setVisible(true);
}
public class ResizableSplitPane extends JSplitPane {
//
// instance variables
//
private boolean painted;
private double defaultDividerLocation;
private ResizableSplitPane resizableSplitPane = this;
private double currentDividerLocation;
private Component first;
private Component second;
private boolean dividerPositionCaptured = false;
//
// constructors
//
public ResizableSplitPane(int splitType, Component first, Component second, Component parent) {
this(splitType, first, second, parent, 0.5);
}
public ResizableSplitPane(int splitType, Component first, Component second, Component parent, double defaultDividerLocation) {
super(splitType, first, second);
this.defaultDividerLocation = defaultDividerLocation;
this.currentDividerLocation = defaultDividerLocation;
this.setResizeWeight(defaultDividerLocation);
this.first = first;
this.second = second;
parent.addComponentListener(new DividerLocator());
first.addComponentListener(new DividerMovedByUserComponentAdapter());
}
//
// trivial getters and setters
//
public double getDefaultDividerLocation() {
return defaultDividerLocation;
}
public void setDefaultDividerLocation(double defaultDividerLocation) {
this.defaultDividerLocation = defaultDividerLocation;
}
//
// implementation
//
#Override
public void paint(Graphics g) {
super.paint(g);
if (!painted) {
painted = true;
this.setDividerLocation(currentDividerLocation);
}
dividerPositionCaptured = false;
}
private class DividerLocator extends ComponentAdapter {
#Override
public void componentResized(ComponentEvent e) {
setDividerLocation(currentDividerLocation);
}
}
private class DividerMovedByUserComponentAdapter extends ComponentAdapter {
#Override
public void componentResized(ComponentEvent e) {
if (dividerPositionCaptured == false) {
dividerPositionCaptured = true;
currentDividerLocation = (double) first.getWidth() / (double) (first.getWidth() + second.getWidth());
System.out.println(currentDividerLocation);
}
}
}
}
}
Here's my attempt:
Override the doLayout method of the parent component of JSplitPane, after the resizing of JSplitPane has been completed, to adjust the position of the divider.
import java.awt.*;
import java.awt.event.*;
import java.math.BigDecimal;
import javax.swing.*;
public class Example2 {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new Example2().makeUI());
f.setSize(400, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
private JComponent makeUI() {
JPanel p = new JPanel(new GridLayout(0, 1, 0, 0));
JPanel left1 = new JPanel();
left1.setBackground(Color.YELLOW);
JPanel right1 = new JPanel();
right1.setBackground(Color.ORANGE);
JSplitPane split1 = new ResizableSplitPane(
JSplitPane.HORIZONTAL_SPLIT, left1, right1, p);
p.add(split1);
JPanel left2 = new JPanel();
left2.setBackground(Color.YELLOW);
JPanel right2 = new JPanel();
right2.setBackground(Color.ORANGE);
JSplitPane split2 = new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT, left2, right2);
p.add(new SplitPaneWrapper(split2));
return p;
}
}
class ResizableSplitPane extends JSplitPane {
//
// instance variables
//
private boolean painted;
private double defaultDividerLocation;
private ResizableSplitPane resizableSplitPane = this;
private double currentDividerLocation;
private Component first;
private Component second;
private boolean dividerPositionCaptured = false;
//
// constructors
//
public ResizableSplitPane(int splitType, Component first, Component second, Component parent) {
this(splitType, first, second, parent, 0.5);
}
public ResizableSplitPane(int splitType, Component first, Component second, Component parent, double defaultDividerLocation) {
super(splitType, first, second);
this.defaultDividerLocation = defaultDividerLocation;
this.currentDividerLocation = defaultDividerLocation;
this.setResizeWeight(defaultDividerLocation);
this.first = first;
this.second = second;
parent.addComponentListener(new DividerLocator());
first.addComponentListener(new DividerMovedByUserComponentAdapter());
}
//
// trivial getters and setters
//
public double getDefaultDividerLocation() {
return defaultDividerLocation;
}
public void setDefaultDividerLocation(double defaultDividerLocation) {
this.defaultDividerLocation = defaultDividerLocation;
}
//
// implementation
//
#Override
public void paint(Graphics g) {
super.paint(g);
if (!painted) {
painted = true;
this.setDividerLocation(currentDividerLocation);
}
dividerPositionCaptured = false;
}
private class DividerLocator extends ComponentAdapter {
#Override
public void componentResized(ComponentEvent e) {
setDividerLocation(currentDividerLocation);
}
}
private class DividerMovedByUserComponentAdapter extends ComponentAdapter {
#Override
public void componentResized(ComponentEvent e) {
if (dividerPositionCaptured == false) {
dividerPositionCaptured = true;
currentDividerLocation = (double) first.getWidth() / (double)(first.getWidth() + second.getWidth());
System.out.println(currentDividerLocation);
}
}
}
}
class SplitPaneWrapper extends JPanel {
private final JSplitPane splitPane;
protected SplitPaneWrapper(JSplitPane splitPane) {
super(new BorderLayout());
this.splitPane = splitPane;
splitPane.setResizeWeight(.5);
add(splitPane);
}
private static int getOrientedSize(JSplitPane sp) {
return sp.getOrientation() == JSplitPane.VERTICAL_SPLIT
? sp.getHeight() - sp.getDividerSize()
: sp.getWidth() - sp.getDividerSize();
}
#Override public void doLayout() {
int size = getOrientedSize(splitPane);
double d = splitPane.getDividerLocation() / (double) size;
BigDecimal bd = new BigDecimal(d).setScale(2, BigDecimal.ROUND_HALF_UP);
super.doLayout();
if (splitPane.isShowing()) {
EventQueue.invokeLater(() -> {
int s = getOrientedSize(splitPane);
int iv = (int)(.5 + s * bd.doubleValue());
splitPane.setDividerLocation(iv);
});
}
}
}
This is what I ended up using. It seems to do exactly what I set out to do: create a split pane that proportionally resizes including proportionally resizing after the divider is moved.
This is what the example gui looks like before and after resize:
And here's what the code looks like. This is a complete example that includes proportional resizing of the split pane for horizontal and vertical split panes and the case where the user has moved the divider.
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
public class Example {
public static void main(String[] args) {
new Example().showGui();
}
private void showGui() {
// create the jframe
JFrame jFrame = new JFrame();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(400, 200);
// create the left panel
JPanel left = new JPanel();
left.setBackground(Color.yellow);
// create the right panel
JPanel right = new JPanel();
right.setBackground(Color.orange);
// create the bottom panel
JPanel bottom = new JPanel();
bottom.setBackground(Color.green);
// create the split panes
ResizableSplitPane horizontalSplit = new ResizableSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right, jFrame);
ResizableSplitPane verticalSplit = new ResizableSplitPane(JSplitPane.VERTICAL_SPLIT, horizontalSplit, bottom, jFrame);
jFrame.getContentPane().add(verticalSplit);
// show the gui
jFrame.setVisible(true);
}
public class ResizableSplitPane extends JSplitPane {
//
// instance variables
//
private boolean painted;
private double defaultDividerLocation;
private double dividerProportionalLocation;
private int currentDividerLocation;
private Component first;
private Component second;
private boolean dividerPositionCaptured = false;
//
// constructors
//
public ResizableSplitPane(int splitType, Component first, Component second, Component parent) {
this(splitType, first, second, parent, 0.5);
}
public ResizableSplitPane(int splitType, Component first, Component second, Component parent, double defaultDividerLocation) {
super(splitType, first, second);
this.defaultDividerLocation = defaultDividerLocation;
this.dividerProportionalLocation = defaultDividerLocation;
this.setResizeWeight(defaultDividerLocation);
this.first = first;
this.second = second;
parent.addComponentListener(new DividerLocator());
second.addComponentListener(new DividerMovedByUserComponentAdapter());
}
//
// trivial getters and setters
//
public double getDefaultDividerLocation() {
return defaultDividerLocation;
}
public void setDefaultDividerLocation(double defaultDividerLocation) {
this.defaultDividerLocation = defaultDividerLocation;
}
//
// implementation
//
#Override
public void paint(Graphics g) {
super.paint(g);
if (painted == false) {
painted = true;
this.setDividerLocation(dividerProportionalLocation);
this.currentDividerLocation = this.getDividerLocation();
}
}
private class DividerLocator extends ComponentAdapter {
#Override
public void componentResized(ComponentEvent e) {
setDividerLocation(dividerProportionalLocation);
currentDividerLocation = getDividerLocation();
}
}
private class DividerMovedByUserComponentAdapter extends ComponentAdapter {
#Override
public void componentResized(ComponentEvent e) {
System.out.println("RESIZED: " + dividerPositionCaptured);
int newDividerLocation = getDividerLocation();
boolean dividerWasMovedByUser = newDividerLocation != currentDividerLocation;
System.out.println(currentDividerLocation + "\t" + newDividerLocation + "\t" + dividerProportionalLocation);
if (dividerPositionCaptured == false || dividerWasMovedByUser == true) {
dividerPositionCaptured = true;
painted = false;
if(getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
dividerProportionalLocation = (double) first.getWidth() / (double) (first.getWidth() + second.getWidth());
} else {
dividerProportionalLocation = (double) first.getHeight() / (double) (first.getHeight() + second.getHeight());
}
System.out.println(dividerProportionalLocation);
}
}
}
}
}
I have a JPanel that contains 11 JLabels, each of them registered with a MouseMotionListener like so (generated by Netbeans):
label1.addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseMotionEvent evt){
label1MouseDragged(evt);
}
and the individual labelXMouseDragged methods contain (for example):
label1.setLocation(label1.getParent().getMousePosition());
This Panel lives inside another Panel alongside various other controls. I find that I can drag my labels just fine inside the panel (I had my methods correctly checking for bounds, but I have left them like the above for simplicity). However, if the mouse is clicked anywhere that is not a control, either within the inner panel or within the parent panel, the locations of the labels reset. What is causing this to happen? I have no mouseListeners of any kind registered anywhere else, and if I make this panel by itself, I seem to have no issues with clicks.
You could achieve this by using a null layout, but I have a pathalogical dislike for null layouts, too many things go wrong with them.
The Swing API is based around the use of layout managers when laying out components.
Instead, you could create a layout manager whose sole responsibility would be to honour the position of the components it is managing
The basic benefit of this is you don't need to worry about sizing the components and that it will respond to changes to the parent container as well as changes to the containers around it without you needing to add additional listeners.
You could even devise a bounds check within the layoutContainer to ensure that the components stay within bounds
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AbsoluteLayoutExample {
public static void main(String[] args) {
new AbsoluteLayoutExample();
}
public AbsoluteLayoutExample() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new AbsoluateLayoutManager());
JLabel test = new JLabel("Test");
add(test);
MouseAdapter ma = new MouseAdapter() {
private Point offset;
private Component dragComp;
#Override
public void mousePressed(MouseEvent e) {
Point point = e.getPoint();
for (Component comp : getComponents()) {
if (comp.getBounds().contains(point)) {
offset = new Point(point.x - comp.getX(), point.y - comp.getY());
dragComp = comp;
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
offset = null;
dragComp = null;
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragComp != null) {
Point p = e.getPoint();
Point dragP = new Point(p.x - offset.x, p.y - offset.y);
dragComp.setLocation(dragP);
}
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class AbsoluateLayoutManager implements LayoutManager2 {
#Override
public void addLayoutComponent(Component comp, Object constraints) {
}
#Override
public Dimension maximumLayoutSize(Container target) {
return preferredLayoutSize(target);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
}
#Override
public Dimension preferredLayoutSize(Container parent) {
int maxX = 0;
int maxY = 0;
for (Component comp : parent.getComponents()) {
Dimension size = comp.getPreferredSize();
maxX = Math.max(comp.getX() + size.width, maxX);
maxY = Math.max(comp.getY() + size.height, maxY);
}
return new Dimension(maxX, maxY);
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
for (Component comp : parent.getComponents()) {
Dimension size = comp.getPreferredSize();
comp.setSize(size);
}
}
}
}
You might also consider something like this example which is a "percentage" based constraint to lay out components, so thay are always at a given perctange point within the container
I am preparing a window with some horizontal tabs using JTabbedPane, Tabs And window are prepared properly.
Now I need to setup these tabs in level basis, based on my requirement (My logic returns integer value based on that I need to setup levels ).
Levels look like :
Can you please advise?
Just the sight of screenshot:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TabHeightTest {
public JComponent makeUI() {
JTabbedPane tabbedPane = new JTabbedPane(
JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
tabbedPane.setUI(new javax.swing.plaf.basic.BasicTabbedPaneUI() {
#Override protected int calculateTabHeight(
int tabPlacement, int tabIndex, int fontHeight) {
return 32;
}
#Override protected void paintTab(
Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex,
Rectangle iconRect, Rectangle textRect) {
if(tabIndex==0) {
rects[tabIndex].height = 20 + 1;
rects[tabIndex].y = 32 - rects[tabIndex].height + 1;
} else if(tabIndex==1) {
rects[tabIndex].height = 26 + 1;
rects[tabIndex].y = 32 - rects[tabIndex].height + 1;
}
super.paintTab(g, tabPlacement, rects, tabIndex, iconRect, textRect);
}
});
tabbedPane.addTab("000", new JLabel("aaaaaaaaaaa"));
tabbedPane.addTab("111", new JScrollPane(new JTree()));
tabbedPane.addTab("222", new JSplitPane());
return tabbedPane;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new TabHeightTest().makeUI());
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
The tab bounds are the responsibility of the look and feel. If you are in control of that (and if you're not, you should not try such non-portable tricks anyway), you can modify it in the layout manager of the tabbed pane.
For BasicLookAndFeel the tab bounds calculation is done in BasicTabbedPaneUI.TabbedPaneLayout.calculateTabRects() and BasicTabbedPaneUI.TabbedPaneScrollLayout.calculateTabRects(). For Basic L&F derived themes, BasicTabbedPaneUI has a createLayout() method which can be overridden to return the layout manager(s) with the desired behaviour.
I am trying to make a JPanel slide in from the side using this class i made:
public class AnimationClass {
private int i;
private int y;
private JPanel panel;
private int xTo;
private Timer timer;
private int xFrom;
synchronized void slidePanelInFromRight(JPanel panelInput, int xFromInput, int xToInput, int yInput, int width, int height) {
this.panel = panelInput;
this.xFrom = xFromInput;
this.xTo = xToInput;
this.y = yInput;
panel.setSize(width, height);
timer = new Timer(0, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
for (int i = xFrom; i > xTo; i--) {
panel.setLocation(i, y);
panel.repaint();
i--;
timer.stop();
timer.setDelay(100);
if (i >= xTo) {
timer.stop();
}
}
timer.stop();
}
});
timer.start();
}
}
Well, i dont know what the problem is. I've tried a lot of different things, but i doesn't seem like I can get it to work.
The timer should be changing the location on each tick, until it is in place, instead, on each tick, you're running through a for-next loop, which is blocking the EDT until the loop finishes, preventing from updating the UI
Update with example
For example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestAnimatedPane {
public static void main(String[] args) {
new TestAnimatedPane();
}
public TestAnimatedPane() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JPanel panel;
public TestPane() {
setLayout(null);
panel = new JPanel();
panel.setBackground(Color.RED);
add(panel);
Dimension size = getPreferredSize();
Rectangle from = new Rectangle(size.width, (size.height - 50) / 2, 50, 50);
Rectangle to = new Rectangle((size.width - 50) / 2, (size.height - 50) / 2, 50, 50);
Animate animate = new Animate(panel, from, to);
animate.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class Animate {
public static final int RUN_TIME = 2000;
private JPanel panel;
private Rectangle from;
private Rectangle to;
private long startTime;
public Animate(JPanel panel, Rectangle from, Rectangle to) {
this.panel = panel;
this.from = from;
this.to = to;
}
public void start() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long duration = System.currentTimeMillis() - startTime;
double progress = (double)duration / (double)RUN_TIME;
if (progress > 1f) {
progress = 1f;
((Timer)e.getSource()).stop();
}
Rectangle target = calculateProgress(from, to, progress);
panel.setBounds(target);
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.setInitialDelay(0);
startTime = System.currentTimeMillis();
timer.start();
}
}
public static Rectangle calculateProgress(Rectangle startBounds, Rectangle targetBounds, double progress) {
Rectangle bounds = new Rectangle();
if (startBounds != null && targetBounds != null) {
bounds.setLocation(calculateProgress(startBounds.getLocation(), targetBounds.getLocation(), progress));
bounds.setSize(calculateProgress(startBounds.getSize(), targetBounds.getSize(), progress));
}
return bounds;
}
public static Point calculateProgress(Point startPoint, Point targetPoint, double progress) {
Point point = new Point();
if (startPoint != null && targetPoint != null) {
point.x = calculateProgress(startPoint.x, targetPoint.x, progress);
point.y = calculateProgress(startPoint.y, targetPoint.y, progress);
}
return point;
}
public static int calculateProgress(int startValue, int endValue, double fraction) {
int value = 0;
int distance = endValue - startValue;
value = (int)Math.round((double)distance * fraction);
value += startValue;
return value;
}
public static Dimension calculateProgress(Dimension startSize, Dimension targetSize, double progress) {
Dimension size = new Dimension();
if (startSize != null && targetSize != null) {
size.width = calculateProgress(startSize.width, targetSize.width, progress);
size.height = calculateProgress(startSize.height, targetSize.height, progress);
}
return size;
}
}
Update
I should have added this in last night (1 year who didn't want to go to bed, 2 parents that did, say no more...)
Animation is complex topic, especially when you start looking at variable speed (the example is static).
Instead of reinventing the wheel, you should seriously consider taking a look at...
Timing Framework - This is base animation framework, that makes no assumptions about how you might like to use it.
Trident - Similar to the Timing Framework, but also has support for Swing based components (via reflection) build in
Universal Tween Engine
This well-factored example easily admits the variation below. It leverages the enclosed panel's preferred size in a JLayeredPane.
/**
* #see https://stackoverflow.com/a/16322007/230513
* #see https://stackoverflow.com/a/16316345/230513
*/
public class TestPane extends JLayeredPane {
private static final int WIDE = 200;
private static final int HIGH = 5 * WIDE / 8; // ~1/phi
private JPanel panel;
public TestPane() {
panel = new JPanel();
panel.setBackground(Color.RED);
panel.add(new JButton("Test"));
add(panel);
Dimension size = panel.getPreferredSize();
int half = HIGH / 2 - size.height / 2;
Rectangle from = new Rectangle(size);
from.translate(WIDE, half);
Rectangle to = new Rectangle(size);
to.translate(0, half);
panel.setBounds(from);
Animate animate = new Animate(panel, from, to);
animate.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(WIDE, HIGH);
}
}
There are a number of problems in the OP's code. As MadProrammer points, should only be moving one step per timer tick. Here is a simple,tested correction to the OPs code which moves the JPanel one pixel at a time, 25 times a second. Note the comments:
synchronized void slidePanelInFromRight(JPanel panelInput, int xFromInput, int xToInput, int yInput, int width, int height) {
this.panel = panelInput;
this.xFrom = xFromInput;
this.xTo = xToInput;
this.y = yInput;
panel.setSize(width, height);
// timer runs 25 times per second
timer = new Timer(40, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
// Must 'remember' where we have slid panel to by using instance variable rather than automatic variable
// Only move one step at a time.
// No need to restart timer, it continues to run until stopped
if (xFrom > xTo){
xFrom = xFrom - 1;
panel.setLocation(xFrom, y);
panel.repaint();
} else {
timer.stop();
}
panel.setLocation(xFrom, y);
panel.repaint();
}
});
timer.start();
}
example to slid Anything
package TestingPackage;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class ToggleBtn extends JPanel {
JFrame frame;
JPanel panelOut;
JLabel labelOn;
JLabel labelOff;
JButton btn;
int count = 1;
public ToggleBtn() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(500, 300, 300, 300);
frame.setLayout(null);
panelOut = new JPanel(null);
panelOut.setBounds(50, 100, 120, 30);
panelOut.setBackground(Color.gray);
frame.add(panelOut);
btn = new JButton("::");
btn.setBounds(0, 0, 60, 30);
panelOut.add(btn);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
startThread();
}
});
labelOn = new JLabel("ON");
labelOn.setBounds(0, 0, 60, 30);
panelOut.add(labelOn);
labelOff = new JLabel("OFF");
labelOff.setBounds(60, 0, 60, 30);
panelOut.add(labelOff);
frame.setVisible(true);
}
public void startThread() {
count++;
new Move().start();
}
public static void main(String[] args) {
new ToggleBtn();
}
class Move extends Thread {
#Override
public void run() {
if (count % 2 == 0) {
System.out.println("if");
for (int i = 0; i <= 60; i++) {
try {
Thread.sleep(3);
} catch (InterruptedException ex) {
Logger.getLogger(ToggleBtn.class.getName()).log(Level.SEVERE, null, ex);
}
btn.setBounds(i, 0, 60, 30);
}
} else {
System.out.println("else");
for (int i = 60; i >= 0; i--) {
try {
Thread.sleep(3);
} catch (InterruptedException ex) {
Logger.getLogger(ToggleBtn.class.getName()).log(Level.SEVERE, null, ex);
}
btn.setBounds(i, 0, 60, 30);
}
}
}
}
}