Java, jframe resizing while dragging issue - java

I would like my jframe to change its size and contents depending on the x coordinate.
Initially the jframe appears in the 'primary zone' (x <= 1400) witn a panel sized 500x500 added to the jframe's content pane.
Desired: When it is dragged and leaves the 'primary zone' and enters the 'secondary zone' everything is removed from the content pane, the panel gets wrapped into a jscrollpane sized 200x200, and the jscrollpane is added the content pane. When the jframe leaves the 'secondary zone' the jscrollpane is removed from the content pane and the panel is added back.
Actual: Results are not stable. When the jframe leaves the primary zone I can see some flipping. Scrollbars appear but and the frame changes its size but then immediately resized back to the previous size. Stopping at breakpoints inside runnables in the changeSizeAndContent invokeLater codeblocks (not a good practice actually) brings the desired result and so does a conditional breakpoint.
There is some Swing multithreading taking place which I do not understand. I can see the EDT calling EventQueue's dispatchEvent and the COMPONENT_RESIZED (new, correct size) events triggered by runnables in the changeSizeAndContent are followed by COMPONENT_MOVED (old, now incorrect size) events which reference the component with its old size.
Tried with Java 8 and 11.
package Jna;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
/* 0------------------1200-1400--------->
* +---------------+
* | SECONDARY |
* +--------------------+---+ |
* | PRIMARY | B | x: 1200- |
* | x: 0-1400 | U | |
* | | F | |
* | | F | |
* | | E | |
* | | R | |
* | +---+-----------+
* | |
* +------------------------+
*/
public class FrameDemo3 {
static final JPanel panel;
static final JScrollPane jsp;
static {
panel = getInitializedPanel();
jsp = getInitilalizedJScrollPane();
}
static boolean isPrimaryZone = true;
static boolean isCurrentPrimaryZone = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("FrameDemo");
frame.addComponentListener(new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
int x = frame.getX();
if (x > 1400) {
isCurrentPrimaryZone = false;
} else if (x < 1200) {
isCurrentPrimaryZone = true;
}
if (isPrimaryZone != isCurrentPrimaryZone) {
isPrimaryZone = isCurrentPrimaryZone;
changeSizeAndContent(frame);
}
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
public static void changeSizeAndContent(JFrame frame) {
if (isPrimaryZone) {
SwingUtilities.invokeLater(() -> {
frame.getContentPane().removeAll();
frame.getContentPane().add(panel);
frame.pack();
});
} else {
SwingUtilities.invokeLater(() -> {
frame.getContentPane().removeAll();
frame.getContentPane().add(jsp);
frame.pack();
});
}
}
private static JPanel getInitializedPanel() {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBackground(Color.WHITE);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
panel.add(new JLabel(getLabelText(i, j)), getConstraints(i, j));
}
}
panel.setPreferredSize(new Dimension(500, 500));
return panel;
}
private static JScrollPane getInitilalizedJScrollPane() {
JScrollPane jsp = new JScrollPane(panel,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
jsp.setPreferredSize(new Dimension(200, 200));
return jsp;
}
static GridBagConstraints getConstraints(int gridX, int gridY) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = gridX;
gbc.gridx = gridY;
gbc.ipady = 40;
gbc.ipadx = 40;
return gbc;
}
static String getLabelText(int gridX, int gridY) {
StringBuilder sb = new StringBuilder("EXAMPLE ")
.append(gridX)
.append(',')
.append(gridY);
return sb.toString();
}
}

As mentioned in the comments, a component can only have a single parent, so an easier solution might be to add the component to the scroll pane and change the scrollbar policy as required. The code below demonstrates this.
There is some Swing multithreading taking place which I do not understand?
The issues appears to be the title bar of the frame.
(Note I changed the resizing behaviour to work at 1000/800 instead of 1400, 1200)
When the frame is moved to location 1001 your logic is invoked and the frame is resized as expected.
However, the issue is that additional events on the frame will always be generated.
For example if you move the frame to 1001 and hold the mouse down the frame will remain at the correct size. However as soon as you release the mouse it goes back to the previous size.
Or if you move the frame to location 1002 or greater it goes back to the previous size.
In both of the above cases code in your application is not executed to change the frame size.
I have no idea how to fix this as this logic is controlled by the OS frame widget. This may explain why it works for user153... I use Windows 10 and I see the problems you describe.
In the code below I added a Swing Timer to reset the frame size to the expected size after a delay of 2 seconds. It is not a practical solution, but it does demonstrate that your code is working as expected, you just can't control the external behaviour of the frame.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class FrameDemo4 {
static final JPanel panel;
static final JScrollPane jsp;
static Dimension preferredSize;
static {
panel = getInitializedPanel();
jsp = getInitilalizedJScrollPane();
}
static boolean isPrimaryZone = true;
static boolean isCurrentPrimaryZone = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("FrameDemo");
frame.addComponentListener(new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
int x = frame.getX();
System.out.println(x);
if (x > 1000) {
isCurrentPrimaryZone = false;
} else if (x < 800) {
isCurrentPrimaryZone = true;
}
if (isPrimaryZone != isCurrentPrimaryZone) {
isPrimaryZone = isCurrentPrimaryZone;
changeSizeAndContent(frame);
}
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// frame.getContentPane().add(panel);
frame.getContentPane().add(jsp);
frame.pack();
frame.setLocation(1000, 0);
frame.setVisible(true);
}
public static void changeSizeAndContent(JFrame frame) {
System.out.println(isPrimaryZone);
if (isPrimaryZone) {
//frame.getContentPane().removeAll();
//frame.getContentPane().add(panel);
jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
jsp.setPreferredSize( panel.getPreferredSize() );
frame.pack();
preferredSize = frame.getPreferredSize();
} else {
//frame.getContentPane().removeAll();
//frame.getContentPane().add(jsp);
jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
jsp.setPreferredSize( new Dimension(200, 200));
frame.pack();
preferredSize = frame.getPreferredSize();
}
Timer timer = new Timer(2000, (e) -> frame.setSize( preferredSize.width, preferredSize.height ) );
timer.start();
}
private static JPanel getInitializedPanel() {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBackground(Color.WHITE);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
panel.add(new JLabel(getLabelText(i, j)), getConstraints(i, j));
}
}
panel.setPreferredSize(new Dimension(500, 500));
return panel;
}
private static JScrollPane getInitilalizedJScrollPane() {
// JScrollPane jsp = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
// jsp.setPreferredSize(new Dimension(200, 200));
JScrollPane jsp = new JScrollPane(panel);
jsp.setPreferredSize(panel.getPreferredSize());
return jsp;
}
static GridBagConstraints getConstraints(int gridX, int gridY) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = gridX;
gbc.gridx = gridY;
gbc.ipady = 40;
gbc.ipadx = 40;
return gbc;
}
static String getLabelText(int gridX, int gridY) {
StringBuilder sb = new StringBuilder("EXAMPLE ")
.append(gridX)
.append(',')
.append(gridY);
return sb.toString();
}
}
The only solution I can think of is to:
Maybe use the Metal LAF. It uses an undecorated frame with a custom title bar
Create your own title bar to use on an undecorated frame.

Related

amount of generated JLabels seems random on each start

I am trying generate a JLabel grid in Java, but on every start the generated JLabels vary, on one start of the program there can be 25 visual JLabels, and without changing the code and restarting there can be 27 visual JLabels, im so confused, I have no idea what might be causing this. In theory there should be 169 JLabels visual on the screen (13 x 13 grid).
public class GUI {
JFrame frame;
JPanel panel;
int matrixLength = 13;
JLabel label[] = new JLabel[matrixLength * matrixLength];
public GUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setVisible(true);
frame.setResizable(false);
panel = (JPanel) frame.getContentPane();
panel.setLayout(null);
for (JLabel labels : label) {
labels = new JLabel();
labels.setVisible(true);
}
}
public void drawLabels() {
int xInc = 50;
int yInc = 50;
int xStart = 50;
int yStart = 0;
int y = yStart;
int x = 50;
for (int i = 0; i < label.length; i++) {
label[i] = new JLabel(" ");
label[i].setOpaque(true);
label[i].setVisible(true);
label[i].setBackground(Color.black);
if (i % (matrixLength) == 0) {
y += yInc;
x = xStart;
} else {
x += xInc;
}
System.out.println("i: " + i + " | y: " + y + " | x: " + x);
label[i].setBounds(x, y, 40, 40);
panel.add(label[i]);
}
}
}
I created the following GUI from your code.
Here are the changes I made.
I added a main method so I could start the application. In the main method, I called the SwingUtilities invokeLater method to ensure that the Swing components are created and executed on the Event Dispatch Thread.
I used two Swing layout managers to create the GUI. The JFrame has a default BorderLayout. The JPanel I created to hold the JLabels uses a GridLayout.
I added a text value to each of the JLabels so you could see them in the grid.
Here's the complete runnable code I used to create the GUI.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LabelGridGUI {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new LabelGridGUI();
}
});
}
JFrame frame;
int matrixLength = 13;
JLabel label[] = new JLabel[matrixLength * matrixLength];
public LabelGridGUI() {
frame = new JFrame("Label Grid");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createLabelPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setResizable(false);
frame.setVisible(true);
}
public JPanel createLabelPanel() {
JPanel panel = new JPanel(new GridLayout(0, matrixLength));
for (int i = 0; i < label.length; i++) {
label[i] = new JLabel("X");
label[i].setBackground(Color.black);
label[i].setForeground(Color.yellow);
label[i].setOpaque(true);
panel.add(label[i]);
}
return panel;
}
}

How to put 1 JPanel and 1 Custom JPanel in a JFrame

I want to have 2 JPanels in my app, side by side. One that will have some info about my custom board on the right and one about painting that custom board on the left. The first JPanel is a classic, but the second is a custom panel. It seems that im having problems with putting my custom panel into the frame.
I've created a class named BoardPanel within my gui class to draw my custom board. I don't know if this is the best approach. Should i create a separate class instead?
This is the code of the gui class:
public class BattleshipGUI extends JFrame {
private BoardPanel mainPanel;
///////////////////////////////////////////////////////////////////////////////////////////////
// Create my frame
///////////////////////////////////////////////////////////////////////////////////////////////
public BattleshipGUI() {
JPanel container = new JPanel(new BorderLayout()); //the container panel that contains the 2 other panels
mainPanel = new BoardPanel(); //main panel with my custom painting
JPanel detailsPanel = new JPanel(new BorderLayout()); //secondary panel with various details about the game
container.add(mainPanel, BorderLayout.CENTER); //add the 2 panels in the container
container.add(detailsPanel, BorderLayout.EAST);
this.add(container); //add container to my frame
//this.setContentPane(container);
this.setIconImage(Toolkit.getDefaultToolkit().getImage(BattleshipGUI.class.getResource("/resources/battleship_128.png")));
this.setTitle("My Battleship Game");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//this.setBounds(100, 100, 850, 700);
//this.pack();
this.setSize(850, 600);
this.setVisible(true);
}
And this is the code of the inner class for the custom painting
class BoardPanel extends JPanel {
private static final int ROWS = 20;
private static final int COLUMNS = 20;
public void paintComponent(Graphics g) {
super.paintComponent(g);
int sqSize = this.getHeight()/ROWS;
for(int i=0; i<ROWS; i++) {
for(int j=0; j<COLUMNS; j++) {
int x = j * sqSize;
int y = i * sqSize;
g.drawRect(x, y, sqSize, sqSize);
}
}
}
}
Aside from all these, i have a question. If i want to have a custom painting, is it possible to work along side with the WindowsBuilderPro? I begun using that tool at first. But, i saw that i cant draw something custom with the tool and i had to write code to do that. Is it possible to write code for a custom paint AND use the tool at the same time for different purposes, like adding a simple text label, or even to edit that custon paint? The expected result that i want to see, appears when i run the program. My frame with the two panels. But when i open the WindowsBuilderPro, my custom panel does not appear and the result is a bit wrong. Thit is the reason why i have a question about my approach and if i can write code and use the tool at the same time. Thank you and sorry for the long text guys. I am too confused about this.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
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 {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = gbc.BOTH;
gbc.gridx = 0;
gbc.gridy = 0;
JPanel filler = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 300);
}
};
filler.setBackground(Color.BLUE);
add(filler, gbc);
gbc.gridx++;
add(new BoardPanel(), gbc);
}
}
class BoardPanel extends JPanel {
private static final int ROWS = 20;
private static final int COLUMNS = 20;
private int sqSize = 20;
#Override
public Dimension getPreferredSize() {
return new Dimension(COLUMNS * sqSize, ROWS * sqSize);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLUMNS; j++) {
int x = j * sqSize;
int y = i * sqSize;
g.drawRect(x, y, sqSize, sqSize);
}
}
}
}
}
Take the time to read through Laying Out Components Within a Container to get a better understanding how the layout management API works

How to force component in JScrollPane to show

I have a problem with showing specific component placed in JScrollPane. I have horizontal JScrollPane with GridLayout(1,0) and it contains variable number of JPanels - each containing image. It's like a preview of frames in GIF image. I use button to move among these JPanels (by changing borders and keeping index of chosen one), but I don't know how to force JScrollPane to show me JPanel if it's chosen (and center it if possible).
So I want this
force to do this:
Thanks in advance!
EDIT: almost working code with scrollRectToVisible() method
public class MiniatursPanel extends JPanel{
private int indexOfChosenFrame = 0;
private ArrayList<JPanel> frames;
private JScrollPane scrollPane;
private JPanel innerPanel;
public MiniatursPanel(){
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(),BorderFactory.createLoweredBevelBorder()));
setPreferredSize(new Dimension(1200,170));
setLayout(null);
}
public void initialize(){
int width = GifImageStats.getInstance().getWidth();
int height = GifImageStats.getInstance().getHeight();
int numberOfFrames = GifImageStats.getInstance().getNumberOfFrames();
frames = new ArrayList(numberOfFrames);
for (int i = 0; i < numberOfFrames; i++) {
JPanel frameBox = new JPanel();
frameBox.setLayout(new FlowLayout(FlowLayout.CENTER));
JButton button = new JButton(String.valueOf(i+1));
button.setPreferredSize(new Dimension(2*width,2*height));
button.setBackground(Color.white);
button.setFocusable(false);
frameBox.add(button);
frames.add(frameBox);
}
innerPanel = new JPanel();
innerPanel.setLayout(new GridLayout(1,0,10,10));
for (JPanel button : frames) {
innerPanel.add(button);
}
scrollPane = new JScrollPane(innerPanel);
scrollPane.setBounds(10, 10, 1180, 145);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
highlightFrame(frames.get(0));
add(scrollPane);
}
public void nextFrame(){
if (indexOfChosenFrame == frames.size() - 1) {
unhighlightFrame(frames.get(indexOfChosenFrame));
indexOfChosenFrame = 0;
highlightFrame(frames.get(0));
}else{
unhighlightFrame(frames.get(indexOfChosenFrame));
indexOfChosenFrame++;
highlightFrame(frames.get(indexOfChosenFrame));
}
}
public void previousFrame(){
if (indexOfChosenFrame == 0) {
unhighlightFrame(frames.get(0));
indexOfChosenFrame = frames.size()-1;
highlightFrame(frames.get(indexOfChosenFrame));
}else{
unhighlightFrame(frames.get(indexOfChosenFrame));
indexOfChosenFrame--;
highlightFrame(frames.get(indexOfChosenFrame));
}
}
private void highlightFrame(JPanel frame){
Rectangle rect = frame.getBounds();
rect.setBounds(frame.getX()-550, frame.getY(), frame.getWidth()+1050, frame.getHeight());
innerPanel.scrollRectToVisible(rect);
frame.setBorder(BorderFactory.createLineBorder(Color.red,2));
}
private void unhighlightFrame(JPanel frame){
frame.setBorder(null);
}
The relevant method here is JComponent#scrollRectToVisible(Rectangle). It has to be called on the component that is in the viewport of the scroll pane. (In your case, this is the panel with the grid layout, which contains the other sub-panels).
The rectangle that is passed to this method can be the bounds of one sub-panel. In this case, the scoll pane will do the "minimum" scrolling that is necessary to make the given rectangle visible. If you want to make sure that the respective sub-panel is in the center, then you can increase the size of this rectangle - that is, you define a rectangle in a way that the desired sub-panel will be in the center.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class ScrollToVisible
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int n = 20;
final JPanel panel = new JPanel(new GridLayout(1,0));
final List<JComponent> components = new ArrayList<JComponent>();
for (int i=0; i<n; i++)
{
JComponent component = new JLabel(String.valueOf(i), SwingConstants.CENTER);
component.setPreferredSize(new Dimension(100,100));
component.setBorder(BorderFactory.createLineBorder(Color.BLACK));
components.add(component);
panel.add(component);
}
final JScrollPane scrollPane = new JScrollPane(panel);
final JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, n-1, 1));
spinner.addChangeListener(new ChangeListener()
{
JComponent selectedComponent = components.get(0);
#Override
public void stateChanged(ChangeEvent e)
{
selectedComponent.setBorder(BorderFactory.createLineBorder(Color.BLACK));
int index = (Integer)spinner.getValue();
JComponent component = components.get(index);
Rectangle bounds = component.getBounds();
// This would make the component "just" visible:
//panel.scrollRectToVisible(bounds);
// This will center the component:
int cx = bounds.x + bounds.width / 2;
int w = scrollPane.getViewport().getWidth();
Rectangle r = new Rectangle(cx-w/2, bounds.y, w, bounds.height);
panel.scrollRectToVisible(r);
selectedComponent = component;
selectedComponent.setBorder(BorderFactory.createLineBorder(Color.RED));
}
});
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(scrollPane, BorderLayout.CENTER);
f.getContentPane().add(spinner, BorderLayout.NORTH);
f.setSize(800, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
EDIT: You should NOT use setLayout(null), and you should not do manual calls to setBounds, and you should rarely use setPreferredSize. And... when you post code that already is so close to a https://stackoverflow.com/help/mcve (or even was created from a runnable example of another post) then you should make it really runnable. It's annoying to re-insert the boilerplate code and waste some time with debugging until you realize that initialize() is not called at all...
However, change the code according to this:
private void highlightFrame(JPanel frame){
Rectangle rect = frame.getBounds();
int c = rect.x + rect.width / 2;
int w = scrollPane.getViewport().getWidth();
int x = c-w/2;
rect.setBounds(x, rect.y, w, rect.height);
innerPanel.scrollRectToVisible(rect);
frame.setBorder(BorderFactory.createLineBorder(Color.red,2));
}
private void unhighlightFrame(JPanel frame){
frame.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
The most important thing is to make sure that the size of the components is correct, by setting an empty border with the same size as the "highlighting" border.

Change color of jpanels after their creation

I am trying to make a GUI maze game, where as the computer tries to solve the maze, it changes the colors of the point in the maze it is on. The maze is made up of a JFrame with a JPanel (GridLayout). In the grid is the JPanels that I need to change their colors. I'm not sure how to even access them after I create them.
My code:
public Maze(int length) {
JFrame frame = new JFrame();
JPanel panel = new JPanel(new GridLayout(length, length, 5,5));
panel.setPreferredSize(new Dimension(500, 500));
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
JPanel p2 = new JPanel();
p2.setBackground(Color.red);
panel.add(p2);
}
}
frame.setDefaultCloseOperation(3);
frame.setTitle("Maze Game");
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
}
Is there a way to change the color of p2 in a different method? Or is there a better way to do it?
If your have the referee of JFrame then you can do it in this way.
int count = 0;
for (Component comp : frame.getContentPane().getComponents()) {
System.out.println(comp.getBackground());
if (count == 6) {
comp.setBackground(Color.GREEN);
}
count++;
}
Here 6 represent 2nd row and 3rd column as in the same order the JPanel are added in JFrame.
Complete Sample Code [EDITED]
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class Maze {
private JFrame frame = null;
public Maze(int length) {
frame = new JFrame();
JPanel panel = new JPanel(new GridLayout(length, length, 5, 5)) {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
};
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
JPanel p2 = new JPanel();
p2.setBackground(Color.red);
panel.add(p2);
}
}
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Maze Game");
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void setPanelColor(int index) {
frame.getContentPane().getComponents()[index].setBackground(Color.GREEN);
}
public static void main(String[] a) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
Maze maze = new Maze(4);
maze.setPanelColor(6);
}
});
}
}
Edits:
EventQueue.invokeLater()
All GUI related things in java should always go through a single thread. The thread is our legendary AWT-EventQueue . Hence all GUI related actions should necessarily go through the AWT Event thread. If not so you may end up in a deadlock.
Read more Should we use EventQueue.invokeLater for any GUI update in a Java desktop application?
UIManager.setLookAndFeel()
UIManager manages the current look and feel, the set of available look and feels.
Read more How to Set the Look and Feel
JComponent#getPreferredSize()
Read more Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?

JScrollPane resize containing JPanel when scrollbars appear

I have a small problem when using JScrollPane in my Java application.
I have a JScrollPane containing a JPanel.
This JPanel is dynamically updated with buttons (vertically ordered) that can be of any width.
The JPanel automatically adjusts its width to the largest JButton component inside.
Now when the vertical scrollbar appears, it takes away some space on the right side of my JPanel, which causes the largest buttons not to appear completely. I don't want to use a horizontal scrollbar in addition to display the whole button.
Is there a way to resize my JPanel when a scrollbar appears, so it appears nicely next to my buttons? Or is there any other way to have the scrollbar appear next to my JPanel?
Thanks in advance!
EDIT: Here is a demo of my problem. When you resize the window to a smaller height, a little part of the buttons on the right side gets covered.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.GridLayout;
/**
* #author Dylan Kiss
*/
public class Demo {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame myFrame = new JFrame("Demo");
JPanel sideBar = new JPanel();
JPanel centerPanel = new JPanel();
centerPanel.add(new JLabel("This is the center panel."));
JPanel buttonContainer = new JPanel();
JButton myButton = null;
for (int i = 0; i < 20; i++) {
buttonContainer.setLayout(new GridLayout(20, 1, 0, 0));
myButton = new JButton("This is my button nr. " + i);
buttonContainer.add(myButton);
}
sideBar.setLayout(new BorderLayout(0, 0));
JScrollPane scrollPane = new JScrollPane(buttonContainer);
sideBar.add(scrollPane);
myFrame.getContentPane().add(sideBar, BorderLayout.WEST);
myFrame.getContentPane().add(centerPanel, BorderLayout.CENTER);
myFrame.setLocationByPlatform(true);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.pack();
myFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
Here is a simple, not pretty, solution:
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
EDIT:
I thought that might not do the job in your case. Here is a better solution although it has quite a lot of boilerplate:
private class ButtonContainerHost extends JPanel implements Scrollable {
private static final long serialVersionUID = 1L;
private final JPanel buttonContainer;
public ButtonContainerHost(JPanel buttonContainer) {
super(new BorderLayout());
this.buttonContainer = buttonContainer;
add(buttonContainer);
}
#Override
public Dimension getPreferredScrollableViewportSize() {
Dimension preferredSize = buttonContainer.getPreferredSize();
if (getParent() instanceof JViewport) {
preferredSize.width += ((JScrollPane) getParent().getParent()).getVerticalScrollBar()
.getPreferredSize().width;
}
return preferredSize;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return orientation == SwingConstants.HORIZONTAL ? Math.max(visibleRect.width * 9 / 10, 1)
: Math.max(visibleRect.height * 9 / 10, 1);
}
#Override
public boolean getScrollableTracksViewportHeight() {
if (getParent() instanceof JViewport) {
JViewport viewport = (JViewport) getParent();
return getPreferredSize().height < viewport.getHeight();
}
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return orientation == SwingConstants.HORIZONTAL ? Math.max(visibleRect.width / 10, 1)
: Math.max(visibleRect.height / 10, 1);
}
}
It implements Scrollable to get full control of scrolling, does a fancy trick with tracking the viewport height to ensure the buttons expand when the space is available and adds on the width of the vertical scroll bar to the preferred width at all times. It could expand when the vertical scroll bar is visible but that looks bad anyway. Use it like this:
scrollPane = new JScrollPane(new ButtonContainerHost(buttonContainer));
It looks to me like this workaround is required because of a possible bug in javax.swing.ScrollPaneLayout:
if (canScroll && (viewSize.height > extentSize.height)) {
prefWidth += vsb.getPreferredSize().width;
}
Here extentSize is set to the preferred size of the viewport and viewSize is set to viewport.getViewSize(). This does not seem correct, AFAIK the size of the view inside the viewport should always equal the preferred size. It seems to me that the view size should be compared to the actual size of the viewport rather than its preferred size.
A simple workaround to meet your demands regarding
Is there a way to resize my JPanel when a scrollbar appears, so it
appears nicely next to my buttons?
is the use of EmptyBorder, this will let you achieve what you feel like, should happen, as shown in the image below :
I just added this line written below after this line JPanel buttonContainer = new JPanel();
ADDED LINE
buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
Here is your code with that added line :
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.GridLayout;
/**
* #author Dylan Kiss
*/
public class Demo
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
try
{
JFrame myFrame = new JFrame("Demo");
JPanel sideBar = new JPanel();
JPanel centerPanel = new JPanel();
centerPanel.add(new JLabel("This is the center panel."));
JPanel buttonContainer = new JPanel();
buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JButton myButton = null;
for (int i = 0; i < 20; i++)
{
buttonContainer.setLayout(new GridLayout(20, 1, 0, 0));
myButton = new JButton("This is my button nr. " + i);
buttonContainer.add(myButton);
}
sideBar.setLayout(new BorderLayout(0, 0));
JScrollPane scrollPane = new JScrollPane(buttonContainer);
sideBar.add(scrollPane);
myFrame.getContentPane().add(sideBar, BorderLayout.WEST);
myFrame.getContentPane().add(centerPanel, BorderLayout.CENTER);
myFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
myFrame.pack();
myFrame.setLocationByPlatform(true);
myFrame.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
You could resize the JPanel by calling setPreferredSize when the JPanel needs to be resized.
buttonContainer.setPreferredSize(Dimension d);

Categories