Let's say I have a JButton, and I want it to be big enough to fit a string of 8 "M" characters, regardless of the string that is actually assigned to it and the font size, without using elipsis.
The JButton has to have precisely this size, no more, no less. Layout manager in use is GridBagLayout.
I tried overwriting the getPreferredSize() method and perform a calculation using the string and the current font of the system. The calculation gives me back some sensible value, however, I have no idea how to set the preferred size in such a way that the borders are also considered.
I tried to get the insets of the component, but they are all 0's.
This is the code of my method:
public void getPreferredSize() {
Dimension d = super.getPreferredSize();
// Geometry width indicates how many characters must fit
char[] pad = new char[propGeometryWidth];
Arrays.fill(pad, 'M');
String tmpTemplateString = new String(pad);
FontMetrics tmpMetrics = getFontMetrics(getFont());
Rectangle2D tmpR2D = tmpMetrics.getStringBounds(tmpTemplateString, getGraphics());
int tmpWidth = (int)tmpR2D.getWidth();
int tmpHeight = (int)(tmpR2D.getHeight() * propGeometryHeight + tmpR2D.getHeight());
// We need to take into consideration borders and padding!
Insets insets = getInsets();
Dimension tmpSize = new Dimension(tmpWidth + insets.left + insets.right, tmpHeight + insets.top + insets.bottom);
return tmpSize;
}
I get the feeling that this might be related to the fact that my component is not realized yet, but I am completely unsure how I could solve this issue. Am I approaching this problem from the wrong perspective?
I think you may actually be doing it right already. From the Javadoc for getInsets():
If a border has been set on this component, returns the border's insets; otherwise calls super.getInsets.
A freshly-created JButton for me shows insets of java.awt.Insets[top=5,left=17,bottom=5,right=17] with the default look and feel, and java.awt.Insets[top=4,left=16,bottom=4,right=16] with the Windows look and feel. Are you using a custom look and feel, perhaps?
I found the reason for my problem. The problem is that I had a panel with a JButton inside, and I overwrote the method on the panel (There is a relatively complex hierarchy of classes). Then, of course, the insets for the Panel are all set to 0. After getting the insets for the button, as stated by Mr. Mmyers, it all works great.
Related
I looked around at all the other answers, but they all recommend to use GroupLayout, BoxLayout, or to wrap the panel that's using GridBagLayout with another panel that uses one of the layouts mentioned above.
I'm currently using GridBagLayout, and I'm wondering if there's a way to set the maximum height of a panel. I don't want a limit on width, only height, so setMaximumSize(Dimension) won't work.
Does GridBagLayout support this in any way? Sorry if my question doesn't contain any code, the only attempt I could possibly find is setMaximumSize(Dimension), or wrapping it in another panel (which I'm hoping to avoid)
If you want to limit only one dimension then just do not limit the other:
component.setMaximumSize( new Dimension(
Integer.MAX_VALUE,
requiredMaxHeight
) );
Well, I would say that almost always the right solution is to fix the layouting either by using a different layout manager, or by using a different hierarchy of containers (or both).
However, since it seems you won't be persuaded (I infer that from your question), I can suggest a solution to the specific question you ask (again, I would recommend to take a different path of fixing the layout, which probably is your real problem).
You can set the maximum height without affecting the maximum width, by overriding the setMaximumSize() method as follows:
#Override
public void setMaximumSize(Dimension size) {
Dimension currMaxSize = getMaximumSize();
super.setMaximumSize(currMaxSize.width, size.height);
}
Another approach can be to keep the "overridden" setting of the max height, and return it when returning the maximum height, like so:
private int overriddenMaximumHeight = -1;
public void setMaximumHeight(int height) {
overriddenMaximumHeight = height;
}
#Override
public Dimension getMaximumSize() {
Dimension size = super.getMaximumSize();
int height = (overriddenMaximumHeight >=0) ? overriddenMaximumHeight : size.height;
return new Dimension(size.width, height);
}
Again (lastly), I would recommend taking a more common approach, but if you insist ...
I have JLabel which I would like to change its size while I resize the window. When JLabel contains String which is too big, the String should be shortened, with right part visible and adds dots on the left hand side of the String.
My JLabel is inside innerPanel which is a header in middlePanel which is added to outerPanel. So when I resize window I use listener on outerPanel in that way:
outerPanel.addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent evt) {
int width = ((JPanel) evt.getSource()).getWidth();
windowSize = width;
refresh();
}
// [...] other not used override methods
});
refresh() repaints view and creates new middlePanel where is called class which creates innerPanel where is located my JLabel:
Public class InnerPanel extends JPanel {
private int maxSize;
String string = "<VERY_LONG_STRING>";
private static final int DEFAULT_INDEND_PIXEL = 70;
public InnerPanel(int windowSize) {
maxSize = windowSize - DEFAULT_INDENT_PIXEL;
createPanel();
}
private createPanel() {
// [...] gridbag and GridBagConstraints implementation
String shortString = countString();
JLabel label = new JLabel(shortString);
add(label,gc);
}
private String countString() {
int index = 0;
boolean toBig = true;
StringBuilder sb = new StringBuilder(string);
while(toBig) {
Rectangle2d rect = // [...] code which creates rectangle around text from sb.toString()
// I have no access to repo at home but if it's important I can paste it tomorrow
if(rect.getWidth() > maxSize)
sb.deleteCharAt(0);
else
toBig = false;
}
return sb.toString();
}
}
That's works fine in general, bacause it do resize JLabel in one step when I enlarge window in width. But the problem is appear when I try to reduce the window in width. In this case componentResized() calculate width step by step (and it's called multiple times), gradually decreases width by some amount of pixels till it reach real window size. It's behave in that way even thow I change window size in one step from maximum size to 800. Whole process is so slow, that it takes around a second to fit string to window size. So it looks bit like an animation.
The problem is very rare to me, bacause width in componentResized() method is calculeted step by step only when I assign windowSize variable.
When I give windowSize fixed size like for example 500 - componentResized() is called only onces - with correct width indicated real window size (!!) - and there's no its step by step decrease!
It's look like width variable which is assigned by ((JPanel) evt.getSource()).getWidth() knows that windowSize is used to dynamically change size of JLabel component even before first call of refresh() method.
If anyone have an idea what is going on here - I will be very appreciate for help.
You may be able to adapt one of the approaches shown here to better effect. As shown here, the ellipsis is supplied by the label's UI delegate via a call to SwingUtilities2.clipString(), which appends the clipString. Rather than re-invent the label UI, use TextLayout to determine the required geometry, prepend the ellipsis, and handle the alignment in a table or list renderer, as shown here.
I have a JPanel in which I need to add bunch of JLabels at a required coordinates. These JLabels will have key Listeners assigned to them that will determine new position using arrow keys.
To be more specific I know how to do it when there is only one JLabel but whenever I put a more of them the things mess up. while I use arrow key the first JLabel moves but all other JLabel disappears.
Can Anyone give me some hints to write a method to put a JLabel in a specific coordinate and also move them using arrow key later without making other JLabels dissapear?
Huge Thanks in Advance
You can try using JDesktopPane or JLayeredPane, it works the same as the JPanels, but you won't use layouts, with these you will use Bounds, you always have to set the bound of a jlabel like this.
JLabel label = new JLabel("Hello");
label.setBounds(0, 0, 100, 20);
//label.setBounds(x, y, width, height);
pane.add(label)
if you need to move that label, then you can use something like
int xx = label.getBounds().getX();
int yy = label.getBounds().getY();
int ww = label.getBounds().getWidth();
int hh = label.getBounds().getHeight();
//to the right 10 units
xx+=10;
label.setBounds( xx, yy, ww, hh );
I assume you are using repaint() to update the UI. Btw, upon which component you are calling repaint()?
In all the examples that I can find that use a JTextArea, the height & width is known before constructing the JTextArea, and if the JTextArea would require more height, then it is put inside of a JScrollPane. Obviously, the height of JTextArea is dependent on the width and the text contents.
Now, my situation requires that I do not use a JScrollPane, but instead that the JTextArea be just tall enough to display all the text. When I create the JTextArea, I know the text contents and how much width it will have to work with; I don't know the height - I want that to be as small as possible without cutting off any of the text. This seems very difficult to accomplish.
As a side note, the JTextArea will be added to a JPanel that does not have a layout manager - it uses absolute positioning based on the added component's preferred size. This requires that my JTextArea would return the correct dimensions on getPreferredSize(). The correct dimensions should be the width that I provided when I constructed it, and the minimum height that is required to display all the text with the provided width.
I've found some similar threads that discuss the oddities/bugs involved with the JTextArea that are sometimes solved by calling pack() twice on the parent container. This is not an option for me. I'm tempted to basically create my own JTextArea that takes a width and String and computes the necessary minimum height based on the width and font settings, but I figured I would ask around first before spending the time to do that.
Hopefully my question is clear. Thank you all for your help!
it uses absolute positioning based on the added component's preferred size.
Sounds like the job of a layout manager.
This requires that my JTextArea would return the correct dimensions on getPreferredSize().
JTextArea textArea = new JTextArea();
textArea.setLineWrap( true );
textArea.setWrapStyleWord( true );
textArea.setText("one two three four five six seven eight nine ten");
System.out.println("000: " + textArea.getPreferredSize());
textArea.setSize(100, 1);
System.out.println("100: " + textArea.getPreferredSize());
textArea.setSize( textArea.getPreferredSize() );
import java.awt.*;
import javax.swing.*;
class FixedWidthLabel {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
String pt1 = "<html><body width='";
String pt2 =
"px'><h1>Label Height</h1>" +
"<p>Many Swing components support HTML 3.2 &" +
" (simple) CSS. By setting a body width we can cause the " +
" component to find the natural height needed to display" +
" the component.<br><br>" +
"<p>The body width in this text is set to " +
"";
String pt3 =
" pixels." +
"";
JPanel p = new JPanel( new BorderLayout() );
JLabel l1 = new JLabel( pt1 + "125" + pt2 + "125" + pt3 );
p.add(l1, BorderLayout.WEST);
JLabel l2 = new JLabel( pt1 + "200" + pt2 + "200" + pt3 );
p.add(l2, BorderLayout.CENTER);
JOptionPane.showMessageDialog(null, p);
}
};
SwingUtilities.invokeLater(r);
}
}
The solution described in FixedWidthLabel , using <html><body width="..."
will require the programmer to provide the message as part of the html string.
If the message is something like invalid integer: i<0 not allowed ,
then the < will have to be escaped (encoded?), otherwise there is no telling how JLabel will interpret the html.
This adds complexity to this solution.
Only if you know that the message doesn't contain any such characters, you will be allright.
Well perhaps if you know your width you could run some tests and work out how wide each character of text is, that way you could use a loop to determine how many characters fit on each line and total the characters that are to be shown, then you could set the height based on how many lines there are to be.
Say your text has 1000 characters including blank spaces, and the width of a character is equivalent to 4pixels, then you can work out if the width is 400 that 100 characters fit on each line, subsequently you will need 10 lines. Now say the height is 10 for the font size, you now know you need 10 x 10 == 100 pixels, so your TextArea should be 400x100
I'm trying to make a Swing JLabel with multiple lines of text. It's added just fine, but the line breaks don't come through. How do I do this? Alternatively, can I just specify a maximum width for a JLabel and know that the text would wrap, like in a div?
private void addLegend() {
JPanel comparisonPanel = getComparisonPanel();
//this all displays on one line
JLabel legend = new JLabel("MMM FFF MMM FFFO O OOM M MMMM.\nMMM FFF MMM FFFO O OOM M MMMM.\nMMM FFF MMM FFFO O OOM M MMMM.\n");
comparisonPanel.add(legend);
}
Use HTML in setText, e.g.
myLabel.setText("<html><body>with<br>linebreak</body></html>");
You can get automatic line break if you set the paragraph width in html.
label.setText("<html><p style=\"width:100px\">"+paragraph+"</p></html>");
By default, Swing does not wrap text. If you specify a size on the JLabel it will only paint the part of the text that fits and then add "..." to the end.
As suggested you can use HTML to enable line wrapping. However, I've actually created a custom Swing UI delegate not long ago to achieve this and even more: MultiLineLabelUI.
It will wrap your text to fit the available space and also respect hard line breaks. If you choose to try it out, it is as simple as:
JLabel label = new JLabel("Text that'll wrap if necessary");
label.setUI(MultiLineLabelUI.labelUI);
Or alternatively use the custom MultiLineLabel class that in addition to wrapping text supports vertical and horizontal text alignment.
UPDATE
I lost the domain with the original code samples. It can now be viewed on github instead: https://github.com/sasjo/multiline
You can put HTML inside of a JLabel and use the linebreak tag to achieve this.
What about using the wrapping feature in a JTextArea?
String text = "some really long string that might need to"+
"be wrapped if the window is not wide enough";
JTextArea multi = new JTextArea(text);
multi.setWrapStyleWord(true);
multi.setLineWrap(true);
multi.setEditable(false);
JLabel single = new JLabel(text);
JPanel textpanel = new JPanel(new GridLayout(2,1));
textpanel.add(multi);
textpanel.add(single);
JFrame frame = new JFrame();
frame.add(textpanel);
frame.pack();
frame.setVisible(true);
Simple,use HTML. Java Swing components though does not provide a 'fantastic' support for the HTML, you can use it for such simple purposes.
label.setText("<html>This is first line.<br/>This is second line.</html>");
I did not manage to specify a maximum width for a label but you can specify a concrete width.
By measuring the current width of a JLabel we can only apply the new fixed width if the JLabels's width is higher that our maxWidth:
JLabel label = new JLabel("<html>" + myVeryLongMessage + "<html>");
int maxWidth = 400;
Dimension size = label.getPreferredSize();
if (size.width > maxWidth) {
// Estimate the number of lines
int lineCount = (int) Math.ceil(((double) size.width) / maxWidth);
lineCount += 1; // Add one extra line as reserve
size.width = maxWidth; // Apply the maximum width
// Increase the height so that all lines will be visible
size.height *= lineCount;
label.setPreferredSize(size);
}
You can use a JTextArea and disable the TextArea, this way, you will only display what you want, and the user won't be able to type in
JTextArea area = new JTextArea("Here \n\n you \n\n put \n\n your \n\n text");
area.setBounds(10, 11, 500, 143);
area.setEditable(false);
yourPannel.add(area);