How to fill left over space regarding Layout Managers? - java

I recently have been developing a Swing application. I have run into a problem with Layout managers. I can't seem to figure out how to make components in the layout grow all the way to the edge of their parent container. Let me explain.
Say I have 8 buttons all in one row. Depending on the window size will determine if they take up all the space. GBL I have found centers so both space on left and right. BoxLayout usually space on the right side. This is probably due their anchors or alignment.
I think the problem is because the Layouts when all components have same settings it tries to give each component same space. So that little extra space can't be divided up equally to each component they leave it out.
I was wondering if there was a work around for this. Like the space is so small I was hoping there was a way to make last component eat it up or divide it best it can between the components.
Here is example code showing the problem. Note when you resize the panel you get extra space.
public class LeftoverExample {
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
public void run(){
LeftoverExample.createGUI();
}
});
}
public static void createGUI(){
JFrame jF = new JFrame();
jF.setSize(new Dimension(1333,500));
jF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create ContentPane
JPanel contentPane = new JPanel();
contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.X_AXIS));
GridBagLayout gBL = new GridBagLayout();
gBL.columnWidths = new int[]{0};
gBL.rowHeights = new int[]{50, 50, 50 , 50};
contentPane.setLayout(gBL);
//Initial Constraints
GridBagConstraints gBC = new GridBagConstraints();
gBC.fill = GridBagConstraints.BOTH;
gBC.gridx = 0;
gBC.gridy = 0;
gBC.weightx = 1;
gBC.weighty = 0;
gBC.insets = new Insets(10, 0, 10, 0);
//Add Examples to ContentPane
contentPane.add(LeftoverExample.createGBL(false), gBC);
gBC.gridy++;
contentPane.add(LeftoverExample.createGBL(true), gBC);
gBC.gridy++;
contentPane.add(LeftoverExample.createBoxLayout(false), gBC);
gBC.gridy++;
contentPane.add(LeftoverExample.createBoxLayout(true), gBC);
//Final
jF.setContentPane(contentPane);
jF.setVisible(true);
}
private static JComponent createGBL(boolean addButtons){
//GBL Example
JLabel gBLJLabel = new JLabel("GridBagLayout");
gBLJLabel.setVerticalAlignment(SwingConstants.CENTER);
gBLJLabel.setLayout(new BoxLayout(gBLJLabel, BoxLayout.X_AXIS));
gBLJLabel.setBackground(Color.CYAN);
gBLJLabel.setOpaque(true);
gBLJLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
GridBagLayout gBL = new GridBagLayout();
gBL.columnWidths = new int[]{0};
gBL.rowHeights = new int[]{50};
gBLJLabel.setLayout(gBL);
//Initial Constraints
GridBagConstraints gBC = new GridBagConstraints();
gBC.fill = GridBagConstraints.BOTH;
gBC.gridx = 0;
gBC.gridy = 0;
gBC.weightx = 1;
gBC.weighty = 0;
gBC.insets = new Insets(0, 0, 0, 0);
//Add to GBL Panel
if(addButtons){
LeftoverExample.addButtons(gBLJLabel, gBC);
LeftoverExample.addButtons(gBLJLabel, gBC);
LeftoverExample.addButtons(gBLJLabel, gBC);
LeftoverExample.addButtons(gBLJLabel, gBC);
LeftoverExample.addButtons(gBLJLabel, gBC);
LeftoverExample.addButtons(gBLJLabel, gBC);
}
return gBLJLabel;
}
private static JComponent createBoxLayout(boolean addButtons){
//BoxLayout Example
JLabel boxLayoutJL = new JLabel("BOX_LAYOUT");
boxLayoutJL.setVerticalAlignment(SwingConstants.CENTER);
boxLayoutJL.setLayout(new BoxLayout(boxLayoutJL, BoxLayout.X_AXIS));
boxLayoutJL.setBackground(Color.GREEN);
boxLayoutJL.setOpaque(true);
boxLayoutJL.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
//Add to BoxLayout Panel
if(addButtons){
LeftoverExample.addButtons(boxLayoutJL);
LeftoverExample.addButtons(boxLayoutJL);
LeftoverExample.addButtons(boxLayoutJL);
}
return boxLayoutJL;
}
private static JButton createButton(Color c){
JButton jB = new JButton();
jB.setBackground(c);
jB.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
return jB;
}
private static void addButtons(JComponent jC, GridBagConstraints gBC){
//Create Buttons
Color[] colorA = {Color.RED, Color.BLUE, Color.BLACK, Color.GREEN};
for(Color c : colorA){
jC.add(LeftoverExample.createButton(c), gBC);
gBC.gridx++;
}
}
private static void addButtons(JComponent jC){
//Create Buttons
Color[] colorA = {Color.BLUE, Color.BLACK, Color.GREEN, Color.RED};
for(Color c : colorA){
jC.add(LeftoverExample.createButton(c));
}
}
}
See how each West and East side there is some space left that the parent (in this case JLabel) takes up but the buttons don't. I want to be able to have the buttons take up that space as well.
Picture showing example:

The problem is caused by Swing using integer values for dimensions rather than double.
Taking this fact into consideration, the remainder r of the division of the containers width divided by the number of Component (in your case JButton objects) objects it contains can be used to increase the size of the first r Component objects by 1 to compensate. Obviously this means the first r Component objects will be +1 larger than the other Components, but this should not be noticeable.
In order to update the width of the Component objects we need to have access to there container (e.g. JPanel) and all the Component objects we wish to update. In my example, I will use a List for this purpose.
Here is a method to do the work of resizing the Component objects accordingly.
private static void fixComponentWidths(Component container,
List<? extends Component> componentList, int componentHeight) {
if (!componentList.isEmpty()) { // Avoid possible division by zero
// get the desired component width for the container using integer division
int baseComponentWidth = container.getWidth() / componentList.size();
// find the remainder
int remainder = container.getWidth() % componentList.size();
// update all the components
for (int i = 0; i < componentList.size(); i++) {
// the component width will be the base width plus 1 iff i < remainder
int componentWidth = baseComponentWidth;
if (i < remainder) {
componentWidth++;
}
// update the maximum size
componentList.get(i).setMaximumSize(new Dimension(componentWidth, componentHeight));
}
// be sure to revalidate otherwise it may not work
container.revalidate();
}
}
In order for this to work on resize, a ComponentListener must be implemented for our container. This could either be the JFrame or just a JPanel (as per my example). Note, only the componentResized(ComponentEvent) method needs implementing for this task.
buttonContainer.addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent ce) { // just implementing this
fixComponentWidths(buttonContainer, buttons, BUTTON_HEIGHT);
// where buttonContainer is a JPanel,
// buttons is a List of JButtons
// BUTTON_HEIGHT, well the height of the button!
}
#Override
public void componentMoved(ComponentEvent ce) { // not needed
}
#Override
public void componentShown(ComponentEvent ce) { // not needed
}
#Override
public void componentHidden(ComponentEvent ce) { // not needed
}
});
That is all that is needed. But for completeness here's a small example, based on the author's question, followed by a subclass of JPanel which uses a BoxLayout that can be used to resolve this behavior for both BoxLayout.X_AXIS and BoxLayout.Y_AXIS.
Complete example
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class FillExample extends JFrame {
private static final int FRAMEL_DEFAULT_WIDTH = 700;
private static final int FRAME_DEFAULT_HEIGHT = 400;
private static final int BUTTON_HEIGHT = Integer.MAX_VALUE;
private final List<JButton> buttons;
public FillExample() {
buttons = new ArrayList<>();
}
public void createAndShow() {
setTitle("Fill Example");
setSize(FRAMEL_DEFAULT_WIDTH, FRAME_DEFAULT_HEIGHT);
final JPanel buttonContainer = new JPanel();
buttonContainer.setLayout(new BoxLayout(buttonContainer, BoxLayout.X_AXIS));
for (int i = 0; i < 3; i++) {
addButtons(buttonContainer);
}
getContentPane().add(buttonContainer);
buttonContainer.addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent ce) {
fixComponentWidths(buttonContainer, buttons, BUTTON_HEIGHT);
}
#Override
public void componentMoved(ComponentEvent ce) {
}
#Override
public void componentShown(ComponentEvent ce) {
}
#Override
public void componentHidden(ComponentEvent ce) {
}
});
setVisible(true);
}
private static void fixComponentWidths(Component container, List<? extends Component> componentList, int componentHeight) {
if (!componentList.isEmpty()) {
int baseComponentWidth = container.getWidth() / componentList.size();
int remainder = container.getWidth() % componentList.size();
for (int i = 0; i < componentList.size(); i++) {
int componentWidth = baseComponentWidth;
if (i < remainder) {
componentWidth++;
}
componentList.get(i).setMaximumSize(new Dimension(componentWidth, componentHeight));
}
container.revalidate();
}
}
private void addButtons(JComponent component) {
Color[] colorA = {Color.RED, Color.BLUE, Color.BLACK, Color.GREEN};
for (Color c : colorA) {
JButton button = createButton(c);
buttons.add(button);
component.add(button);
}
}
private static JButton createButton(Color color) {
JButton button = new JButton();
button.setBackground(color);
button.setMaximumSize(new Dimension(Integer.MAX_VALUE, BUTTON_HEIGHT));
return button;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new FillExample().createAndShow();
}
});
}
}
FillBoxLayoutPanel
This small class can be used to quickly resolve this spacing issue for both BoxLayout.X_AXIS and BoxLayout.Y_AXIS. Note, that the class create the BoxLayout and the LayoutManager cannot be changed.
Component objects can be added to the panel using add(Component comp) and add(Component comp, int index). Note, not all add methods are overridden, the class should be used carefully.
import java.awt.Component;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
public class FillBoxLayoutPanel extends JPanel {
public static final int X_AXIS = BoxLayout.X_AXIS;
public static final int Y_AXIS = BoxLayout.Y_AXIS;
private final List<Component> components;
private final int direction;
private boolean layoutSet;
public FillBoxLayoutPanel(int direction) {
components = new ArrayList<>();
this.direction = direction;
setLayout(new BoxLayout(this, direction));
layoutSet = true;
addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent ce) {
adjustComponents();
}
#Override
public void componentMoved(ComponentEvent ce) {
}
#Override
public void componentShown(ComponentEvent ce) {
}
#Override
public void componentHidden(ComponentEvent ce) {
}
});
}
#Override
public void setLayout(LayoutManager mgr) {
if (layoutSet) {
throw new UnsupportedOperationException("FillPanel's layout manager cannot be changed.");
} else {
super.setLayout(mgr);
}
}
#Override
public Component add(Component comp) {
comp = super.add(comp);
components.add(comp);
return comp;
}
#Override
public Component add(Component comp, int i) {
comp = super.add(comp, i);
components.add(i, comp);
return comp;
}
private void adjustComponents() {
if (!components.isEmpty()) {
int size = direction == X_AXIS ? getWidth() : getHeight();
int baseComponentSize = size / components.size();
int remainder = size % components.size();
for (int i = 0; i < components.size(); i++) {
int componentSize = baseComponentSize;
if (i < remainder) {
componentSize++;
}
Dimension dimension;
if (direction == X_AXIS) {
dimension = new Dimension(componentSize, components.get(i).getHeight());
} else {
dimension = new Dimension(components.get(i).getWidth(), componentSize);
}
components.get(i).setMaximumSize(dimension);
}
revalidate();
}
}
}

I think the problem is because the Layouts when all components have same settings it tries to give each component same space. So that little extra space can't be divided up equally to each component they leave it out.
Maybe you can use the Relative Layout.
It allows you to easily make each component the same size.
It then has a property that allows you to determine how extra pixels should be allocated if needed.

Related

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 redraw JPanels inside BorderLayout

Inside my borderlayout, i have JPanels which are referenced by the array theGrid. In a separate function, I want to change the look of a JPanel, so I change how the specific JPanel should be painted. The problem im having is that i dont know how i now make it paint this new version of the JPanel?
I have looked at other questions and have tried using .revalidate, .validate, .repaint(), etc on mainPanel or on contentPane, or on both but none will get the new version of the JPanel i created to be drawn on.
In the constructor below I setup the grid and how it will fit into the rest of the JFrame
public class GraphicDisplay extends JFrame {
private static int ROWS = 6;
private static int COLS = 7;
private JPanel[][] theGrid = new JPanel[ROWS][COLS];
private JPanel mainPanel = new JPanel(new GridLayout(ROWS, COLS));
private Container contentPane;
public GraphicDisplay() {
for (int i = 0; i < theGrid.length; i++) { //Initialize theGrid (with blanks)
for (int j = 0; j < theGrid[i].length; j++) {
theGrid[i][j] = new JPanel();
}
}
//add them to the JFrame
contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
JPanel boardElements = new JPanel();
boardElements.setLayout(new BoxLayout(boardElements, BoxLayout.Y_AXIS)); //vertical layout for the two parts, theGrid itself and then the
// button which goes underneath,
final int SPACE = 3;
final Color COLORCHOICE = Color.BLACK;
mainPanel.setBorder(BorderFactory.createEmptyBorder(SPACE, SPACE, SPACE, SPACE));
mainPanel.setBackground(COLORCHOICE);
JPanel[][] panels = new JPanel[ROWS][COLS];
for (int i = 0; i < panels.length; i++) {
for (int j = 0; j < panels[i].length; j++) {
panels[i][j] = new JPanel(new GridLayout(1, 1, 1, 1));
panels[i][j].setBackground(COLORCHOICE);
panels[i][j].setBorder(BorderFactory.createEmptyBorder(SPACE, SPACE, SPACE, SPACE));
mainPanel.add(panels[i][j]);
panels[i][j].add(theGrid[i][j]);
}
}
//adding the grid to the vertical layout
boardElements.add(mainPanel);
//adding the button which will go directly under the grid
boardElements.add(new JButton("Button"));
contentPane.add(boardElements, BorderLayout.CENTER);
}
The function below which is in the same class is suppose to update the grid by adding a dot at the point 3,3 (but the problem i don't see any visible change)
public void addDotToGrid() {
//theGrid[3][3] reference was added to the panels array which is part of the layout, so I would of thought by changing the value of it here would then change this JPanel on the UI
theGrid[3][3] = new JPanel() {
public void paintComponent( Graphics g) {
//g.setColor(Color.RED);
int y = 0;
int diameter = getWidth() -10;
int x = (getWidth()/2) - (diameter/2);
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
// Assume x, y, and diameter are instance variables.
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter);
g2d.fill(circle);
}
};
}
Main method in another class where GraphicDisplay object is created
public static void main(String[] args) {
GraphicDisplay display2 = new GraphicDisplay();
display2.setSize(600, 600);
display2.setVisible(true);
display2.addDotToGrid();
}
The problem is the grid is displayed but the addDotToGrid() does not change anything and there isn't a dot added to the gird
You're changing a JPanel held by your theGrid array, but this has no effect on the JPanels displayed in the GUI, and this gets to the key difference between a variable and a reference or object -- changing the object that a variable refers to has no effect on the object that it previously referred to. Your solution is to change the JPanel held by the grid.
One way to do this is to give all of them paintComponent methods that draw what you want, but have them controlled by a boolean, and then change the boolean variable of the panel of interest.
If this were my GUI though, I'd create a grid of JLabels and simply swap ImageIcons where and when I want -- keep it simple as possible!
For example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyGridEg extends JPanel {
private static final int SPACE = 6;
private static final int ROWS = 6;
private static final int COLS = 7;
private static final int IMG_W = 80;
private static final int SML_GAP = 3;
private static final Color IMG_BACKG = new Color(240, 240, 240);
private static final String TITLE = "Click on a Cell";
private JLabel[][] labelGrid = new JLabel[ROWS][COLS];
private Icon blankIcon = createIconDisk(new Color(0, 0, 0, 0));
private Icon redIcon = createIconDisk(Color.RED);
public MyGridEg() {
MyMouse myMouse = new MyMouse();
JPanel gridHolder = new JPanel(new GridLayout(ROWS, COLS, SPACE, SPACE));
gridHolder.setBackground(Color.BLACK);
for (int i = 0; i < labelGrid.length; i++) {
for (int j = 0; j < labelGrid[i].length; j++) {
JLabel label = new JLabel(blankIcon);
label.addMouseListener(myMouse);
labelGrid[i][j] = label;
gridHolder.add(label);
}
}
gridHolder.setBorder(BorderFactory.createLineBorder(Color.black, SPACE));
JLabel titleLabel = new JLabel(TITLE, SwingConstants.CENTER);
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 20));
JButton clearButton = new JButton(new ClearAction("Clear"));
JPanel btnPanel = new JPanel();
btnPanel.add(clearButton);
setLayout(new BorderLayout());
add(gridHolder, BorderLayout.CENTER);
add(btnPanel, BorderLayout.PAGE_END);
add(titleLabel, BorderLayout.PAGE_START);
}
private Icon createIconDisk(Color color) {
BufferedImage img = new BufferedImage(IMG_W, IMG_W, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setBackground(IMG_BACKG);
g2.clearRect(0, 0, IMG_W, IMG_W);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
int x = SML_GAP;
int y = x;
int width = IMG_W - 2 * x;
int height = IMG_W - 2 * y;
g2.fillOval(x, y, width, height);
g2.dispose();
return new ImageIcon(img);
}
private class MyMouse extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selected = (JLabel) e.getSource();
Icon icon = selected.getIcon() == blankIcon ? redIcon : blankIcon;
selected.setIcon(icon);
}
}
private class ClearAction extends AbstractAction {
public ClearAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
for (JLabel[] labelRow : labelGrid) {
for (JLabel cell : labelRow) {
cell.setIcon(blankIcon);
}
}
}
}
private static void createAndShowGui() {
MyGridEg mainPanel = new MyGridEg();
JFrame frame = new JFrame("MyGridEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
The key to this code is that I'm not swapping out JLabels or JPanels but rather I'm keeping the JLabels the same, but am changing their state. This way if I change the state of the JLabels held by the 2D array, labelGrid, this is reflected by changes in the view, as I do in the ClearAction class that the clearButton JButton calls:
private class ClearAction extends AbstractAction {
public ClearAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
for (JLabel[] labelRow : labelGrid) {
for (JLabel cell : labelRow) {
cell.setIcon(blankIcon);
}
}
}
}

Jprogressbar.setStringpainted(true); is painting two strings

This code is creating a problem that is when I click the button two strings are being painted one horizontal and one vertical, but need only horizontal string to be painted, so please tell what should I do???
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
public class R implements ActionListener {
static int y;
CustomProgressBar b = new CustomProgressBar();
public static void main(String arg[]) throws Exception {
new R();
}
public R() throws Exception {
JFrame f = new JFrame();
JButton btn = new JButton("Click");
f.setExtendedState(JFrame.MAXIMIZED_BOTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setUndecorated(true);
f.setLayout(new FlowLayout());
btn.addActionListener(this);
f.add(b);
f.add(btn);
f.setVisible(true);
}
class CustomProgressBar extends JProgressBar{
private static final long serialVersionUID = 1L;
private boolean isStringToBePainted = false;
public CustomProgressBar() {
super(JProgressBar.VERTICAL,0,100);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(isStringToBePainted ) {
Dimension size = CustomProgressBar.this.getSize();
if( CustomProgressBar.this.getPercentComplete()<0.9 )
R.y = (int)( size.height - size.height * CustomProgressBar.this.getPercentComplete() );
String text = getString();
g.setColor(Color.BLACK );
g.drawString(text, 0, R.y);
}
}
#Override
public void setStringPainted(boolean b) {
super.setStringPainted(b);
isStringToBePainted=b;
}
}
#Override
public void actionPerformed(ActionEvent e) {
b.setStringPainted(true);
}
}
The problem is, the property setStringPainted is already defined within by the component and has a predefined functionality...which you know seem to want to change...
You will either, need to define a new property of your own which you can control OR change the way the UI delegate works, which is a lot of work as you will need to provide one for each look and feel you might want to support...
Instead of doing custom painting, you could cheat (a little)... and use a JLabel instead, for example...
class CustomProgressBar extends JProgressBar {
private static final long serialVersionUID = 1L;
private boolean isStringToBePainted = false;
private JLabel progress;
public CustomProgressBar() {
super(JProgressBar.VERTICAL, 0, 100);
progress = new JLabel("0%");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.weighty = 1;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.SOUTH;
add(progress, gbc);
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
Dimension labelSize = progress.getPreferredSize();
Insets insets = getInsets();
if (size.width < labelSize.width) {
size.width = insets.left + insets.right + labelSize.width;
}
return size;
}
}
You're still going to need to provide your own property to turn it on or off, but it's an idea...
(ps- I had a quick look at trying to implement my own UI delegate, after I copy and pasted my third method, I gave up, as it would just be more work then the reward would provide)

How to align multiple textfields on a background image within a JOptionPane?

I want to have multiple textfiled to be aligned in a customized order, so that they are on the top of the background image. I tried to use setBounds but it did not work:
import javax.swing.*;
public class TestJP {
public static void main(String[] args) {
JLabel myLabel = new JLabel();
myLabel.setIcon ( new ImageIcon("system\\myBackground.jpg"));
myLabel.setBounds(0,0,750,500);
JTextField login = new JTextField(5);
login.setBounds(50,50,20,100); // This does not work
JPasswordField password = new JPasswordField(5);
password.setBounds( 50, 70, 20, 100); // Doesn't help either
JPanel myPanel = new JPanel();
myPanel.add(myLabel);
myPanel.add(login);
myPanel.add(password);
int result = JOptionPane.showConfirmDialog(null, myPanel,
"Please Login", JOptionPane.OK_CANCEL_OPTION);
// etc
}
}
Don't use setBounds(). Swing was designed to be used with layout managers.
You can add the text fields to the label by doing something like:
JLabel myLabel = new JLabel( new ImageIcon("system\\myBackground.jpg") );
mylabel.setLayout( new FlowLayout() );
mylabel.add(login);
mylabel.add(password);
Use the appropriate layout manager for the label to get the desired layout.
"I tried to use setBounds but it did not work:"
Use the layout managers as that's what they're for. You can find the layout manager tutorial here.
Add a layout to the JLabel that is displaying the image.
Add your JTextFields to that JLabel.
Display the JLabel in your JOptionPane.
For more help, post an image of what you're trying to do.
Edit You state in a comment:
Thank You! I used setBounds many times in a contentPane ... It worked fine.
While as a newbie, you will find that setBounds and null layouts seem easier to work with, the longer you code with swing, the more involved your GUI's the more you'll find that null layouts will fail youy, will look terrible on different platform, will be inflexible and not allow you to update or improve your GUI later.
How come it doesn't work for JOptionPane? The regular Layout managers are not precise ... I do not want to use those: east, west
It has nothing to do with JOptionPane and all to do with how you're not creating your GUI correctly. Learn to use the layout managers and they will be easier to use.
Edit 2 For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.beans.Transient;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.*;
public class GridBagEg {
public static final String IMG_PATH = "https://duke.kenai.com/tshirts/.Midsize/Tshirt1997.png.png";
private static void createAndShowGui() {
BufferedImage img = null;
try {
URL imgUrl = new URL(IMG_PATH);
img = ImageIO.read(imgUrl);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
PlayerEditorPanel playerEditorPane = new PlayerEditorPanel(img);
int result = JOptionPane.showConfirmDialog(null, playerEditorPane,
"Edit Player", JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
// TODO: do something with info
for (PlayerEditorPanel.FieldTitle fieldTitle :
PlayerEditorPanel.FieldTitle.values()) {
System.out.printf("%10s: %s%n", fieldTitle.getTitle(),
playerEditorPane.getFieldText(fieldTitle));
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class PlayerEditorPanel extends JPanel {
enum FieldTitle {
NAME("Name"), SPEED("Speed"), STRENGTH("Strength");
private String title;
private FieldTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
};
private static final Insets WEST_INSETS = new Insets(5, 0, 5, 5);
private static final Insets EAST_INSETS = new Insets(5, 5, 5, 0);
private static final double SCALE = 0.4;
private Map<FieldTitle, JTextField> fieldMap = new HashMap<FieldTitle, JTextField>();
private BufferedImage backgroundImg = null;
private int imgWidth;
private int imgHeight;
public PlayerEditorPanel(BufferedImage img) {
this.backgroundImg = img;
imgWidth = (int) (backgroundImg.getWidth() * SCALE);
imgHeight = (int) (backgroundImg.getHeight() * SCALE);
setLayout(new GridBagLayout());
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Player Editor"),
BorderFactory.createEmptyBorder(5, 5, 5, 5)));
GridBagConstraints gbc;
for (int i = 0; i < FieldTitle.values().length; i++) {
FieldTitle fieldTitle = FieldTitle.values()[i];
gbc = createGbc(0, i);
JLabel fieldLabel = new JLabel(fieldTitle.getTitle() + ":",
JLabel.LEFT);
fieldLabel.setForeground(new Color(200, 10, 10));
fieldLabel.setFont(fieldLabel.getFont().deriveFont(Font.BOLD, 24f));
add(fieldLabel, gbc);
gbc = createGbc(1, i);
JTextField textField = new JTextField(10);
add(textField, gbc);
fieldMap.put(fieldTitle, textField);
}
}
#Override
#Transient
public Dimension getPreferredSize() {
if (backgroundImg != null) {
return new Dimension(imgWidth, imgHeight);
}
return super.getPreferredSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImg != null) {
g.drawImage(backgroundImg, 0, 0, imgWidth, imgHeight, this);
}
}
private GridBagConstraints createGbc(int x, int y) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.anchor = (x == 0) ? GridBagConstraints.WEST : GridBagConstraints.EAST;
gbc.fill = (x == 0) ? GridBagConstraints.BOTH
: GridBagConstraints.HORIZONTAL;
gbc.insets = (x == 0) ? WEST_INSETS : EAST_INSETS;
gbc.weightx = (x == 0) ? 0.1 : 1.0;
gbc.weighty = 1.0;
return gbc;
}
public String getFieldText(FieldTitle fieldTitle) {
return fieldMap.get(fieldTitle).getText();
}
}
Which displays a JOptionPane like so:

Why does Canvas as contentPane not paint opaque components that are added to the JFrame glasspane

Okay I have a Canvas (I am using this because it can implement BufferedStrategy).
I add my Canvas to my JFrame contentPane and than add HUDPanel ( which is a JPanel consisting of 2 other JPanels - with JProgressBars and JLabels - to show health and chakara bars) to my JFrames glassPane.
All of the components on HUD are opaque.
Now when I add the HUDPanel to my JFrames glassPane with a Canvas as its contentPane the 2 JPanels which hold components are not painted opaque:
But if I add a JPanel instead of Canvas to JFrame contentPane all works fine:
Why is this?
and how can I fix it?
Thank you in advance
HUDPanelTest.java
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class HUDPanelTest {
public HUDPanelTest() {
initComponents();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new HUDPanelTest();
}
});
}
private void initComponents() {
JFrame frame = new JFrame() {
#Override
public Dimension getPreferredSize() {//testing only
return new Dimension(800, 600);
}
};
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
HUDPanel hudPanel = new HUDPanel();
frame.setGlassPane(hudPanel);
JPanel p = new JPanel() {
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
grphcs.setColor(Color.yellow);
grphcs.fillRect(0, 0, getWidth(), getHeight());
}
};
Canvas c = new Canvas() {
#Override
public void paint(Graphics grphcs) {//for testing ONLY I actually use a BufferStrategy
super.paint(grphcs);
grphcs.setColor(Color.yellow);
grphcs.fillRect(0, 0, getWidth(), getHeight());
}
};
frame.add(c);
//frame.add(p);//this works as expected
frame.pack();
frame.setVisible(true);
hudPanel.setVisible(true);
}
}
HUDPanel.java
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class HUDPanel extends JPanel {
HUDPanel.HealthPanel healthPanel;
HUDPanel.ChakaraPanel chakaraPanel;
public HUDPanel() {
super(new BorderLayout(), true);
setOpaque(false);
healthPanel = new HUDPanel.HealthPanel();
chakaraPanel = new HUDPanel.ChakaraPanel();
initComponents();
setHealthPlayer1(100);
setHealthPlayer2(100);
setChakaraPlayer1(0);
setChakaraPlayer2(0);
setLabelTextHealthPlayer1("Player 1 (Health):");
setLabelTextHealthPlayer2("Player 2 (Health):");
setLabelTextChakaraPlayer1("Player 1 (Chakara):");
setLabelTextChakaraPlayer2("Player 2 (Chakara):");
}
private void initComponents() {
add(healthPanel, BorderLayout.NORTH);
add(chakaraPanel, BorderLayout.SOUTH);
}
public void setHealthPlayer1(int health) {
healthPanel.setPlayer1ProgressBarValue(health);
}
public void setHealthPlayer2(int health) {
healthPanel.setPlayer2ProgressBarValue(health);
}
public void setChakaraPlayer1(int chakara) {
chakaraPanel.setPlayer1ProgressBarValue(chakara);
}
public void setChakaraPlayer2(int chakara) {
chakaraPanel.setPlayer2ProgressBarValue(chakara);
}
public void setLabelTextHealthPlayer1(String text) {
healthPanel.setPlayer1LabelText(text);
}
public void setLabelTextHealthPlayer2(String text) {
healthPanel.setPlayer2LabelText(text);
}
public void setLabelTextChakaraPlayer1(String text) {
chakaraPanel.setPlayer1LabelText(text);
}
public void setLabelTextChakaraPlayer2(String text) {
chakaraPanel.setPlayer2LabelText(text);
}
private class HealthPanel extends JPanel {
JProgressBar player1ProgressBar = new JProgressBar();
JProgressBar player2ProgressBar = new JProgressBar();
JLabel player1Label = new JLabel();
JLabel player2Label = new JLabel();
public HealthPanel() {
super(new GridBagLayout(), true);
setOpaque(false);
initComponents();
}
private void initComponents() {
GridBagConstraints gc = new GridBagConstraints();
gc.fill = GridBagConstraints.NONE;
gc.anchor = GridBagConstraints.WEST;
gc.insets = new Insets(10, 10, 10, 10);
//or else the anchoring wont work
gc.weightx = 1;
//gc.gridx = 0;//does not seem to make a difference
//gc.gridy = 0;
add(player1Label, gc);
//gc.gridx = 1;
//gc.gridy = 0;
add(player1ProgressBar, gc);
gc.anchor = GridBagConstraints.EAST;
//gc.gridx = 2;
//gc.gridy = 0;
add(player2Label, gc);
//gc.gridx = 3;
//gc.gridy = 0;
add(player2ProgressBar, gc);
}
public void setPlayer1ProgressBarValue(int val) {
player1ProgressBar.setValue(val);
}
public void setPlayer2ProgressBarValue(int val) {
player2ProgressBar.setValue(val);
}
public void setPlayer2LabelText(String text) {
player2Label.setText(text);
}
public void setPlayer1LabelText(String text) {
player1Label.setText(text);
}
}
private class ChakaraPanel extends JPanel {
JProgressBar player1ProgressBar = new JProgressBar();
JProgressBar player2ProgressBar = new JProgressBar();
JLabel player1Label = new JLabel();
JLabel player2Label = new JLabel();
public ChakaraPanel() {
super(new GridBagLayout(), true);
setOpaque(false);
initComponents();
}
private void initComponents() {
GridBagConstraints gc = new GridBagConstraints();
gc.fill = GridBagConstraints.NONE;
gc.anchor = GridBagConstraints.WEST;
gc.insets = new Insets(10, 10, 10, 10);
//or else the anchoring wont work
gc.weightx = 1;
gc.weighty = 1;
//gc.gridx = 0;
//gc.gridy = 0;
add(player1Label, gc);
//gc.gridx = 1;
//gc.gridy = 0;
add(player1ProgressBar, gc);
gc.anchor = GridBagConstraints.EAST;
//gc.gridx = 2;
//gc.gridy = 0;
add(player2Label, gc);
//gc.gridx = 3;
//gc.gridy = 0;
add(player2ProgressBar, gc);
}
public void setPlayer1ProgressBarValue(int val) {
player1ProgressBar.setValue(val);
}
public void setPlayer2ProgressBarValue(int val) {
player2ProgressBar.setValue(val);
}
public void setPlayer2LabelText(String text) {
player2Label.setText(text);
}
public void setPlayer1LabelText(String text) {
player1Label.setText(text);
}
}
}
GlassPane is lightweight, then is always behind AWT Canvas,
this is quite common issue for JLayeredPane, GlassPane and Java and 3D, CAD/CAM, Open GL/Cl, these APIs are based on AWT Containers
same issue will be by using JLayer (lightweight container), but there you can to test isLightWe....() for peer from Native OS and/or its resources

Categories