How to put a JPanel with 2DGraphics on a JFrame (same class) - java

I have a class that extends JFrame and I would like to add two JPanels to that JFrame: a text panel and a graphics panel. My text panel is a panel containing a label with text. My graphics panel will contain a graph (created using 2DGraphics). I'm adding the graphic panel to the left side (0,0) and the text panel to right side (1,0) using a gridbaglayout method. However, the graphic panel won't show up in the frame. I have tried numerous methods to try to get the panel to show up with no success.
import java.awt.*;
import javax.swing.*;
public class GraphicsTest extends JFrame {
private final JPanel textPanel;
private final JLabel textLabel;
public GraphicsTest() {
textPanel = new JPanel(new BorderLayout());
textLabel = new JLabel("Home label");
this.setBounds(180, 112, 1080, 675);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(new GridBagLayout());
textPanel.add(textLabel, BorderLayout.NORTH);
addComponent(this, new GraphicPanel(), 0, 0, 1, 1, GridBagConstraints.CENTER);
addComponent(this, textPanel, 1, 0, 1, 1, GridBagConstraints.CENTER);
this.setVisible(true);
}
public class GraphicPanel extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public void paintComponents(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
// drawing lots of shapes and lines, removed for readibility
}
}
public void addComponent(JFrame frame, JComponent component, int x, int y, int width, int height, int align) {
GridBagConstraints c = new GridBagConstraints();
c.gridx = x;
c.gridy = y;
c.gridwidth = width;
c.gridheight = height;
c.weightx = 100.0;
c.weighty = 100.0;
c.insets = new Insets(5, 0, 5, 0);
c.anchor = align;
c.fill = GridBagConstraints.NONE;
frame.add(component, c);
}
public static void main(String[] args) {
GraphicsTest gui = new GraphicsTest();
}
}

Works fine for me when I use a size like (200, 200).
GridbagLayout works such that if a component can't be displayed at its preferred size, then it will be displayed at its minimum size, which would be (0, 0).
So whether (700, 700) works will depend on your screen resolution. It also works at (700, 600) for my screen.
I question if you want to use a GridBadLayout. Maybe just use a BorderLayout. The custom painting panel would go in the CENTER so it will grow/shrink as the frame size changes. The other panel would go to the LINE_END, so it will remain at a fixed size.

Related

AbstractBorder paints over JTabbedPane

So I'm using Swing and trying to use a JTabbedPane to encompass several JPanels with JScrollPanes to scroll through a list of JPanels. The issue that arises is whenever I change tabs, among other things, the custom border function I have in the JPanels inside the scroll pane results in the components painting over the top of the JTabbedPane header and I have no idea of how to fix this problem: AbstractBorder paints over JTabbedPane header. I have provided code that mimics what I'm trying to do below. To replicate the issue, scroll down some on one tab until one of the outlined panels is halfway visible at the top. Then, switch to a different tab and then back. Then, scroll some more and the issue will present itself at the top of the JTabbedPane
public class Example extends JFrame {
private final JTabbedPane tabbedPane;
public Example() {
setTitle("MIN Example");
setSize(600, 700);
setLayout(new BorderLayout());
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(Color.BLACK);
setExtendedState(JFrame.MAXIMIZED_BOTH);
tabbedPane = new JTabbedPane();
tabbedPane.setBorder(BorderFactory.createEmptyBorder());
tabbedPane.setFocusable(false);
add(tabbedPane, BorderLayout.CENTER);
createTabs();
setVisible(true);
}
private void createTabs() {
// Get the list of guilds
List<String> tabs = new ArrayList<>(Arrays.asList("Tab 1", "Tab 2", "Tab 3"));
// Iterate through the tabs and create the tab
for (String tab : tabs) {
// Initialize the list JPanel and formatting
JPanel panelList = new JPanel();
panelList.setLayout(new GridBagLayout());
// House the list panel inside a JScrollPane
JScrollPane listScroll = new JScrollPane(panelList);
listScroll.setBorder(BorderFactory.createEmptyBorder());
// Create tab and formatting
tabbedPane.addTab(tab, listScroll);
populateList(panelList);
}
}
private void populateList(JPanel tab) {
// Create GBC for formatting
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weightx = 1;
gbc.weighty = 1;
// Remove all components from the JPanel
tab.removeAll();
// Add filler JPanel
JPanel filler = new JPanel();
filler.setOpaque(false);
tab.add(filler, gbc);
// Update GBC constraints
gbc.insets = new Insets(10, 10, 0, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weighty = GridBagConstraints.RELATIVE;
// Go through the tabs and add JPanels
for (int i = 10; i >= 0; i--) {
JPanel tempPanel = new JPanel();
tempPanel.setBorder(new RoundedBorder(Color.BLACK, 6, 16));
tab.add(tempPanel, gbc, 0);
tempPanel.setPreferredSize(new Dimension(0, 100));
}
// Refresh the console to display updated lists
validate();
repaint();
}
}
public class RoundedBorder extends AbstractBorder {
private final Color color;
private final int thickness;
private final int radii;
private final Insets insets;
private final BasicStroke stroke;
private final int strokePad;
RenderingHints hints;
/**
* Creates the rounded border
*
* #param color The color of the border outline
* #param thickness The thickness of the border outline
* #param radii The radius of the rounded border
*/
public RoundedBorder(Color color, int thickness, int radii) {
this.thickness = thickness;
this.radii = radii;
this.color = color;
stroke = new BasicStroke(thickness);
strokePad = thickness / 2;
hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int pad = radii + strokePad;
int bottomPad = pad + strokePad;
insets = new Insets(pad, pad, bottomPad, pad);
}
#Override
public Insets getBorderInsets(Component c) {
return insets;
}
#Override
public Insets getBorderInsets(Component c, Insets insets) {
return getBorderInsets(c);
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2 = (Graphics2D) g;
int bottomLineY = height - thickness;
RoundRectangle2D.Double bubble = new RoundRectangle2D.Double(strokePad, strokePad, width - thickness, bottomLineY, radii, radii);
Area area = new Area(bubble);
g2.setRenderingHints(hints);
// Paint the background color of the parent
Component parent = c.getParent();
if (parent != null) {
Color background = parent.getBackground();
Rectangle rect = new Rectangle(0,0,width, height);
Area borderRegion = new Area(rect);
borderRegion.subtract(area);
g2.setClip(borderRegion);
g2.setColor(background);
g2.fillRect(0, 0, width, height);
g2.setClip(null);
}
g2.setColor(color);
g2.setStroke(stroke);
g2.draw(area);
}
}
The only things I have tried to do to fix this issue was changing the order of adding the JTabbedPane (Add before the tabs were populated with panels) to no avail. Help would be greatly appreciated, thank you.
In the future, an MRE should include:
the import statements
the main() method
In the original code the "clip" of the Graphics is being set to null so the entire Rectangle of the panel appears to be painted.
I modified the painting of the parent to use a separate Graphics object so the original "clip" area is not affected:
// Paint the background color of the parent
Component parent = c.getParent();
if (parent != null) {
Graphics2D g2d = (Graphics2D)g2.create();
Color background = parent.getBackground();
//Rectangle rect = new Rectangle(0, 0, width, height);
Rectangle rect = g2d.getClip().getBounds();
Area borderRegion = new Area(rect);
borderRegion.subtract(area);
g2d.setClip(borderRegion);
g2d.setColor(background);
g2d.fillRect(0, 0, width, height);
g2d.dispose();
}

Java Rendering Artifacts When Adding Buttons To Semi-Transparent JPanel

I have a problem. I am trying to create "alert" component that I can display in a JFrame. This alert has a semi-transparent background (70% opacity, white) with any trivial amount of JButtons on it. The background itself has rounded corners, and thus I made a custom component.
When rendering this alert, artifacts appear: no alert-background is rendered on the button that is initially selected. When moving my mouse across the buttons, it looks like the text of the buttons is being drawn in two places for no reason.
Screenshots of the artifacts (top = initial render, bottom = moving mouse around):
Code that causes these problems:
public class Test {
public static void main(String[] args) {
MyRoundedTransparentBackground background = new MyRoundedTransparentBackground();
background.setSize(200, 200);
background.setLocation(100, 100);
background.setLayout(new GridBagLayout());
JPanel spacer = new JPanel();
spacer.setBackground(new Color(0, 0, 0, 0));
GridBagConstraints c = new GridBagConstraints();
c.gridwidth = 1;
c.gridheight = 1;
c.weighty = 1;
background.add(spacer, c);
JButton button1 = new JButton("Hi there");
JButton button2 = new JButton("Bye ...");
button1.setBackground(new Color(0, 0, 0, 0));
button1.setFocusPainted(false);
button1.setBorderPainted(false);
button2.setBackground(new Color(0, 0, 0, 0));
button2.setFocusPainted(false);
button2.setBorderPainted(false);
c.weighty = 0;
c.gridy = 1;
background.add(button1, c);
c.gridx = 1;
background.add(button2, c);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
frame.setLocation(2000, 100);
frame.setLayout(null);
frame.getContentPane().setBackground(Color.red);
frame.add(background);
frame.setVisible(true);
}
private static class MyRoundedTransparentBackground extends JComponent {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(new Color(1f, 1f, 1f, 0.7f));
g2.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 15, 15);
}
}
Can someone help me find out what the issue is, and give a potential solution to achieve the desired behavior?

Alignment of JPanels

I'm fairly new to Java and I'm at a complete loss. I'm trying to create a simple program that draws a circle and outputs it's properties on the screen. Functionally, the program works fine, but the layout of the JPanels is off. For some reason, there is a gap between the JPanel that holds the 'Draw Me' button and the JPanel that holds the circle. Even if I resize the height of the program, the gap doesn't disappear. Likewise, the labels within the program aren't aligning to the left, they kind of sit off-center for some weird reason, removing the border doesn't seem to help. I'm stuck...
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
public class MainPanel extends JPanel {
private JPanel userHelpSubPanel;
private JPanel inputSubPanel;
private JPanel buttonSubPanel;
private JPanel drawSubPanel;
private JPanel resultsSubPanel;
private JLabel userHelpLabel;
private JLabel diameterLabel;
private JLabel circumferenceLabel;
private JLabel areaLabel;
private JButton drawButton;
private JTextField inputTextField;
private float radius;
public MainPanel() {
radius = 0;
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
userHelpSubPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
inputSubPanel = new JPanel();
inputSubPanel.setLayout(new BoxLayout(inputSubPanel, BoxLayout.Y_AXIS));
drawSubPanel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.draw(new Ellipse2D.Double(150, 20, radius * 2, radius * 2));
}
};
resultsSubPanel = new JPanel();
resultsSubPanel.setLayout(new BoxLayout(resultsSubPanel, BoxLayout.Y_AXIS));
buttonSubPanel = new JPanel();
userHelpLabel = new JLabel();
userHelpLabel.setText("Enter the radius of the ellipse: ");
inputTextField = new JTextField();
inputTextField.setMaximumSize(new Dimension(Integer.MAX_VALUE, inputTextField.getPreferredSize().height));
drawButton = new JButton("Draw Me");
drawButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MainPanel.this.radius = Float.parseFloat(inputTextField.getText());
diameterLabel.setText(String.format("Diameter: %.1f", 2 * radius));
circumferenceLabel.setText(String.format("Circumference: %.1f", 2 * Math.PI * radius));
areaLabel.setText(String.format("Area: %.1f", Math.PI * Math.pow(radius, 2)));
drawSubPanel.repaint();
}
});
circumferenceLabel = new JLabel("Circumference: N/A");
diameterLabel = new JLabel("Diameter: N/A");
areaLabel = new JLabel("Area: N/A");
userHelpSubPanel.add(userHelpLabel);
inputSubPanel.add(inputTextField);
buttonSubPanel.add(drawButton);
resultsSubPanel.add(diameterLabel);
resultsSubPanel.add(circumferenceLabel);
resultsSubPanel.add(areaLabel);
add(userHelpLabel);
add(inputSubPanel);
add(buttonSubPanel);
add(drawSubPanel);
add(resultsSubPanel);
}
}
And here is what it looks like....
BoxLayout will respect the minimum and maximum sizes of a component. So if there is extra space in the frame then the panels will be given more space.
The solution is to override the getMaximumSize() method of the custom panel to return the preferred size of the panel.
Every Swing component is responsibile for determiningin its own preferred size. So this would mean you also need to override the getPreferredSize() of the panel where you do the custom painting. The preferred size would be based on the size/location of the oval that you are drawing.
A simple fix for these issues is to give your panels (relevant ones) an empty border and then reduce the specific direction that is too large:
// this example reduces the top of your circle panel.
drawSubPanel.setBorder(new EmptyBorder(-100, 0, 0, 0));
Obviously this isn't the root of the problem and isn't the most optimal but it should do the trick.

Fit the component in the container by width in Swing

I have problem with creating own swing component. I want to create component that will render some image and it will fit current window (or container) size proportionally.
The problem is that I cant know real available space for component in container (without insents, borders, may be something else...).
This example shows the problem:
public class Main
{
static class MyComponent extends JComponent
{
#Override
public Dimension getPreferredSize()
{
if(isShowing())
{
int width = getParent().getSize().width - 4;
Dimension dimension = new Dimension(width, (int)(width / 0.8));
System.out.println(dimension);
return dimension;
}
return super.getPreferredSize();
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.RED);
g.fillOval(0, 0, getWidth(), getHeight());
}
}
public static void main(String... args)
{
JFrame frame = new JFrame("Test");
JPanel panel = new JPanel(new GridBagLayout());
panel.setBackground(Color.DARK_GRAY);
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(2, 2, 2, 2);
c.gridx = c.gridy = 0;
panel.add(new MyComponent(), c);
c.gridy++;
panel.add(new MyComponent(), c);
frame.add(new JScrollPane(panel));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setVisible(true);
}
}
If you remove 4 (left & right from insets) from getParent().getSize().width - 4 expression and launch, you will see that component(s) will increase its size infinitely.
In the above code I get full width of container, but I need available width so that component will not trigger container to resize again and again.
Or is there another way to fit component normally?

Java, BorderLayout.CENTER, getting the width and height of the JPanel

I am using Swing and AWT (for the listeners) to make a small program. I have a problem concerning getting the size of my JPanel (the class named Chess).
My Layout:
public class Main extends JFrame implements MouseListener, ActionListener{
Chess chessPanel = new Chess ();
JButton newGameButton = new JButton ("New Game");
JButton loadGameButton = new JButton ("Load Game");
JButton saveGameButton = new JButton ("Save Game");
JButton exitButton = new JButton ("Exit");
public static void main (String [] args) {
new Main();
}
Main () {
super ("Chess");
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
setSize(dim);
setLocation(0,0);
setUndecorated(true);
chessPanel.addMouseListener(this);
add(chessPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
newGameButton.addActionListener(this);
loadGameButton.addActionListener(this);
saveGameButton.addActionListener(this);
exitButton.addActionListener(this);
buttonPanel.add(newGameButton);
buttonPanel.add(loadGameButton);
buttonPanel.add(saveGameButton);
buttonPanel.add(exitButton);
add(buttonPanel, BorderLayout.SOUTH);
setVisible(true);
}
// ... Code ...
}
As you can see by the code, I have one JPanel in the CENTER, which takes nearly all the screen. In the bottom I have another JPanel (SOUTH), which has a row of buttons.
What I need is the size that the JPanel in the CENTER takes. When I call the getWidth(), getHeight() or getBounds() methods inherited from JPanel, they all return 0, because of the BorderLayout.
Any idea how to get the real values?
PS: The screen always takes up the entire screen, and will never be resized, if that helps.
You're likely calling getWidth before the JPanel has been rendered, and so it will be 0. The solution is to get the size after rendering, for instance after pack() or setVisible(true) has been called on the root container that holds this JPanel.
Also, I recommend against calling setSize() on anything since most of the standard layout managers observe the preferred size of a component, not the size, and when you call pack() telling the layout managers to do their thing, the set sizes are usually ignored. You may want to make your JPanel that is in the center set its own size by overriding its setPreferredSize method if it needs to be a certain size. Then let the JFrame and its held containers set the bet fit size based on the their layout managers when you call pack.
e.g.,
import java.awt.*;
import javax.swing.*;
public class Main extends JFrame {
Chess chessPanel = new Chess();
JButton newGameButton = new JButton("New Game");
JButton loadGameButton = new JButton("Load Game");
JButton saveGameButton = new JButton("Save Game");
JButton exitButton = new JButton("Exit");
public static void main(String[] args) {
new Main();
}
Main() {
super("Chess");
add(chessPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
buttonPanel.add(newGameButton);
buttonPanel.add(loadGameButton);
buttonPanel.add(saveGameButton);
buttonPanel.add(exitButton);
System.out.printf("chessPanel Size before rendering: %s%n", chessPanel.getSize());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(buttonPanel, BorderLayout.SOUTH);
pack();
System.out.printf("chessPanel Size after rendering: %s%n", chessPanel.getSize());
setLocationRelativeTo(null);
setVisible(true);
}
// ... Code ...
}
#SuppressWarnings("serial")
class Chess extends JPanel {
private static final int CHESS_WIDTH = 600;
private static final int CHESS_HEIGHT = CHESS_WIDTH;
private static final int MAX_ROW = 8;
private static final int MAX_COL = 8;
private static final Color LIGHT_COLOR = new Color(240, 190, 40);
private static final Color DARK_COLOR = new Color(180, 50, 0);
#Override
public Dimension getPreferredSize() {
return new Dimension(CHESS_WIDTH, CHESS_HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int panelWidth = getWidth();
int panelHeight = getHeight();
int sqrWidth = panelWidth / MAX_ROW;
int sqrHeight = panelHeight / MAX_COL;
for (int row = 0; row < MAX_ROW; row++) {
for (int col = 0; col < MAX_COL; col++) {
Color c = (row % 2 == col % 2) ? LIGHT_COLOR : DARK_COLOR;
g.setColor(c);
int x = (row * panelWidth) / MAX_ROW;
int y = (col * panelHeight) / MAX_COL;
g.fillRect(x, y, sqrWidth, sqrHeight);
}
}
}
}

Categories