BoxLayout can't align single element with odd width to center - java

I am trying to align a number of elements along a vertical axis in the center of a panel, and BoxLayout seems to be just what I need. However, it seems to do strange things when all the elements added have odd-numbered widths.
Here is a SSCCE that demonstrates this screwy behavior:
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.JFrame;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.BoxLayout;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Component;
public class BoxBug {
public static void main(String[] args){
UIManager.put("swing.boldMetal", Boolean.FALSE);
SwingUtilities.invokeLater(new Runnable(){
public void run(){
gui();
}
});
}
public static void gui(){
JFrame f = new JFrame("Title");
Box b = new Box(BoxLayout.Y_AXIS);
JComponent c = new JComponent(){
public void paint(Graphics g){
g.setColor(new Color(255, 0, 0));
g.fillRect(0, 0, getWidth(), getHeight());
}
// just change the first argument here
// (even numbers work fine, odd ones fail)
private Dimension p = new Dimension(3, 20);
public Dimension getPreferredSize(){return p;}
public Dimension getMinimumSize(){return p;}
public Dimension getMaximumSize(){return p;}
};
c.setAlignmentX(Component.CENTER_ALIGNMENT);
b.add(c);
f.add(b);
f.pack();
f.setVisible(true);
}
}
This is what it looks like:
When I change the width of the JComponent from 3 to 4, it works fine:
Then it fails again when I change the width to 5:
I've searched on Google and StackOverflow for this issue, but haven't found any documentation on this, so it seems to me like a bug.
If it is a bug, can someone find a hack to get around it?

However, it seems to do strange things when all the elements added have odd-numbered widths.
It gets stranger than that. The size of the parent container also affects the layout.
I replaced the f.pack() with:
f.setSize(150, 100);
and it doesn't work. This is basically the scenario you described since this method or the f.pack() will result in the parent container to have an even width and the layout doesn't work.
However, if you use:
f.setSize(151, 100);
the parent container has an odd width and the layout does work properly.
Another strange observation. I tried adding multiple components to the Box. The problem only seems to happen when the last component added has an odd width.
Anyway, I have no idea what the box layout is doing, but it sure seems like a bug to me.
The solution is to use a different layout manager. You can use a GridBagLayout to display components on different rows. You will need to set the constraint for each component to go to a new row.
Or you could try to use the Relative Layout, which support vertical layout with centered alignment and you don't need any constraints. The only change to your code would be:
//Box b = new Box(BoxLayout.Y_AXIS);
JPanel b = new JPanel( new RelativeLayout(RelativeLayout.Y_AXIS) );

Related

JPanel size not being set

I'm currently working on an irc bot and today I would like to create a GUI for it.
I'm trying to create a column layout that has two parts, left and right.
The left side will show console output, the right will contain all the controls (joining channels, commands etc).
I'm having issues creating the two columns. I have a JPanel that is the whole width and height of the window and has a border of 10 pixels, and then I have two panels within that; left and right.
The left and right panels are for some reason taking the whole size of the window, and the right panel is overlapping everything.
Here's an example picture: http://i.imgur.com/lc4vHVH.png
The white is the right panel, it should only be half the size and have an identical but black panel on the left of it.
Here's my current code, sorry if it's messy, new to the whole Swing GUI.. Thanks for any help.
package tk.TaylerKing.GribbyBot;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import javax.swing.BorderFactory;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import com.alee.laf.WebLookAndFeel;
import com.alee.laf.menu.WebMenuBar;
import com.alee.laf.menu.WebMenuItem;
import com.alee.laf.panel.WebPanel;
import com.alee.laf.rootpane.WebFrame;
public class GribbyBot extends WebFrame {
private static final long serialVersionUID = 4641597667372956773L;
public static HashMap<String, ArrayList<String>> connections = new HashMap<String, ArrayList<String>>();
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(WebLookAndFeel.class.getCanonicalName());
SwingUtilities.invokeLater(new Runnable(){
public void run(){
GribbyBot gb = new GribbyBot();
gb.setVisible(true);
}
});
}
public GribbyBot(){
WebPanel panel = new WebPanel();
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.setPreferredSize(new Dimension(780, 580));
WebMenuBar menu = new WebMenuBar();
WebMenuItem file = new WebMenuItem("Exit");
file.setMnemonic(KeyEvent.VK_E);
file.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
menu.add(file);
setJMenuBar(menu);
WebPanel left = new WebPanel();
left.setPreferredSize(new Dimension(380, 580));
left.setBackground(Color.BLACK);
WebPanel right = new WebPanel();
right.setPreferredSize(new Dimension(380, 580));
right.setBackground(Color.WHITE);
add(panel);
panel.add(left);
panel.add(right);
setTitle("GribbyBot");
setSize(800, 600);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
}
}
On a side note, all the variables that are prefixed with "Web" are the same as Swing, but it's a custom GUI.
Override JComponent#getPreferredSize() instead of using setPreferredSize()
Read more Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
If extremely needed in case of Performing Custom Painting then try in this way:
Sample code:
final JPanel panel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
// your custom painting code here
}
#Override
public Dimension getPreferredSize() {
return new Dimension(40, 40);
}
};
Why are using setPreferredSize() method whereas you can achieve this design easily using any proper layout such as BoxLayout, GridLayout, BorderLayout etc.
Read more about layout How to Use Various Layout Managers
EDIT
try JPanel panel = new JPanel(new GridLayout(1,2));

Layout trouble within JTextPane

I am sorry for the lack of a better title, but I have no idea how to specify the error further, since I don't understand its nature. Maybe someone can edit it, when the problem is understood.
I am writing an application in which the user can add icons into a text field. I obviously picked a JTextPane to display the text and the icons. After playing around with the insertComponent() function of the class I ran into some weird layout problems, so I decided to lookup the tutorial at oracle.com. After looking at the source code of the example, I decided to do the same and also use styles to add components to the underlying StyledDocument. It was when I started the first test run, when I discovered, that the layout problems stayed the same.
So, what is actually happening?
What I intended the text pane to show is "abcOdefO", but as you can tell by the screenshot, the two icons (circles) have some space to the right of them. I want the icon to be treated as a slightly larger character, so it should only occupy as much space as it needs, not (availableSpace / numberOfIcons), which seems to be what it actually occupies.
When typing another character at the caret position:
This is even weirder. If the icons have MouseListeners, all 4 visible circles trigger the event. If I drag the frame to another window or minimize and restore it, the weird parts vanish and the frame looks like the first image (except for the additional character). So I guess, this part of my problem is fixed with a call to repaint() at the correct location - but where?
This is the code that produces the images seen above:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
public class TextPaneTestPanel extends JPanel {
private class myIcon extends JPanel {
private final int side;
private final int padding = 1;
public myIcon(int size) {
this.side = size - 2 * padding;
this.setSize(size, size);
this.setPreferredSize(getSize());
this.setMinimumSize(getSize());
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(2));
g2d.drawOval(padding, padding, side, side);
}
}
private final JTextPane textPane;
public TextPaneTestPanel() {
textPane = new JTextPane();
StyledDocument doc = textPane.getStyledDocument();
Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
StyleConstants.setFontFamily(def, "Monospaced");
Style regular = doc.addStyle("regular", def);
try {
doc.insertString(0, "abc", regular);
Style s1 = doc.addStyle("icon1", regular);
StyleConstants.setComponent(s1, new myIcon(20));
doc.insertString(3, " ", s1);
doc.insertString(4, "def", regular);
Style s2 = doc.addStyle("icon2", regular);
StyleConstants.setComponent(s2, new myIcon(20));
doc.insertString(7, " ", s2);
} catch (BadLocationException e1) {
e1.printStackTrace();
}
this.setLayout(new GridBagLayout());
this.add(textPane, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(4, 4, 4, 4), 0, 0));
}
public static void main(String[] args) {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
TextPaneTestPanel panel = new TextPaneTestPanel();
frame.getContentPane().add(panel);
frame.setSize(300, 100);
frame.setVisible(true);
}
}
To sum up my questions:
What causes the space to appear after an icon?
Where do I add the repaint() or revalidate() to fix the problem seen in image #2?
P.S.: I know, that my "icon" does not implement Icon, but it shouldn't be necessary, since JTextPanes can handle all sorts of Components.
1.What causes the space to appear after an icon?
Well, you have the following code:
this.setMinimumSize(getSize());
What about the maximum size?
How to fix the problem seen in image #2?
Custom painting is done by overriding the paintComponent() method, not the paint() method and don't forget to invoke super.paintComponent.
An Icon would be more appropriate here since all you are doing is custom painting. Or even a JComponent, but not a JPanel which is a Container used for holding other components. Plus is you use an Icon you don't have the size problems that you have with a panel, or JComponent since that method is specifically implemented as part of the interface.

How to get the painted size of a Swing component?

When I add Swing component (like a JButton) to a JPanel, it renders with it's 'preferred size'.
However, the preferred size is actually larger than the painted button. There appears to be an invisible border around it.
Here's a simple frame with my test panel:
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TestPanel pnl = new TestPanel();
frame.getContentPane().add(pnl);
frame.pack();
frame.setVisible(true);
Here's my test panel ...
public class TestPanel extends JPanel {
JButton btn1 = new JButton("Test1");
JButton btn2 = new JButton("Test2");
public TestPanel() {
this.add(btn1);
this.add(btn2);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.RED);
Dimension dim = btn1.getPreferredSize();
g.drawRect(btn1.getX(), btn1.getY(), (int)(dim.getWidth()), (int)(dim.getHeight()));
}
}
Notice I painted btn1's "PreferredSize" in RED to demonstrate that the preferredSize is actually larger than the button itself.
My question is, how can I determine the width and height of the painted button, not the JButton's preferredSize?
Any help is greatly appreciated, thanks!
UPDATE
Because I actually need this to work for all Swing components, here's a screen shot with the more components.
Unfortunately, I need to figure this out, determining the "real" size of the visible widget is crucial to my application.
I don't think this is particular or practically achievable.
The problem is, the button is using the "unpainted" area to paint other elements, like the focus highlight.
You could try look at the AbstractButton#set/getMargin
If nothing better comes along, note that the authors "recommend that you put the component in a JPanel and set the border on the JPanel."
Addendum: Based on your comments below, it's clear that your question is not about rendering borders but about establishing a component's boundary. What you perceive as unused space is actually reserved by the UI delegate for any number of uses, e.g. selection highlighting or esthetic coherence. You can get an idea of how this varies by selecting different Look & Feel themes in the examples here and here.
Using getbounds():
Using setBorder():
import component.Laf;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
* #see https://stackoverflow.com/a/15490187/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new FlowLayout());
// https://stackoverflow.com/a/11949899/230513
f.add(Laf.createToolBar(f));
f.add(decorate(new JButton("Test")));
f.add(decorate(new JTextField("Test")));
f.add(decorate(new JTextArea(3, 8)));
f.add(decorate(new JCheckBox("Test")));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private JPanel decorate(final JComponent c) {
JPanel p = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle r = c.getBounds();
g.setColor(Color.red);
// NB pen hangs down and to the right
g.drawRect(r.x - 1, r.y - 1, r.width + 1, r.height + 1);
}
};
p.add(c);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}

AWT Scrollpane scrollbars flash on resize

I am having issues with the java.awt.ScrollPane class's SCROLLBARS_AS_NEEDED display policy handling component resize. Ideally, if I have a ScrollPane that contains a Component that is significantly smaller than the Scrollpane and I shrink the Scrollpane to a size that is still greater than the child component, no scrollbars will appear. However, in practice both scrollbars seem to flicker while the resize operation is occurring and may even persist until the next redraw after the operation completes. Here is a small example to demonstrate what I mean:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.ScrollPane;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Tester implements Runnable{
/**
* #param args
*/
public static void main(String[] args) {
System.setProperty("sun.awt.noerasebackground", "true");
Tester t = new Tester();
SwingUtilities.invokeLater(t);
}
#SuppressWarnings("serial")
#Override
public void run() {
JFrame frame = new JFrame("Tooltip tester");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 800);
Canvas c = new Canvas(){
#Override
public void paint(Graphics g){
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
}
};
c.setPreferredSize(new Dimension(400, 400));
ScrollPane s = new ScrollPane();
s.add(c);
frame.add(s);
frame.setVisible(true);
}
}
Shrinking the window in the application above should cause scrollbars to flash. I believe this is caused by a bug in the implementation of the peer of the ScrollPane. (Source Here) I've copied the method that I believe has the error (line 145 in the link).
Dimension getChildSize() {
ScrollPane sp = (ScrollPane)target;
if (sp.countComponents() > 0) {
Component c = sp.getComponent(0);
return c.size();
} else {
return new Dimension(0, 0);
}
}
To me, it seems like the getChildSize() method should call c.getPreferredSize() rather than c.size(). If the ScrollPane child's size is greater than its current preferred size, it should be able to shrink without scroll bars showing up (in my opinion). To test this theory, I overrode the size() method in my example from above:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.ScrollPane;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Tester implements Runnable{
/**
* #param args
*/
public static void main(String[] args) {
System.setProperty("sun.awt.noerasebackground", "true");
Tester t = new Tester();
SwingUtilities.invokeLater(t);
}
#SuppressWarnings("serial")
#Override
public void run() {
JFrame frame = new JFrame("Tooltip tester");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 800);
Canvas c = new Canvas(){
private Dimension prefSize = new Dimension();
#Override
public void paint(Graphics g){
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
}
#Override
public void setPreferredSize(Dimension preferredSize) {
prefSize = preferredSize;
super.setPreferredSize(preferredSize);
}
#Override
public Dimension size() {
return prefSize;
}
};
c.setPreferredSize(new Dimension(400, 400));
ScrollPane s = new ScrollPane();
s.add(c);
frame.add(s);
frame.setVisible(true);
}
}
This code behaves exactly how I want it to. However, overriding the size() method to do something other than what the documentation says it should do isn't a particularly elegant way to fix this problem. I feel like I must be doing something wrong here. Is it possible that a fairly basic functionality of the ScrollPane peer for X11 is broken?
A few other related notes:
-Use of java.awt.Canvas is non-negotiable for my purposes. In my actual application, I am drawing something very quickly using Java AWT Native Interface.
-While I read the documentation about new improvements to light/heavyweight compatibility in Java 7 and later versions of Java 6, I didn't really see an improvement in either case so I decided to stick with awt.ScrollPane over swing.JScrollPane. Either way, a ScrollPane should work correctly in the simple case I showed above.
hard to help you without SSCCE, for future readers
However, in practice both scrollbars seem to flicker while the resize operation is occurring and may even persist until the next redraw after the operation completes
used LCD/LED panel caused those flickering (including MsExcell e.g., btw many times discused),
same flickering is for Swing JFrame with JScrollPane
not presented on CRT or Plasma display
if you playing FullHD video on PC, then every good multimedia players waiting until resize ended, thenafter fill available space into its container
you have to add ComponentListener, put there Swing Timer with small delay (KMPlayer show me 350-500milisecs), untill resize continue call for Timer#restart,
Canvas is good workaround for CAD / CAM, OpenGL(CL), good Java video players are based on AWT
you have to avoiding mixing Swing Container with AWT contents, lets everything is based on AWT (undecorated container)

Custom JComponent only Displays on JFrame not JPanel

Can somebody please help me understand why my custom JComponent 'Bar', only displays when added directly to the JFrame, and not when added to a JPanel (which is then added to the JFrame)?
Thanks!
package main;
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Board {
public void start(){
JFrame frame = new JFrame();
JButton button1 = new JButton("Button 1");
Bar statusbar = new Bar();
JLabel status = new JLabel("Status: ");
JPanel topPanel = new JPanel();
topPanel.add(status);
topPanel.add(statusbar);
JPanel mainPanel = new JPanel();
mainPanel.add(button1);
mainPanel.add(statusbar);
frame.getContentPane().add(BorderLayout.NORTH, topPanel);
frame.getContentPane().add(BorderLayout.SOUTH, mainPanel);
frame.getContentPane().add(BorderLayout.CENTER, statusbar);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200,100);
frame.setVisible(true);
}
}
Here is my Bar Class...
package main;
import java.awt.Graphics;
import javax.swing.JComponent;
public class Bar extends JComponent{
public void paint(Graphics g){
g.fillRect(0, 0, 100, 10);
}
}
You're adding statusbar to several different places in the component tree, Swing doesn't deal with that well (or at all).
Create a separate Bar instances each time you use it, if you want their display to be synchronized, they should share the same model.
Edit
Ah, on a second glance, the problem here is that you never set a size (or preferred size) for the Bar components, so they get squished to 0 by the layout manager.
Try:
public static class Bar extends JComponent {
private Bar() {
setPreferredSize(new Dimension(25, 5));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillRect(0, 0, 100, 10);
}
}
You should also add a frame.pack() before display.
(the multiple references to the same component thing is still true, too)
The dimension of the custom component is (0, 0).
When added to a container with a BorderLayout layout manager, it will expand to fill the available space, and therefore become visible.
When added to a container with a FlowLayout layout manager, it will not expand and will instead remain at its preferred size (i.e. (0, 0)). And therefore, will not become visible, albeit it is still there.
This explains why the custom component is only displayed when added directly to the JFrame, since it uses a BorderLayout layout manager, whereas a JPanel uses a FlowLayout layout manager.

Categories