I have only JTabbedPane inside JFrame. JTabbedPane sets its dimensions to biggest page width/height.
As pages has different size is it possible to force JTabbedPane to change its dimensions when selecting other page?
http://grab.by/3hIg
Top one is how it behave now and bottom one is how i want it to behave (i resized frame by hand)
This is fairly simple. It involves dynamic calculation of differences between your pages dimensions and the using them to force preferred size on you JTabbedPane. I did a quick experiment and it worked. So instead of putting a lot of text here - here is the code. It is not perfect but you should get an idea. Questions are welcome, of course.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
private static int maxW = 0;
private static int maxH = 0;
public static void main(String[] args) {
final JFrame f = new JFrame();
final JTabbedPane tabs = new JTabbedPane();
tabs.add( createPanel(Color.RED, 100, 100), "Red");
tabs.add( createPanel(Color.GREEN, 200, 200), "Green");
tabs.add( createPanel(Color.BLUE, 300, 300), "Blue");
final Dimension originalTabsDim = tabs.getPreferredSize();
tabs.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
Component p = ((JTabbedPane) e.getSource()).getSelectedComponent();
Dimension panelDim = p.getPreferredSize();
Dimension nd = new Dimension(
originalTabsDim.width - ( maxW - panelDim.width),
originalTabsDim.height - ( maxH - panelDim.height) );
tabs.setPreferredSize(nd);
f.pack();
}
});
f.setContentPane(tabs);
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static final JPanel createPanel( Color color, int w, int h ) {
JPanel p = new JPanel();
p.setBackground(color);
p.setPreferredSize( new Dimension(w, h));
maxW = Math.max(w, maxW);
maxH = Math.max(h, maxH);
return p;
}
}
I think another option is to dynamically change the panels of each tab when the tab is selected:
install a listener on JTabbedPane selection
install an empty panel on every tab but the selected tab by default (that contains the real panel for that tab)
in the selection listener:
remove the panel from the previously selected tab (ie, replace it with an empty panel)
change the empty panel by the real panel in the newly selected tab
call pack() on the window/dialog containing the JTabbedPane
Disclaimer: I haven't tested this approach but I believe it should work according to what you want.
Please also note that dynamically changing the size of the dialog based on the selected tab is not very user-friendly from a pure GUI viewpoint.
How about this?
tabbedPane.addChangeListener(new ChangeListener(){
#Override
public void stateChanged(ChangeEvent arg0) {
Component mCompo=tabbedPane.getSelectedComponent();
tabbedPane.setPreferredSize(mCompo.getPreferredSize());
BasicFrame.this.pack();
}
});
Related
I am trying to hide a JSplitPane with animation. By hide, I mean to setDividerLocation(0) so its left component is invisible (technically it is visible, but with zero width):
public class SplitPaneTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
// rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
timer.stop();
});
timer.start();
}
}
If you run it, will see that everything seems good, and the animation runs smooth.
However, in the real application the right of the JSplitPane is a JPanel with CardLayout and each card has a lot of components.
If you uncomment this line in order to simulate the number of components:
// rightPanel.add(new JLabel("s"));
and re-run the above example, you will see that the animation no longer runs smoothly. So, the question is, is is possible to make it smooth(-ier)?
I have no idea how to approach a solution - if any exists.
Based on my research, I registered a global ComponentListener:
Toolkit.getDefaultToolkit()
.addAWTEventListener(System.out::println, AWTEvent.COMPONENT_EVENT_MASK);
and saw the tons of events that are being fired. So, I think the source of the problem is the tons of component events that are being fired for each component. Also, it seems that components with custom renderers (like JList - ListCellRenderer and JTable - TableCellRenderer), component events are firing for all of the renderers. For example, if a JList has 30 elements, 30 events (component) will be fired only for it. It also seems (and that's why I mentioned it) that for CardLayout, events are taking place for the "invisible" components as well.
I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.
I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.
The layout manager is invoked every time the divider location is changed which would add a lot of overhead.
One solution might be to stop invoking the layout manager as the divider is animating. This can be done by overriding the doLayout() method of the right panel:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SplitPaneTest2 {
public static boolean doLayout = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60))
{
#Override
public void doLayout()
{
if (SplitPaneTest2.doLayout)
super.doLayout();
}
};
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
SplitPaneTest2.doLayout = false;
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
SplitPaneTest2.doLayout = true;
splitPane.getRightComponent().revalidate();
}
});
timer.start();
}
}
Edit:
I was not going to include my test on swapping out the panel full of components with a panel that uses an image of components since I fell the animation is the same, but since it was suggested by someone else here is my attempt for your evaluation:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;
public class SplitPaneTest2 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
Component right = splitPane.getRightComponent();
Dimension size = right.getSize();
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
right.paint( g );
g.dispose();
JLabel label = new JLabel( new ImageIcon( bi ) );
label.setHorizontalAlignment(JLabel.LEFT);
splitPane.setRightComponent( label );
splitPane.setDividerLocation( splitPane.getDividerLocation() );
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
splitPane.setRightComponent( right );
}
});
timer.start();
}
}
#GeorgeZ. I think the concept presented by #camickr has to do with when you actually do the layout. As an alternative to overriding doLayout, I would suggest subclassing the GridLayout to only lay out the components at the end of the animation (without overriding doLayout). But this is the same concept as camickr's.
Although if the contents of your components in the right panel (ie the text of the labels) remain unchanged during the animation of the divider, you can also create an Image of the right panel when the user clicks the button and display that instead of the actual panel. This solution, I would imagine, involves:
A CardLayout for the right panel. One card has the actual rightPanel contents (ie the JLabels). The second card has only one JLabel which will be loaded with the Image (as an ImageIcon) of the first card.
As far as I know, by looking at the CardLayout's implementation, the bounds of all the child components of the Container are set during layoutContainer method. That would probably mean that the labels would be layed out inspite being invisible while the second card would be shown. So you should probably combine this with the subclassed GridLayout to lay out only at the end of the animation.
To draw the Image of the first card, one should first create a BufferedImage, then createGraphics on it, then call rightPanel.paint on the created Graphics2D object and finally dispose the Graphics2D object after that.
Create the second card such that the JLabel would be centered in it. To do this, you just have to provide the second card with a GridBagLayout and add only one Component in it (the JLabel) which should be the only. GridBagLayout always centers the contents.
Let me know if such a solution could be useful for you. It might not be useful because you could maybe want to actually see the labels change their lay out profile while the animation is in progress, or you may even want the user to be able to interact with the Components of the rightPanel while the animation is in progress. In both cases, taking a picture of the rightPanel and displaying it instead of the real labels while the animation takes place, should not suffice. So it really depends, in this case, on how dynamic will be the content of the rightPanel. Please let me know in the comments.
If the contents are always the same for every program run, then you could probably pre-create that Image and store it. Or even, a multitude of Images and store them and just display them one after another when the animation turns on.
Similarly, if the contents are not always the same for every program run, then you could also subclass GridLayout and precalculate the bounds of each component at startup. Then that would make GridLayout a bit faster in laying out the components (it would be like encoding a video with the location of each object), but as I am testing it, GridLayout is already fast: it just calculates about 10 variables at the start of laying out, and then imediately passes over to setting the bounds of each Component.
Edit 1:
And here is my attempt of my idea (with the Image):
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntBinaryOperator;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SplitPaneTest {
//Just a Timer which plays the animation of the split pane's divider going from side to side...
public static class SplitPaneAnimationTimer extends Timer {
private final JSplitPane splitPane;
private int speed, newDivLoc;
private IntBinaryOperator directionf;
private Consumer<SplitPaneAnimationTimer> onFinish;
public SplitPaneAnimationTimer(final int delay, final JSplitPane splitPane) {
super(delay, null);
this.splitPane = Objects.requireNonNull(splitPane);
super.setRepeats(true);
super.setCoalesce(false);
super.addActionListener(e -> {
splitPane.setDividerLocation(directionf.applyAsInt(newDivLoc, splitPane.getDividerLocation() + speed));
if (newDivLoc == splitPane.getDividerLocation()) {
stop();
if (onFinish != null)
onFinish.accept(this);
}
});
speed = 0;
newDivLoc = 0;
directionf = null;
onFinish = null;
}
public int getSpeed() {
return speed;
}
public JSplitPane getSplitPane() {
return splitPane;
}
public void play(final int newDividerLocation, final int speed, final IntBinaryOperator directionf, final Consumer<SplitPaneAnimationTimer> onFinish) {
if (newDividerLocation != splitPane.getDividerLocation() && Math.signum(speed) != Math.signum(newDividerLocation - splitPane.getDividerLocation()))
throw new IllegalArgumentException("Speed needs to be in the direction towards the newDividerLocation (from the current position).");
this.directionf = Objects.requireNonNull(directionf);
newDivLoc = newDividerLocation;
this.speed = speed;
this.onFinish = onFinish;
restart();
}
}
//Just a GridLayout subclassed to only allow laying out the components only if it is enabled.
public static class ToggleGridLayout extends GridLayout {
private boolean enabled;
public ToggleGridLayout(final int rows, final int cols) {
super(rows, cols);
enabled = true;
}
#Override
public void layoutContainer(final Container parent) {
if (enabled)
super.layoutContainer(parent);
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
//How to create a BufferedImage (instead of using the constructor):
private static BufferedImage createBufferedImage(final int width, final int height, final boolean transparent) {
final GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice gdev = genv.getDefaultScreenDevice();
final GraphicsConfiguration gcnf = gdev.getDefaultConfiguration();
return transparent
? gcnf.createCompatibleImage(width, height, Transparency.TRANSLUCENT)
: gcnf.createCompatibleImage(width, height);
}
//This is the right panel... It is composed by two cards: one for the labels and one for the image.
public static class RightPanel extends JPanel {
private static final String CARD_IMAGE = "IMAGE",
CARD_LABELS = "LABELS";
private final JPanel labels, imagePanel; //The two cards.
private final JLabel imageLabel; //The label in the second card.
private final int speed; //The speed to animate the motion of the divider.
private final SplitPaneAnimationTimer spat; //The Timer which animates the motion of the divider.
private String currentCard; //Which card are we currently showing?...
public RightPanel(final JSplitPane splitPane, final int delay, final int speed, final int rows, final int cols) {
super(new CardLayout());
super.setBorder(BorderFactory.createLineBorder(Color.red));
spat = new SplitPaneAnimationTimer(delay, splitPane);
this.speed = Math.abs(speed); //We only need a positive (absolute) value.
//Label and panel of second card:
imageLabel = new JLabel();
imageLabel.setHorizontalAlignment(JLabel.CENTER);
imageLabel.setVerticalAlignment(JLabel.CENTER);
imagePanel = new JPanel(new GridBagLayout());
imagePanel.add(imageLabel);
//First card:
labels = new JPanel(new ToggleGridLayout(rows, cols));
for (int i = 0; i < rows * cols; ++i)
labels.add(new JLabel("|"));
//Adding cards...
final CardLayout clay = (CardLayout) super.getLayout();
super.add(imagePanel, CARD_IMAGE);
super.add(labels, CARD_LABELS);
clay.show(this, currentCard = CARD_LABELS);
}
//Will flip the cards.
private void flip() {
final CardLayout clay = (CardLayout) getLayout();
final ToggleGridLayout labelsLayout = (ToggleGridLayout) labels.getLayout();
if (CARD_LABELS.equals(currentCard)) { //If we are showing the labels:
//Disable the laying out...
labelsLayout.setEnabled(false);
//Take a picture of the current panel state:
final BufferedImage pic = createBufferedImage(labels.getWidth(), labels.getHeight(), true);
final Graphics2D g2d = pic.createGraphics();
labels.paint(g2d);
g2d.dispose();
imageLabel.setIcon(new ImageIcon(pic));
imagePanel.revalidate();
imagePanel.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_IMAGE);
}
else { //Else if we are showing the image:
//Enable the laying out...
labelsLayout.setEnabled(true);
//Revalidate and repaint so as to utilize the laying out of the labels...
labels.revalidate();
labels.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_LABELS);
}
}
//Called when we need to animate fully left motion (ie until reaching left side):
public void goLeft() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
minDivLoc = splitPane.getMinimumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc > minDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(minDivLoc, -speed, Math::max, ignore -> flip()); //Start the animation to the left.
}
}
//Called when we need to animate fully right motion (ie until reaching right side):
public void goRight() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
maxDivLoc = splitPane.getMaximumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc < maxDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(maxDivLoc, speed, Math::min, ignore -> flip()); //Start the animation to the right.
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
int rows, cols;
rows = cols = 60;
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
final RightPanel rightPanel = new RightPanel(splitPane, 10, 3, rows, cols);
splitPane.setLeftComponent(leftPanel);
splitPane.setRightComponent(rightPanel);
JButton left = new JButton("Go left"),
right = new JButton("Go right");
left.addActionListener(e -> rightPanel.goLeft());
right.addActionListener(e -> rightPanel.goRight());
final JPanel buttons = new JPanel(new GridLayout(1, 0));
buttons.add(left);
buttons.add(right);
frame.add(splitPane, BorderLayout.CENTER);
frame.add(buttons, BorderLayout.PAGE_START);
frame.setSize(1000, 800);
frame.setMaximumSize(frame.getSize());
frame.setLocationByPlatform(true);
frame.setVisible(true);
splitPane.setDividerLocation(0.5);
});
}
}
I am wondering why the drawing area I have created is not showing up in my second panel. I have checked their locations uses getX and getY (250, 0, which is I am assuming the correct area for it to be since that would be the top left of the second panel), but I cannot seem to figure out what is wrong. I'm assuming this is a problem with some fundamental learning aspect of this that I do not have right, but cannot seem to figure out what the issue is. If you could explain to me what is going wrong and the proper direction as to where I would go about fixing it, that would be appreciated. I do have the drawing area working when I have it standalone; the issue is that I cannot get it to appear when working with other GUI components.
Thank you ^^
Code:
package Drawing;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class DrawingApp extends JFrame{
public static void main(String[] args) {
GridLayout grid = new GridLayout(1, 2);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final DrawingComponent drawingArea = new DrawingComponent();
drawingArea.setSize(600, 250);
JPanel leftPanel = new JPanel();
JPanel rightPanel = new JPanel();
JSlider greSlider = new JSlider();
JSlider bluSlider = new JSlider();
JSlider redSlider = new JSlider();
Point leftLocation = new Point(0, 0);
Point rightLocation = new Point(250, 0);
JLabel greLabel = new JLabel("Green");
JLabel bluLabel = new JLabel("Blue");
JLabel redLabel = new JLabel("Red");
rightPanel.setLocation(rightLocation);
drawingArea.setLocation(rightLocation);
// JButton button = new JButton("Hello");
leftPanel.setSize(250, 600);
//leftPanel.setLocation(leftLocation);
leftPanel.setBorder((BorderFactory.createLineBorder(Color.black)));
rightPanel.setSize(250, 600);
//rightPanel.setLocation(rightLocation);
rightPanel.setBorder((BorderFactory.createLineBorder(Color.green)));
leftPanel.add(greLabel);
leftPanel.add(greSlider);
leftPanel.add(bluLabel);
leftPanel.add(bluSlider);
leftPanel.add(redLabel);
leftPanel.add(redSlider);
rightPanel.add(drawingArea);
frame.add(leftPanel);
frame.add(rightPanel);
//rightPanel.add(button);
frame.setSize(500, 600);
frame.setLayout(grid);
leftPanel.setVisible(true);
rightPanel.setVisible(true);
frame.setVisible(true);
class SlideClickListener implements ChangeListener
{
ChangeListener slideListener = new ChangeListener(){
#Override
public void stateChanged(ChangeEvent e){
if(e.getSource() == greSlider){
}
}
};
public void stateChanged(ChangeEvent ce) {
throw new UnsupportedOperationException("Not supportedyet.");
}
}
class MouseClickListener implements MouseListener
{
public void mouseClicked(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
System.out.println(x + " " + y);
drawingArea.drawPoints(x,y);
}
// Donothing methods
public void mouseReleased(MouseEvent event) {}
public void mousePressed(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
}
MouseListener listener = new MouseClickListener();
drawingArea.addMouseListener(listener);
}
}
I can include the DrawingComponent class if needed, but assuming that it isn't since I know for sure that the class is working.
I'm assuming this is a problem with some fundamental learning aspect of this that I do not have right,
You don't appear to understand how layout managers work:
leftPanel.setSize(250, 600);
//leftPanel.setLocation(leftLocation);
rightPanel.setSize(250, 600);
//rightPanel.setLocation(rightLocation);
None of those statements will do anything. It is the job of the layout manager to determine the size and location of components added to the panel. In your case you are trying to use a GridLayout. So the components added to the grid will be given a size AFTER the decorations of the frame are taken into consideration. So even though the frame may be (500, 600), the space available to the panel will be less (because you need to account for the title bar and borders of the frame).
Also, you should assign the layout manager to the panel BEFORE you add components to the panel.
leftPanel.setVisible(true);
rightPanel.setVisible(true);
Swing components (except top level containers like JFrame, JDialog) are visible by default so the above code does nothing.
I can include the DrawingComponent class if needed,
Until a problem is solved you don't know what is or isn't relative to the problem. My guess is the your DrawingComponent is the problem. Again, the default layout manager of a JPanel is the FlowLayout which respects the preferred size of any component added to it. I'm guessing your DrawingPanel doesn't implement the getPreferredSize() method to the preferred size is (0, 0) so there is nothing to paint.
Read the section from the Swing tutorial on Custom Painting for more information and working examples to get you started.
I would suggest you also look at the Layout Managers section of the tutorial for layout basics and working examples.
look at this simple code:
Main.java :
package CarManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Main extends JFrame {
private static final long serialVersionUID = 1L;
static int width = 400;
static int height = width / 16 * 9;
static String title = "Car Manager";
JButton viewTables = new JButton("View tables");
JButton clients = new JButton("Clients");
JButton search = new JButton("Search");
JButton viewCars = new JButton("View all");
JButton viewRent = new JButton("Rent a car");
JButton viewBuy = new JButton("Buy a car");
JButton viewAccessory = new JButton("Accessory");
public Main() {
setLayout(null);
setLocationRelativeTo(null);
setTitle(title);
setSize(width, height);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
JLabel background = new JLabel(new ImageIcon("res\\background2.jpg"));
add(background);
background.setSize(width, height);
add(viewTables);
add(clients);
add(search);
viewTables.setBounds(20, 20, 110, 30);
clients.setBounds(20, 70, 110, 30);
search.setBounds(20, 120, 110, 30);
viewTables.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
add(viewCars);
viewCars.setBounds(260, 20, 110, 20);
add(viewRent);
viewRent.setBounds(260, 50, 110, 20);
add(viewBuy);
viewBuy.setBounds(260, 80, 110, 20);
add(viewAccessory);
viewAccessory.setBounds(260, 110, 110, 20);
}
});
viewCars.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
View view = new View();
view.addWindowListener(new WindowPlug(Main.this));
setVisible(false);
}
});
}
public static void main(String args[]) {
new Main();
}
}
View.java:
package CarManager;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class View extends JFrame {
private static final long serialVersionUID = 1L;
int width = 400;
int height = width / 16 * 9;
String title = "View all Cars";
public View() {
setLayout(null);
setLocationRelativeTo(null);
setTitle(title);
setSize(width, height);
setResizable(false);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setVisible(true);
JLabel background = new JLabel(new ImageIcon("res\\background2.jpg"));
add(background);
background.setSize(width, height);
}
}
and WindowPlug.java:
package CarManager;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class WindowPlug extends WindowAdapter {
private Main mainFrame;
public WindowPlug(Main mainFrame) { // when creating an instance of this
// WindowAdapter, tell it with which
// Main Window you are working with
this.mainFrame = mainFrame;
}
public void windowClosing(WindowEvent e) {
mainFrame.setVisible(true);
mainFrame.revalidate();
}
}
when i click view tables and then view all (those are the buttons that work for now)
and the first window hides and a new window appears, now when i close the second window the first one appears visible but the buttons are not visible, i have to hover over with the mouse for them to be visible again. ive tried mainFrame.revalidate(); and
mainFrame.repaint();
but no result
im using windows 8.1 pro
One problem with your code, and I'm not sure if this is the main problem since your code works fine on my system, is that you're calling setVisible(true) on your main window before you've added all your components. It should only be called after all components have been added.
Other problems unrelated to your main question:
You should avoid using null layout. While using null layout may seem to a newbie the better way to create complex GUI's, it's a fallacy, and more you create Swing GUI's the more you learn to respect and use the layout managers and see that these creatures help immensely in creating flexible, beautiful and if need be, complex GUI's. Then you can let them size them selves appropriately by calling pack() prior to setting them visible.
It appears that you really want to use a CardLayout to swap views on one GUI rather than spitting multiple GUI's at the user.
If you absolutely must display a dialog window, then you should use a JDialog, not a JFrame. If you used a modal JDialog, you wouldn't be needing a WindowListener.
Edit
OK, a big problem I see is that you're using null layout and adding a JLabel that covers the whole contentPane, and then adding components to the same contentPane.
Instead, make the JLabel your contentPane, and then add your JButtons, etc to it.
But make sure that the JLabel's opaque property is set to true first.
Edit 2
If you need to use an image as a background image you can:
Put the Image in an ImageIcon, put the Icon in a JLabel, and again use the JLabel as your contentPane. Again, you will need to make the JLabel opaque by calling setOpaque(true) on it. This works well if you don't want to change the size of the image or the window.
If you do need to change the size of the image, better to have a JPanel draw the image in its paintComponent(Graphics g) method, and then use this JPanel as your contentPane.
Once you've created your contentPane, then set its layout and add your components to it.
Then call setContentPane(newContentPane) on your top level window and pass in the new contentPane.
A JLabel containing HTML-text automatically wraps lines using the available space. If one adds that JLabel to a JSrollPane he has to set the preferredSize to a decent value otherwise it won`t wrap. All this should work fine along other Components inside a JPanel using a LayoutManager.
Cause I want a resizeable application window I extended JScrollPane to keep track of the resize events and dynamically change the size synced to the width of the viewport. Basically it works but sometimes the calculation of the preferred height by the layout manager is wrong (value too big or too small). For instance the visibility of the red border cutting through the first line indicates that the calculation of the height is wrong.
I cannot reproduce the failure with a single wrapping JLabel.
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class WrappedLabel implements Runnable {
public static void main( String[] args ){
SwingUtilities.invokeLater( new WrappedLabel() );
}
#Override
public void run(){
final JPanel panel = new JPanel( new GridBagLayout() );
final GridBagConstraints gc = new GridBagConstraints();
gc.fill = GridBagConstraints.BOTH;
gc.weightx = 1.0;
gc.weighty = 1.0;
{
gc.gridx = 0;
gc.gridy = 0;
final JLabel label = new JLabel(
"<html>" + "please add some more text here"
);
label.setBorder( BorderFactory.createLineBorder( Color.RED ) );
panel.add( label, gc );
}
{
gc.gridx = 0;
gc.gridy = 1;
final JLabel label = new JLabel(
"<html>" + "please add some more text here"
);
label.setBorder( BorderFactory.createLineBorder( Color.RED ) );
panel.add( label, gc );
}
final JFrame frame = new JFrame();
frame.add( new ScrollPane( panel ) );
frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
frame.setSize( 256, 256 );
frame.setVisible( true );
}
private class ScrollPane extends JScrollPane implements ComponentListener {
ScrollPane( Container view ){
super( view );
this.viewport.addComponentListener( this );
}
#Override
public void componentHidden( ComponentEvent ce ){
}
#Override
public void componentMoved( ComponentEvent ce ){
}
/** calculating required height is a 3 step process
* 1. sync width of client and viewport, set height of client to high value
* 2. let GridbagManager calculate required minimum size
* 3. set preferredSize and revalidate
**/
#Override
public void componentResized( ComponentEvent ce ){
assert( this.viewport == ce.getSource() );
final Container view = (Container) this.viewport.getView();
final int width = this.viewport.getExtentSize().width;
view.setPreferredSize( new Dimension( width, Integer.MAX_VALUE ) );
final int height = view.getLayout().preferredLayoutSize( view ).height;
view.setPreferredSize( new Dimension( width, height ) );
view.revalidate();
}
#Override
public void componentShown( ComponentEvent ce ){
}
}
}
Apparently it's either a bug in GridBagLayout, or you are using the layout engine in a way totally unexpected by the developers. Several multi-line Labels with HTML inside, setting preferred size and immediately asking preferred size by the back door? Ugh!
I noticed that sometimes the layout works incorrectly when decreasing the window size: the panel inside scrollpane doesn't decrease and the horizontal scrollbar appears. (I am using Windows by the way).
Also, sometimes, if the vertical scrollbar was visible and the panel height was large, and then I increase the window size, the panel height remains unreasonably large and gaps appear around the label:
For me, the layout is wrong every other time when I decrease the window; increasing works better but if it goes wrong, it's also incorrect every other time. I tried debugging and printing values to console; it seems that view.getLayout().preferredLayoutSize( view ) depends not only on view.setPreferredSize but also on the current size of the panel and scrollpane. The code of GridBagLayout is too complicated to dive into.
DIRTY HACK
Since every other resize yields the correct result, why not resize it twice? Duplicating things in the ScrollPane.componentResized handler was unsuccessful, probably because the ScrollPane's size remains the same. The ScrollPane itself needs to be resized twice, with different values. To test it in the simplest way, I subclassed JFrame: it listens to componentResized and resizes its child window twice. The second resize has to be deferred via SwingUtilities.invokeLater.
Replace the lines
final JFrame frame = new JFrame();
frame.add( scroll );
by
final MyFrame frame = new MyFrame(scroll);
and add the following class:
private class MyFrame extends JFrame implements ComponentListener {
private Component child;
public MyFrame(Component child){
this.child=child;
setLayout(null);
getContentPane().add(child);
addComponentListener(this);
}
public void componentResized(ComponentEvent e) {
Dimension size=getContentPane().getSize();
child.setSize(new Dimension(size.width-1,size.height));
validate();
SwingUtilities.invokeLater(new ResizeRunner(size));
}
public void componentMoved(ComponentEvent e) {}
public void componentShown(ComponentEvent e) {}
public void componentHidden(ComponentEvent e) {}
private class ResizeRunner implements Runnable {
private Dimension size;
public ResizeRunner(Dimension size){
this.size=size;
}
public void run() {
child.setSize(size);
validate();
}
}
}
The same can be achieved by subclassing a layout manager.
Obviously, this approach is inelegant and inefficient, but as a workaround for a JRE bug, and if nothing else helps... ;-)
If i create non-resizable JFrames, and windows Aero is enabled setLocation does not seem to take account of the window border correctly.
In the following code I would expect the second frame to be positioned to the right of the first frame, instead the borders are overlapping. If Aero is disabled or if I remove the calls to setResizable this is done as expected.
import java.awt.Rectangle;
import javax.swing.JFrame;
public class FrameBorders {
public static void main(String[] args) {
JFrame frame1 = new JFrame("frame 1");
JFrame frame2 = new JFrame("frame 2");
frame1.setResizable(false);
frame2.setResizable(false);
frame1.setVisible(true);
Rectangle bounds = frame1.getBounds();
frame2.setLocation(bounds.x+bounds.width, bounds.y);
frame2.setVisible(true);
}
}
Am I doing something wrong or is this a bug?
How can I display 2 unresizable dialogs side by side without having overlapping borders?
Edit: added screenshots (also changed frame2 to a JDialog instead of a JFrame)
Aero On:
Aero Off:
Aero On but resizable:
What are the problems with settings bounds on non-resizable containers?
Suppose you adjust the bounds to look good on your platform. Suppose the user's platform has a font with different, say larger, FontMetrics. This example is somewhat contrived, but you get the idea. If you change the bounds of a non-resizable container, be sure any text is visible regardless of the host platform's default font.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* #see http://stackoverflow.com/a/12532237/230513
*/
public class Evil extends JPanel {
private static final String s =
"Tomorrow's winning lottery numbers: 42, ";
private JLabel label = new JLabel(s + "3, 1, 4, 1, 5, 9", JLabel.LEFT);
public Evil() {
this.add(label);
}
private void display() {
JFrame f = new JFrame("Evil");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this, BorderLayout.WEST);
f.pack();
int w = SwingUtilities.computeStringWidth(
label.getFontMetrics(label.getFont()), s);
int h = f.getHeight();
f.setSize(w, h);
f.setResizable(false);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Evil().display();
}
});
}
}
It seems that this is not a Java issue but rather an aero appcompat issue , as described here.
One solution that I see in Java is to let the windows be resizable then work around the setMaximumSize bug