I have a JPanel which I want to remain a square however I want it to size so that it fills the maximum amount of space possible in its parent JFrame but remains square i.e. it takes the shortest side of the JFrame as the square width.
I've searched the net, checked all layout managers and none seem to have a simple solution to this very simple problem.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class YouAreSoSquare {
private static JPanel createPanel() {
// GBL is important for the next step..
JPanel gui = new JPanel(new GridBagLayout());
JPanel squareComponent = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
// Relies on being the only component
// in a layout that will center it without
// expanding it to fill all the space.
Dimension d = this.getParent().getSize();
int newSize = d.width > d.height ? d.height : d.width;
newSize = newSize == 0 ? 100 : newSize;
return new Dimension(newSize, newSize);
}
};
squareComponent.setBackground(Color.RED);
gui.add(squareComponent);
return gui;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
JFrame mainFrame = new JFrame("..So Square");
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainFrame.setLocationByPlatform(true);
mainFrame.add(createPanel());
mainFrame.pack();
mainFrame.setMinimumSize(mainFrame.getSize());
mainFrame.setVisible(true);
}
};
SwingUtilities.invokeLater(r);
}
}
You may use a GridBagLayout and ComponentListener,
For example: (inspired from: https://community.oracle.com/thread/1265752?start=0&tstart=0)
public class AspectRatio {
public static void main(String[] args) {
final JPanel innerPanel = new JPanel();
innerPanel.setBackground(Color.YELLOW);
final JPanel container = new JPanel(new GridBagLayout());
container.add(innerPanel);
container.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
resizePreview(innerPanel, container);
}
});
final JFrame frame = new JFrame("AspectRatio");
frame.getContentPane().add(container);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
frame.setVisible(true);
}
private static void resizePreview(JPanel innerPanel, JPanel container) {
int w = container.getWidth();
int h = container.getHeight();
int size = Math.min(w, h);
innerPanel.setPreferredSize(new Dimension(size, size));
container.revalidate();
}
}
Here's my take for a most reusable solution, which use the Swing Layout concept. So that you can use it simply by copy/pasting this Layout class and set the layout on your Swing Containers.
I was surprised not to find it already done on the net, so here it is ! A main method is included, it creates a JFrame similar to the screenshots by
Andrew Thompson
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import javax.swing.JFrame;
import javax.swing.JPanel;
//#Slf4j
/**
* A Swing Layout that will shrink or enlarge keep the content of a container while keeping
* it's aspect ratio. The caveat is that only a single component is supported or an exception
* will be thrown.
* This is the component's {#link Component#getPreferredSize()} method that must return the
* correct ratio. The preferredSize will not be preserved but the ratio will.
*
* #author #francoismarot
* #see https://gist.github.com/fmarot/f04346d0e989baef1f56ffd83bbf764d
*/
public class SingleComponentAspectRatioKeeperLayout implements LayoutManager {
/** Will be used for calculus in case no real component is in the parent */
private static Component fakeComponent = new JPanel();
public SingleComponentAspectRatioKeeperLayout() {
fakeComponent.setPreferredSize(new Dimension(0, 0));
}
#Override
public void addLayoutComponent(String arg0, Component arg1) {
}
#Override
public void layoutContainer(Container parent) {
Component component = getSingleComponent(parent);
Insets insets = parent.getInsets();
int maxWidth = parent.getWidth() - (insets.left + insets.right);
int maxHeight = parent.getHeight() - (insets.top + insets.bottom);
Dimension prefferedSize = component.getPreferredSize();
Dimension targetDim = getScaledDimension(prefferedSize, new Dimension(maxWidth, maxHeight));
double targetWidth = targetDim.getWidth();
double targetHeight = targetDim.getHeight();
double hgap = (maxWidth - targetWidth) / 2;
double vgap = (maxHeight - targetHeight) / 2;
// Set the single component's size and position.
component.setBounds((int) hgap, (int) vgap, (int) targetWidth, (int) targetHeight);
}
private Component getSingleComponent(Container parent) {
int parentComponentCount = parent.getComponentCount();
if (parentComponentCount > 1) {
throw new IllegalArgumentException(this.getClass().getSimpleName()
+ " can not handle more than one component");
}
Component comp = (parentComponentCount == 1) ? parent.getComponent(0) : fakeComponent;
return comp;
}
private Dimension getScaledDimension(Dimension imageSize, Dimension boundary) {
double widthRatio = boundary.getWidth() / imageSize.getWidth();
double heightRatio = boundary.getHeight() / imageSize.getHeight();
double ratio = Math.min(widthRatio, heightRatio);
return new Dimension((int) (imageSize.width * ratio), (int) (imageSize.height * ratio));
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return getSingleComponent(parent).getPreferredSize();
}
#Override
public void removeLayoutComponent(Component parent) {
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(); // the panel we want to keep it's aspect ratio
panel.setPreferredSize(new Dimension(300, 600));
panel.setBackground(Color.ORANGE);
JPanel wrapperPanel = new JPanel(new SingleComponentAspectRatioKeeperLayout());
wrapperPanel.add(panel);
frame.getContentPane().add(wrapperPanel);
frame.setSize(450, 450);
frame.setVisible(true);
}
}
Related
Hi I need to display a large content(its graphical data) of data in single, so I tried following code.
canvas.setPreferredSize(new Dimension(3000, 300));
canvas.setBackground(Color.blue);
JScrollPane jsp = new JScrollPane(canvas);
setPreferredSize(new Dimension(600, 500));
setLayout(new GridLayout(1, 0, 5, 0));
jsp.getHorizontalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
System.out.println(e.getValue());
repaint();
}
});
add(jsp);
this is my MyCanvas class
class MyCanvas extends Canvas {
#Override
public void paint(Graphics g) {
super.paint(g);
System.out.println("paint");
g.setColor(Color.YELLOW);
for (int i = 0; i < 100; i++) {
g.drawString(""+i, i*30, 100);
// g.drawLine(10, 10, 20, 20);
}
}
}
but problem is that when I am scrolling window I cannot see full content as I expected it should print 100 numbers but not printed actually, can any one correct me?
see the result here
I recommend that you avoid mixing AWT and Swing components together (or if you absolutely must do this, then you have to make sure you understand the pitfalls and fully jump through all the necessary hoops.
Myself, I'd extend JPanel, I'd be sure that its preferredSize was where I want it, since this will determine how big it will be within the JScrollPane.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyScrollExample extends JPanel {
private static final int MAX = 100;
private MyPanel myPanel = new MyPanel(MAX);
public MyScrollExample() {
JScrollPane scrollPane = new JScrollPane(myPanel);
scrollPane.getViewport().setPreferredSize(new Dimension(600, 200));
add(scrollPane);
}
private static void createAndShowGui() {
MyScrollExample mainPanel = new MyScrollExample();
JFrame frame = new JFrame("MyScrollExample");
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());
}
}
#SuppressWarnings("serial")
class MyPanel extends JPanel {
private static final Color BG = Color.BLUE;
private static final Color FG = Color.YELLOW;
private static final int WIDTH_GAP = 30;
private static final int HEIGHT_GAP = 100;
private int max;
public MyPanel(int max) {
setBackground(BG);
this.max = max;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(FG);
for (int i = 0; i < max; i++) {
g.drawString("" + i, i * WIDTH_GAP, HEIGHT_GAP);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
int w = (WIDTH_GAP + 1) * max;
int h = HEIGHT_GAP * 3;
return new Dimension(w, h);
}
}
I Have a JFrame which has 2 JPanels contained within, one of which is 70% of the JFrame the other is the remaining 30%. The JPanel that is 30% of the width has a nested JPanel within it. However this nested JPanel should be 50% of the width its parent JPanel however when it is displayed it takes up the full width of the parent JPanel.
The parent JPanel is of width 358 and height 772, the nested JPanel has the following values: width 179 and height 772
I have set the colours of these components to green and gray, so i can easily see if they are being rendered correctly. When i run my application, the nested JPanel is actually being rendered as if it were the full width of the parent (the whole area gets rendered green).
This is the code currently.
public class DisplayFrame extends JFrame{
private final static int WIDTH = 1200;
private final static int HEIGHT = 800;
private DisplayCanvas canvas;
private ControlContainer controlContainer;
public DisplayFrame() {
//simple inheritance.
super(title);
setSize(new Dimension(WIDTH, HEIGHT));
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
addComponents(this.getContentPane());
}
public void addComponents(Container container){
canvas = DisplayCanvas.getInstance(container);
canvas.setAlignmentX(Component.LEFT_ALIGNMENT);
container.add(canvas);
controlContainer = ControlContainer.getInstance(container);
controlContainer.setAlignmentX(Component.RIGHT_ALIGNMENT);
container.add(controlContainer);
}
}
The panel with 70% width
public class DisplayCanvas extends JPanel{
private final double WIDTH_PERCENT = 70;
private static DisplayCanvas instance;
private DisplayCanvas(Container parent) {
this.setSize(new Dimension(MathHelper.calculateXPercentOfY(WIDTH_PERCENT,
parent.getWidth()), parent.getHeight()));
this.setBackground(Color.WHITE);
this.setVisible(true);
}
//implement the singleton
public static DisplayCanvas getInstance(Container parent) {
if(instance != null){
return instance;
}
instance = new DisplayCanvas(parent);
return instance;
}
}
the panel with 30% width:
public class ControlContainer extends JPanel{
private final double WIDTH_PERCENT = 30;
private static ControlContainer instance;
private ControlPanel controlPanel;
private ControlContainer(Container parent) {
this.setSize(new Dimension(MathHelper.calculateXPercentOfY(WIDTH_PERCENT,
parent.getWidth()), parent.getHeight()));
this.setBackground(Color.GRAY);
this.setLayout(new BorderLayout(0,0));
this.setVisible(true);
addControlElements(this);
}
//implement Singelton
public static ControlContainer getInstance(Container parent){
if(instance != null){
return instance;
}
instance = new ControlContainer(parent);
return instance;
}
public void addControlElements(Container parent){
controlPanel = ControlPanel.getInstance(parent);
controlPanel.setAlignmentX(LEFT_ALIGNMENT);
System.out.println("ControlContainer: " + parent.getWidth()
+ " " + parent.getHeight()+ " vis: " + parent.isVisible());
System.out.println("ControlPanel: " + controlPanel.getWidth()
+ " " + controlPanel.getHeight() + " vis: " + controlPanel.isVisible());
parent.add(controlPanel, BorderLayout.CENTER);
}
}
Now, this panel should be nested inside ControlContainer with 50% of its width.
public class ControlPanel extends JPanel{
private final double WIDTH_PERCENT = 50;
private static ControlPanel instance;
private ControlPanel(Container parent) {
super.setSize(new Dimension(MathHelper.calculateXPercentOfY(WIDTH_PERCENT,
parent.getWidth()), parent.getHeight()));
super.setBackground(Color.GREEN);
super.setVisible(true);
}
//implement singelton measures
public static ControlPanel getInstance(Container parent){
if(instance != null){
return instance;
}
instance = new ControlPanel(parent);
return instance;
}
}
This is the output from the addControlElements method:
ControlContainer: 358 772 vis: true
ControlPanel: 179 772 vis: true
This is used just to test if the values are set as expected, as you can see ControlPanel width is 179 which is what is expected however, when rendered it fills the whole of the ControlContainer.
Hopefully you understand what i mean, its not the easiest to explain.
Start by taking a look at Laying Out Components Within a Container to understand how components are laied out in Swing. Take a look at How to Use GridBagLayout to see how you might be able achieve the result you are looking for
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BasePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class BasePane extends JPanel {
public BasePane() {
TestPane top = new TestPane();
TestPane bottom = new TestPane();
// top.setBorder(new LineBorder(Color.RED));
// bottom.setBorder(new LineBorder(Color.BLUE));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 0.3;
add(top, gbc);
gbc.gridy++;
gbc.weighty = 0.7;
add(bottom, gbc);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class TestPane extends JPanel {
public TestPane() {
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Dimension mySize = getSize();
Dimension parentSize = getParent().getSize();
String widthPer = NumberFormat.getPercentInstance().format((float) mySize.width / (float) parentSize.width);
String heightPer = NumberFormat.getPercentInstance().format((float) mySize.height / (float) parentSize.height);
String[] text = new String[]{
"Me = " + mySize.width + "x" + mySize.height,
"Parent = " + parentSize.height + "x" + parentSize.height,
"Perctange = " + widthPer + "x" + heightPer
};
FontMetrics fm = g2d.getFontMetrics();
int y = (getHeight() - (fm.getHeight() * text.length)) / 2;
for (String value : text) {
int x = (getWidth() - fm.stringWidth(value)) / 2;
g2d.drawString(value, x, y + fm.getAscent());
y += fm.getHeight();
}
g2d.dispose();
}
}
}
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)
That is what i want to achieve:
As you see, horizontal scroll is changed and a JLabel("text") should be added in the same line. Currently i find the way to change horizontal scroll (like on image), but i can't find any way to add JLabel("text") in the place, where it is placed on the image.
Any suggestions? Thanks in advance!
import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneLayout;
import javax.swing.WindowConstants;
public class Test {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.setSize(300, 300);
JPanel myPanel = new JPanel();
myPanel.add(new JLabel("Check Check Check Check Check Check Check Check"));
MyScrollPane scrollPane = new MyScrollPane(myPanel);
jFrame.add(scrollPane);
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}
}
class MyScrollPane extends JScrollPane {
JLabel label = new JLabel("text");
public MyScrollPane(Component view) {
super(view, VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_ALWAYS);
this.setLayout(new MyLayout(label));
add(label);
}
}
class MyLayout extends ScrollPaneLayout {
JLabel label;
public MyLayout(JLabel aLabel) {
super();
label = aLabel;
}
public void layoutContainer(Container parent) {
super.layoutContainer(parent);
hsb.setSize(hsb.getWidth() - 100, hsb.getHeight()); // drift
Point location = hsb.getLocation();
label.setLocation(location.x + 12, location.y - 12);
}
}
You might want to consider making your own implementation of JScrollPane. It may sound scary, but in essence all a JScrollPane is is 2 JScrollBars and some graphics logic calling g.translate() If you play around with g.translate you'll see that it's pretty easy to scroll your own stuff.
The advantage of making your own component is that you have full command over layout, display and events. This is the route I would take if I were in your position.
Here's my attempt:
import java.awt.*;
import java.awt.font.*;
import javax.swing.*;
import javax.swing.border.*;
public class Test2 {
public JComponent makeUI() {
JPanel myPanel = new JPanel();
myPanel.add(new JLabel("Check Check Check Check Check Check Check Check"));
JScrollPane scrollPane = new JScrollPane(myPanel,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
JScrollBar hsb = scrollPane.getHorizontalScrollBar();
hsb.setBorder(new StringBorder(hsb, "Test"));
JPanel p = new JPanel(new BorderLayout());
p.add(scrollPane);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new Test2().makeUI());
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class StringBorder implements Border {
private final JComponent parent;
private final Insets insets;
private final Rectangle rect;
private final String str;
public StringBorder(JComponent parent, String str) {
this.parent = parent;
this.str = str;
FontRenderContext frc = new FontRenderContext(null, true, true);
rect = parent.getFont().getStringBounds(str, frc).getBounds();
rect.width = Math.max(rect.width, 100);
insets = new Insets(0,5,0,rect.width);
}
#Override public Insets getBorderInsets(Component c) {
return insets;
}
#Override public boolean isBorderOpaque() {
return false;
}
#Override public void paintBorder(
Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2 = (Graphics2D)g;
float tx = x + width - insets.right + insets.left;
float ty = y - rect.y + (height - rect.height)/2;
g2.setPaint(Color.BLACK);
g2.drawString(str, tx, ty);
}
}
Probably the best way to achieve something like this would be to use your own layout with a JScrollPane. Here is an example that allows any component as lower-left corner component:
public class CustomScrollPaneLayout extends ScrollPaneLayout {
#Override
public void layoutContainer(Container parent) {
super.layoutContainer(parent);
JScrollBar scrollBar = getHorizontalScrollBar();
if (lowerLeft == null || !lowerLeft.isVisible() || scrollBar == null)
return;
Dimension size = lowerLeft.getPreferredSize();
Rectangle bounds = lowerLeft.isVisible()
? lowerLeft.getBounds() : scrollBar.getBounds();
if (size.width > bounds.getWidth()) {
int right = scrollBar.getX()+scrollBar.getWidth();
if (size.width + scrollBar.getMinimumSize().width > right)
size.width = right - scrollBar.getMinimumSize().width;
if (bounds.x + size.width < scrollBar.getX())
size.width = scrollBar.getX() - bounds.x;
lowerLeft.setBounds(bounds.x, bounds.y, size.width, bounds.height);
int x = bounds.x + size.width;
scrollBar.setBounds(x, bounds.y, right - x, bounds.height);
}
lowerLeft.setVisible(true);
}
}
Please note that you need to use a horizontal scrollbar policy of ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS if you want your control to be always visible.
This is my current RectangleComponent class and I add it to a panel in my main JFrame but it never appears. I thought it wasn't drawing so I decided to call the paintComponent method in the Rectangle's constructor, and after sorting through 4-5 nullPointerExceptions, nothing has changed. I've read multiple guides on how to draw rectangles and I have seen multiple code examples, but I can never get the panels to work with more than one JComponent. If you could, please take a brief look at my code and see if you can devise a solution.
Thank you for your time. Also listed is the Frame I call the rectangle constructor in.
public class GameFrame extends JFrame
{
private SpellBarComponent bar;
private JPanel mainPanel = new JPanel();
private JPanel buttonPanel = new JPanel();
private JPanel healthPanel = new JPanel();
Color green = new Color(29, 180, 29);
Color red = new Color(255, 0, 0);
private RectangleComponent life;
private RectangleComponent death;
private JFrame frame = new JFrame();
public GameFrame(char x)
{
frame.setSize(1024, 768);
frame.setTitle("Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
FlowLayout layout = new FlowLayout();
createPanels(x);
healthPanel.setLayout(layout);
buttonPanel.setLayout(layout);
mainPanel.setLayout(layout);
frame.getContentPane().add(mainPanel);
frame.pack();
repaint();
}
public RectangleComponent getLife()
{
return life;
}
private void createHealth()
{
life = new RectangleComponent(green, healthPanel);
death = new RectangleComponent(red, healthPanel);
}
private void createPanels(char x)
{
add(healthPanel);
pack();
createBar(x);
createHealth();
mainPanel.add(buttonPanel);
mainPanel.add(healthPanel);
healthPanel.add(death);
healthPanel.add(life);
buttonPanel.add(bar.getSpell1());
buttonPanel.add(bar.getSpell2());
buttonPanel.add(bar.getSpell3());
add(mainPanel);
}
private void createBar(char x)
{
bar = new SpellBarComponent(x, mainPanel);
}
}
public class RectangleComponent extends JComponent
{
Color color;
int width;
int height = 18;
RoundRectangle2D roundedRectangle;
private JPanel panel;
public RectangleComponent(Color color, JPanel panel)
{
this.panel = panel;
this.color = color;
paintComponent(panel.getGraphics());
}
public void paintComponent(Graphics g)
{
Graphics2D graphics2 = (Graphics2D) g;
width = 125;
roundedRectangle = new RoundRectangle2D.Float(10, 10, width, height, 10, 10);
graphics2.setPaint(color);
graphics2.fill(roundedRectangle);
graphics2.draw(roundedRectangle);
}
public void subtractLife(int amount)
{
width -= amount;
roundedRectangle.setRoundRect(10, 10, width, height, 10, 10);
repaint();
}
}
In order for your Swing Application to work as expected, there are many a things you need to keep in mind. There are always certain steps that one must follow in order to escape certain hurdles, that might can arise, since you coded in the wrong way. For this stick to the basics of Swing Programming Strictly, and follow them.
Like as mentioned by #HovercraftFullOfEels , you calling to your
Graphics directly, which one should never do.
Secondly, look at your GameFrame() constructor, you set it to
visible, even before you had added any components to it and much
before it's real size has been established
Such loop holes inside your coding might can give rise to many a headaches, as you sit down to write huge programs, so better to be on the safe road from the beginning, then to curse yourself at the later stage. As they say Prevention is better than Cure.
Now coming to your program, you missed the main thingy, since you failed to specify the size of your CustomComponent i.e. JComponent, hence you are not been able to see it on your screen. As you extends a JCompoent to your class, make it a customary habbit to override it's getPreferredSize(), in the same manner you override it's paintComponent(...) method.
Have a look at this small program, I had crafted for you, might be this be able to help you out, to understand the logic a bit more.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import javax.swing.*;
public class CustomPainting {
private RectangleComponent life;
private RectangleComponent death;
private void createAndDisplayGUI() {
JFrame frame = new JFrame("Custom Painting");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridLayout(0, 2, 5, 5));
// Specifying the WIDTH, HEIGHT and Colour for this JComponent.
life = new RectangleComponent(Color.GREEN.darker(), 20, 20);
death = new RectangleComponent(Color.RED.darker(), 20, 20);
centerPanel.add(life);
centerPanel.add(death);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));
JButton incLifeButton = new JButton("INCREASE LIFE");
incLifeButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
life.addLife(1);
}
});
JButton decLifeButton = new JButton("DECREASE LIFE");
decLifeButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
life.subtractLife(1);
}
});
JButton incDeathButton = new JButton("INCREASE DEATH");
incDeathButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
death.addLife(1);
}
});
JButton decDeathButton = new JButton("DECREASE DEATH");
decDeathButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
death.subtractLife(1);
}
});
buttonPanel.add(incLifeButton);
buttonPanel.add(decLifeButton);
buttonPanel.add(incDeathButton);
buttonPanel.add(decDeathButton);
frame.getContentPane().add(centerPanel, BorderLayout.CENTER);
frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String\u005B\u005D args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new CustomPainting().createAndDisplayGUI();
}
});
}
}
class RectangleComponent extends JComponent {
private Color colour;
private static final int MARGIN = 10;
private int width;
private int height;
private int originalWidth;
private RoundRectangle2D roundedRectangle;
public RectangleComponent(Color c, int w, int h) {
colour = c;
width = w;
height = h;
originalWidth = width;
}
/*
* Overriding this method, so that
* the size of the JComponent
* can be determined, on the screen
* or by the LayoutManager concern.
*/
#Override
public Dimension getPreferredSize() {
return (new Dimension(width, height));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
roundedRectangle = new RoundRectangle2D.Float(MARGIN, MARGIN,
width, height, MARGIN, MARGIN);
g2d.setPaint(colour);
g2d.draw(roundedRectangle);
g2d.fill(roundedRectangle);
}
public void subtractLife(int amount) {
width -= amount;
System.out.println("ORIGINAL Width : " + originalWidth);
System.out.println("Width : " + width);
if (width > 0) {
roundedRectangle.setRoundRect(MARGIN, MARGIN, width, height,
MARGIN, MARGIN);
/*
* This repaint() will call the paintComponent(...)
* by itself, so nothing else to be done.
*/
repaint();
} else {
width += amount;
}
}
public void addLife(int amount) {
width += amount;
System.out.println("ORIGINAL Width : " + originalWidth);
System.out.println("Width : " + width);
if (width < originalWidth) {
roundedRectangle.setRoundRect(MARGIN, MARGIN, width, height,
MARGIN, MARGIN);
repaint();
} else {
width -= amount;
}
}
}
Do ask any question, that might can arise as you go through this program :-), I be HAPPY to help on that :-)
**LATEST EDIT WITH TWO COLOURS : **
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import javax.swing.*;
public class CustomPainting {
private RectangleComponent lifeDeath;
private void createAndDisplayGUI() {
JFrame frame = new JFrame("Custom Painting");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridLayout(0, 2, 5, 5));
// Specifying the WIDTH, HEIGHT and Colour for this JComponent.
lifeDeath = new RectangleComponent(Color.GREEN, Color.RED, 20, 20);
centerPanel.add(lifeDeath);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new GridLayout(1, 2, 5, 5));
JButton incLifeButton = new JButton("INCREASE LIFE");
incLifeButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
lifeDeath.addLife(1);
}
});
JButton decLifeButton = new JButton("DECREASE LIFE");
decLifeButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
lifeDeath.subtractLife(1);
}
});
buttonPanel.add(incLifeButton);
buttonPanel.add(decLifeButton);
frame.getContentPane().add(centerPanel, BorderLayout.CENTER);
frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String\u005B\u005D args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new CustomPainting().createAndDisplayGUI();
}
});
}
}
class RectangleComponent extends JComponent {
private Color lifeColour;
private Color deathColour;
private static final int MARGIN = 10;
private int widthLife;
private int widthDeath;
private int height;
private int originalWidth;
private RoundRectangle2D roundedRectangle;
public RectangleComponent(Color lc, Color dc, int w, int h) {
lifeColour = lc;
deathColour = dc;
widthLife = w;
height = h;
originalWidth = widthLife;
widthDeath = 0;
}
/*
* Overriding this method, so that
* the size of the JComponent
* can be determined, on the screen
* or by the LayoutManager concern.
*/
#Override
public Dimension getPreferredSize() {
return (new Dimension(originalWidth, height));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
roundedRectangle = new RoundRectangle2D.Float((MARGIN + widthDeath), MARGIN,
widthLife, height, MARGIN, MARGIN);
g2d.setPaint(lifeColour);
g2d.draw(roundedRectangle);
g2d.fill(roundedRectangle);
roundedRectangle.setRoundRect(MARGIN, MARGIN,
widthDeath, height, MARGIN, MARGIN);
g2d.setPaint(deathColour);
g2d.draw(roundedRectangle);
g2d.fill(roundedRectangle);
}
public void subtractLife(int amount) {
widthLife -= amount;
widthDeath += amount;
System.out.println("ORIGINAL Width : " + originalWidth);
System.out.println("Width Life : " + widthLife);
System.out.println("Width Death : " + widthDeath);
if (widthLife > 0 && widthDeath < originalWidth) {
/*
* This repaint() will call the paintComponent(...)
* by itself, so nothing else to be done.
*/
repaint();
} else {
widthLife += amount;
widthDeath -= amount;
}
}
public void addLife(int amount) {
widthLife += amount;
widthDeath -= amount;
System.out.println("ORIGINAL Width : " + originalWidth);
System.out.println("Width Life : " + widthLife);
System.out.println("Width Death : " + widthDeath);
if (widthLife < originalWidth && widthDeath > 0) {
repaint();
} else {
widthLife -= amount;
widthDeath += amount;
}
}
}
No need to pass JPanel to the constructor of RectangleComponent just to get Graphics, and no need to manually call paintComponent. See Painting in AWT and Swing. Check out this example that demonstrates a custom component that paints a rectangle.
Your code is a bit creative, a bit crazy, and with logic that is very hard to follow. The most unusual aspect is that it has two JFrames, one called "frame", and one the GameFrame object itself, both of which get components added, but only one of which shows. You also have many methods that return void (which if over-used increases code smell) and only add to making the code more confusing.
For example,
public GameFrame(char x) {
// here you set up the "frame" JFrame
frame.setSize(1024, 768);
frame.setTitle("Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
FlowLayout layout = new FlowLayout();
createPanels(x);
healthPanel.setLayout(layout);
buttonPanel.setLayout(layout);
mainPanel.setLayout(layout);
// here you add content to the frame JFrame, and pack it
frame.getContentPane().add(mainPanel);
frame.pack();
repaint(); // and then call repaint on the "this" JFrame?
}
public RectangleComponent getLife() {
return life;
}
private void createHealth() {
life = new RectangleComponent(green, healthPanel);
death = new RectangleComponent(red, healthPanel);
}
private void createPanels(char x) {
add(healthPanel); // now you add content to the "this" JFrame
pack(); // and pack it
createBar(x);
createHealth();
mainPanel.add(buttonPanel);
mainPanel.add(healthPanel); // and then re-add a JPanel into a second JPanel?
healthPanel.add(death);
healthPanel.add(life);
buttonPanel.add(bar.getSpell1());
buttonPanel.add(bar.getSpell2());
buttonPanel.add(bar.getSpell3());
add(mainPanel); // and then re -add the mainPanel into the "this" JFrame???
}
This is all very confusing, and not likely going to work.
Then there's your trying to call paintComponent directly and calling getGraphics on a JComponent, both of which should not be done. You will want to go through the graphics tutorials to see how to do this correctly.
I recommend that you consider re-writing this, and first and foremost, using only one JFrame, and organizing your code better.