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();
}
}
}
Related
My JFrame uses a BorderLayout and it has a JLabel nested in several panels with different layout managers. I've tried several methods, however, cannot get the true position of where it sits in the frame.
I made a test UI and it seems like when other components are added the getX and getY parameters do not update. Other methods like getLocation do not provide a correct result either. Is there any way to obtain the exact location without manually calculating every possible offset from each component.
I am tracking the stated positions of the label (content) using a similar sized panel called content2 in the glass pane which I want to sit underneath content perfectly.
public class test {
private Dimension pSize = new Dimension(100,100);
private JFrame frame = new JFrame();
public static void main(String[] args) {
new test();
}
public test() {
//setup frame basics
frame.setPreferredSize(new Dimension(500,500));
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// setup GUI
JMenuBar j = new JMenuBar();
JMenuItem a = new JMenuItem("lol");
j.add(a);
JPanel j2 = new JPanel();
//setup main panel
JPanel main = new JPanel();
main.setBackground(Color.DARK_GRAY);
//setup side panel
FlowLayout f1 = new FlowLayout(FlowLayout.LEADING);
f1.setHgap(10);
f1.setVgap(0);
JPanel side = new JPanel();
side.setLayout(new BorderLayout());
side.setBackground(Color.gray);
side.setPreferredSize(new Dimension(150,100));
//setup JLabel (the main focus)
JLabel content = new JLabel("a");
content.setOpaque(true);
content.setBackground(Color.blue);
content.setPreferredSize(pSize);
// Setup the internal panels of side
JPanel top = new JPanel();//The panel where CONTENT is, the main focus
JPanel bot = new JPanel();
top.setBackground(Color.WHITE);
bot.setBackground(Color.orange);
top.setLayout(f1);
top.add(content);
side.add(top, BorderLayout.NORTH);
side.add(bot, BorderLayout.CENTER);
frame.add(main, BorderLayout.CENTER);
frame.add(side, BorderLayout.WEST);
frame.add(j2, BorderLayout.NORTH);
frame.setJMenuBar(j);
frame.pack();
frame.setVisible(true);
//Setting up the glass panel
JPanel pane = new JPanel();
pane.setLayout(null);
pane.setOpaque(false);
JPanel content2 = new JPanel();
content2.setBackground(Color.red);
content.revalidate();
int x = content.getX();
int y = content.getY();
// y = (int) content.getLocation().getY(); //returns a completely wrong location
//y = (int) content.getLocationOnScreen(); //returns a completely wrong location
/*
Point p = new Point();
p.setLocation(x, y);
p = SwingUtilities.convertPoint(content2, x, y, frame);
//SwingUtilities.convertPoint(content, p, frame);
y = (int) p.getY();
*
* Tried multiple SwingUtility converions to no avail
*
*/
// y = y +j.getHeight() + j2.getHeight(); // Manually calculating the Y off set works successfully but is too tedious for large project
y = y + content.getHeight();
content2.setBounds(x,y,100,100);
pane.add(content2);
frame.setGlassPane(pane);
frame.getGlassPane().setVisible(true);
frame.pack();
}
}
//frame.getContentPane().add(content);
//frame.add(content);
frame.setPreferredSize(new Dimension(500,500));
content.setBorder(BorderFactory.createEmptyBorder());
side.setLayout(new BorderLayout());
JPanel top = new JPanel();
JPanel bot = new JPanel();
top.setBackground(Color.WHITE);
bot.setBackground(Color.orange);
side.add(top, BorderLayout.NORTH);
top.setLayout(f1);
top.add(content);
side.add(bot, BorderLayout.CENTER);
frame.add(main, BorderLayout.CENTER);
frame.add(j2, BorderLayout.NORTH);
frame.add(side, BorderLayout.WEST);
frame.pack();
frame.setVisible(true);
JPanel pane = new JPanel();
pane.setLayout(null);
pane.setOpaque(false);
JPanel content2 = new JPanel();
content2.setBackground(Color.red);
content.revalidate();
int x = content.getX();
int y = content.getY();
// y = y +j.getHeight() + j2.getHeight();
y = y + content.getHeight();
content2.setBounds(x,y,100,100);
pane.add(content2);
frame.setGlassPane(pane);
frame.getGlassPane().setVisible(true);
frame.pack();
}
}
Conceptually you could make use of SwingUtilities.convertPoint or SwingUtilities.convertRectangle to convert between container contexts, for example...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
GlassPane glassPane = new GlassPane();
JFrame frame = new JFrame();
frame.setGlassPane(glassPane);
frame.add(new MainPane(glassPane));
glassPane.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface Tracker {
public void addTrackable(Trackable trackable);
public void removeTrackable(Trackable trackable);
}
public interface Trackable {
public JComponent[] getTrackedComponents();
}
public class MainPane extends JPanel {
private JLabel label = new JLabel("Catch me if you can");
public MainPane(Tracker tracker) {
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(32, 32, 32, 32));
add(label);
tracker.addTrackable(new Trackable() {
#Override
public JComponent[] getTrackedComponents() {
return new JComponent[] { label };
}
});
}
}
public class GlassPane extends JPanel implements Tracker {
private List<Trackable> trackables = new ArrayList<>(8);
public GlassPane() {
setOpaque(false);
}
#Override
public void addTrackable(Trackable trackable) {
trackables.add(trackable);
revalidate();
repaint();
}
#Override
public void removeTrackable(Trackable trackable) {
trackables.remove(trackable);
revalidate();
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
for (Trackable trackable : trackables) {
for (JComponent component : trackable.getTrackedComponents()) {
Rectangle relativeBounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), this);
g2d.draw(relativeBounds);
}
}
g2d.dispose();
}
}
}
Well, that's pretty boring, it's one component inside one container, let's trying something a little more complicated...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
GlassPane glassPane = new GlassPane();
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(2, 2, 8, 8));
frame.add(new MainPane(glassPane));
frame.add(new MainPane(glassPane));
frame.add(new MainPane(glassPane));
frame.add(new MainPane(glassPane));
frame.setGlassPane(glassPane);
glassPane.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface Tracker {
public void addTrackable(Trackable trackable);
public void removeTrackable(Trackable trackable);
}
public interface Trackable {
public JComponent[] getTrackedComponents();
}
public class MainPane extends JPanel {
private JLabel label = new JLabel("Catch me if you can");
public MainPane(Tracker tracker) {
setLayout(new GridBagLayout());
setBorder(new CompoundBorder(new LineBorder(Color.DARK_GRAY, 1, true), new EmptyBorder(32, 32, 32, 32)));
add(label);
tracker.addTrackable(new Trackable() {
#Override
public JComponent[] getTrackedComponents() {
return new JComponent[]{label};
}
});
}
}
public class GlassPane extends JPanel implements Tracker {
private List<Trackable> trackables = new ArrayList<>(8);
private List<Color> masterColors = new ArrayList<>(Arrays.asList(new Color[]{
Color.RED,
Color.GREEN,
Color.BLUE,
Color.CYAN,
Color.DARK_GRAY,
Color.GRAY,
Color.MAGENTA,
Color.ORANGE,
Color.PINK,
Color.YELLOW,}));
public GlassPane() {
setOpaque(false);
}
#Override
public void addTrackable(Trackable trackable) {
trackables.add(trackable);
revalidate();
repaint();
}
#Override
public void removeTrackable(Trackable trackable) {
trackables.remove(trackable);
revalidate();
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
List<Color> colors = new ArrayList<>(masterColors);
for (Trackable trackable : trackables) {
for (JComponent component : trackable.getTrackedComponents()) {
if (colors.isEmpty()) {
colors = new ArrayList<>(masterColors);
}
g2d.setColor(colors.remove(0));
Rectangle relativeBounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), this);
g2d.draw(relativeBounds);
}
}
g2d.dispose();
}
}
}
Here is a new smipler example program, trying to keep as close to your code as possible, that uses the convertRectangle but I can't manage to run it correctly
int y = (int) (r.getY() + r.getHeight()); ... are you deliberately trying to offset the "overlay"? This seems weird to me.
Another issue is, how does the GlassPane know when the child has changed position/size
So, I modified your code, getting rid of the "modification" to the x/y position (so I'm 100% sure that the conversion between context spaces is correct) and added a ComponentListener to monitor changes to the "target" component
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public class Main {
private Dimension pSize = new Dimension(100, 100);
private JFrame frame = new JFrame();
private JLabel content = new JLabel("Grief");
private JPanel content2 = new JPanel();
private SidePane sidePane = new SidePane();
private GlassPane glass = new GlassPane();
private Menu menu = new Menu();
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
content.setBackground(Color.green);
content.setPreferredSize(pSize);
content.setOpaque(true);
//setup frame basics
frame.setPreferredSize(new Dimension(500, 500));
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setGlassPane(glass);
frame.add(new MainPane());
// glass.setNewLocation();
// glass.revalidate();
frame.getGlassPane().setVisible(true);
// glass.setNewLocation();
frame.pack();
frame.setVisible(true);
}
});
}
public class MainPane extends JPanel {
public MainPane() {
//this.setBackground(Color.orange);
this.setLayout(new BorderLayout());
this.add(sidePane, BorderLayout.WEST);
this.add(menu, BorderLayout.NORTH);
}
}
public class SidePane extends JPanel {
public SidePane() {
FlowLayout f1 = new FlowLayout(FlowLayout.LEADING);
this.setLayout(f1);
this.setBackground(Color.blue);
this.add(content);
}
}
public class Menu extends JPanel {
public Menu() {
this.setBackground(Color.orange);
}
}
public class GlassPane extends JPanel {
private Rectangle target;
public GlassPane() {
this.setOpaque(false);
setLayout(null);
content2.setBackground(Color.BLACK);
content2.setPreferredSize(pSize);
content2.setOpaque(true);
add(content2);
content.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
updateOverlay();
}
#Override
public void componentMoved(ComponentEvent e) {
updateOverlay();
}
});
}
protected void updateOverlay() {
// Rectangle t = new Rectangle();
// t.setBounds((int) content.getLocation().getX(), (int) content.getLocation().getY(), content.getWidth(), content.getHeight());
// Rectangle r = SwingUtilities.convertRectangle(content.getParent(), content.getBounds(), this);
// Rectangle r = SwingUtilities.convertRectangle(content.getParent(), content.getBounds(), this);
target = SwingUtilities.convertRectangle(content.getParent(), content.getBounds(), this);
content2.setBounds(target);
// r = SwingUtilities.convertRectangle(content.getParent(), t, this);
// int x = (int) r.getBounds().getX();
// x = (int) r.getX();
// int y = (int) (r.getY() + r.getHeight());
//
// content2.setBounds(x, y, 100, 100);
// this.add(content2);
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g.create();
if (target != null) {
g2d.setColor(Color.RED);
g2d.draw(target);
}
g2d.dispose();
}
}
}
If you have the coordinate within the component, transfer it to screen coordinates using your component's convertPointToScreen(). Afterwards you can transfer back to see where in the window it sits by using the frame's convertPointFromScreen().
Or eliminate one of the two steps by directly using convertPoint().
Fixed the positioning issue using #MadProgrammer 's method of SwingUtilities.convertRectangle and called a new method at the end of the constructor which positioned the tracker panel.
Created a separate class for the glass pane
private class GlassPane extends JPanel {
public GlassPane() {
this.setLayout(null);
}
public void setNewLocation() {
Rectangle r = SwingUtilities.convertRectangle(top, content.getBounds(), this);
JPanel content2 = new JPanel();
int x = (int) r.getBounds().getX();
x = (int) r.getX();
int y = (int) (r.getY() + r.getHeight() + 1);
content2.setBounds(x, y, 100,100);
this.add(content2);
}
}
And added a call to the new method setNewLocation() at the end of the constructor
public test() {
**...**
frame.pack();
frame.setVisible(true);
glass.setNewLocation();
}
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 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);
}
}
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.