How do you resize a JButton at runtime so it adapts to the text given by setSize? I've done some searching and this is the code I've come up with so far. Could this be turned into a utility method?
FontMetrics metrics = getFontMetrics( font );
int width = metrics.stringWidth( string );
P.S: No layout manager is being used.
You need to use setPreferredSize() on the component. Then, to resize it, call setBounds().
I would probably subclass the button, and override the setText(String text) method to include the resizing code.
#Override
public void setText(String arg0) {
super.setText(arg0);
FontMetrics metrics = getFontMetrics(getFont());
int width = metrics.stringWidth( getText() );
int height = metrics.getHeight();
Dimension newDimension = new Dimension(width+40,height+10);
setPreferredSize(newDimension);
setBounds(new Rectangle(
getLocation(), getPreferredSize()));
}
For testing, I did this in the constructor of my new JButton subclass:
public ResizeToTextButton(String txt){
super(txt);
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
setText(JOptionPane.showInputDialog("Text"));
}
});
}
So, whenever I clicked on the button I could change the text and see if it resized properly.
I had the same problem, even when using a layout manager (BorderLayout). But in my case a simple call to layoutContainer() of the associated layout manager and then a repaint() on the JFrame was sufficient for changing the width of the button.
button1.setText("New Label that differs in width");
// button1 is inside the container horizontalBox
horizontalBox.getLayout().layoutContainer(horizontalBox);
repaint(); // on the containing JFrame
Related
I'm beginner in Java and I'm trying to make a simple text editor where the font and text size can be changed. It works although it has an issue that it seems that the text area size is fixed to the font and text size when something is changed.
I want the text area size all time be the same. I was debugging and I realize that rows and columns of text area never change.
Image before change font:
Image after change font:
Full code:
public class Run {
public static void main(String args[]) {
MainFrame main_frame = new MainFrame();
}
}
public class MainFrame extends JFrame{
private TextAreaPanel text_area_panel = new TextAreaPanel(60, 20);
private ComboBoxesPanel combo_boxes_panel = new ComboBoxesPanel(this.text_area_panel);
public MainFrame() {
this.initUI();
}
private void initUI() {
setLayout(new BorderLayout());
add(combo_boxes_panel, BorderLayout.NORTH);
add(text_area_panel, BorderLayout.CENTER);
setSize(getToolkit().getScreenSize().width / 2, getToolkit().getScreenSize().height / 2);
setLocation(getToolkit().getScreenSize().width / 4, getToolkit().getScreenSize().height / 4);
setTitle("Simple Text Editor");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
}
public class TextAreaPanel extends JPanel{
private JTextArea text_area = new JTextArea();
private JScrollPane scroll_text_area = new JScrollPane(text_area);
public TextAreaPanel(int width, int height) {
this.addComponets();
setTextAreaSize(width, height);
}
private void addComponets() {
add(scroll_text_area);
}
public void setTextAreaSize(int width, int height) {
text_area.setRows(height);
text_area.setColumns(width);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//setTextAreaSize(getWidth() / 15, getHeight() / 20);
}
public JTextArea getArea() {
return this.text_area;
}
}
public class ComboBoxesPanel extends JPanel{
private String[] system_fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
private Object[] sizes = {8,10,12,14,16,20,24,36, 48};
private JComboBox font_box = new JComboBox(system_fonts);
private JComboBox size_box = new JComboBox(sizes);
private TextAreaPanel text_area_panel;
public ComboBoxesPanel(TextAreaPanel text_area_panel) {
this.text_area_panel = text_area_panel;
font_box.addActionListener(font_listener);
size_box.addActionListener(size_listener);
add(font_box);
add(size_box);
}
private ActionListener font_listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
(text_area_panel.getArea()).setFont(new Font((String)font_box.getSelectedItem(),
Font.PLAIN, (int)size_box.getSelectedItem()));
}
};
private ActionListener size_listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
(text_area_panel.getArea()).setFont(new Font((String)font_box.getSelectedItem(),
Font.PLAIN, (int)size_box.getSelectedItem()));
}
};
}
Anyone knows how to solve this issue? Any idea how to solve this?
Also all the code in my GitLab repository if anyone wants to help.
https://gitlab.com/RichardCG/simpletexteditor
Thanks. :)
First of all I will rant a little about your variable names.
When you create a JTextArea you specify the "rows/columns" of the text area:
why do you use "width/height" as the variable names in your class? In Swing "width/height" are used to represent pixel values.
why did you change the order of the variables? You specify the "row, column" as the parameters of the text area. Why would you change your parameter order to "width, height". The "width" does NOT correlate to "rows".
It is confusing when the API is not consistent.
it seems that the text area size is fixed to the font and text size
Correct, all Swing components are responsible for determining their own preferred size. The size is based on the properties of the component. So when you change the font or font size the component will recalculate its preferred size.
This is a good thing as it allows:
the layout mangers to work properly
the scroll pane to work properly as the scrollbars will appear when needed.
I want the text area size all time be the same
You can control the size of the scroll pane, which in turn will keep the text area size the size while showing/hiding scrollbars as required.
In the constructor of you class you can add:
int scrollBarSize = UIManager.getInt("ScrollBar.width");
Dimension d = text_area.getPreferredSize();
d.width += scrollBarSize;
d.height += scrollBarSize;
scroll_text_area.setPreferredSize(d);
This works because the FlowLayout respects the preferred size of any component added to the panel.
However, this is NOT a good solution. Instead you should design your editor to be more user friendly.
For any editor I've used the editor component takes up all the available space in the frame. So you may have a tool bar (at the top), or status bar (at the bottom) and the editor takes up the remaining space. This allows the editor size to change as the user resizes the frame.
So typically you would just set the row/columns of the JTextArea when you create the text area and add the text area to a JScrollPane. Then the scroll pane is added to the CENTER of the BorderLayout on the. frame and you then pack() the frame BEFORE making the frame visible.
There is no need for the TextAreaPanel class.
I'm trying to create a mouseOver visual effect for several JLabel elements filled with text. The idea is to make each label darker when mouse enters and then return it to normal when the mouse leaves its area. Also all the labels are placed over a panel that has a background image.
Though simple enough, I've encountered a nasty behavior I can't overcome.
Bug 1: The first time I move mouse over a label it shows me the upper-left corner of my main window as its background.
Bug 2: Then, every time I move mouse over one label once, and then move it over the second label, the second one changes its background to the "summ background" (panel image + semitransparent background) of first label. Above that it seems that even the first label's text contents are being "copied" to the second label's background. This only happened once per label change: if I move mouse over the same label twice, the second mouse over event is painted correctly.
I've already tried to use MouseMotionListener, a different element (JButton), played with component modification methods and eve tried to override paint methods. No result.
I've attached an animated GIF showing the described behavior:
Two JLabels copying backgrounds and contents from each other
I'm relatively new to Swing so I'm not familiar with its caveats. Any idea what might cause this?
Custom Panel class:
public class ImagePanel extends JPanel{
private static final long serialVersionUID = -3995745756635082049L;
private Image image = null;
public ImagePanel(Image image){
this.image = image;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if(image != null){
g.drawImage(image, 0, 0, this);
}
}
}
MouseListener class:
public class MouseHoverPiece implements MouseListener{
private static final Cursor CURSOR_HAND = new Cursor(Cursor.HAND_CURSOR);
private static final Cursor CURSOR_DEFAULT = new Cursor(Cursor.DEFAULT_CURSOR);
private static final Color HOVER_SHADOW = new Color(40, 80, 60, 50);
#Override
public void mouseEntered(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(HOVER_SHADOW);
component.setCursor(CURSOR_HAND);
component.setOpaque(true);
component.repaint();
}
#Override
public void mouseExited(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(null);
component.setCursor(CURSOR_DEFAULT);
component.setOpaque(false);
component.repaint();
}
MainWindow class:
Image background = ResourceLoader.loadImage("board.png");
ImagePanel panel = new ImagePanel(background);
panel.setBounds(10, 55, 480, 480);
panel.setLayout(null);
panel_main.add(panel);
final JLabel lblNewLabel1 = new JLabel("N");
lblNewLabel1.setHorizontalAlignment(SwingConstants.CENTER);
lblNewLabel1.setOpaque(false);
lblNewLabel1.setBounds(25, 24, 52, 52);
lblNewLabel1.setFont(lblNewLabel1.getFont().deriveFont(42f));
lblNewLabel1.addMouseListener(new MouseHoverPiece());
panel.add(lblNewLabel1);
final JLabel lblNewLabel2 = new JLabel("O");
lblNewLabel2.setHorizontalAlignment(SwingConstants.CENTER);
lblNewLabel2.setOpaque(false);
lblNewLabel2.setBounds(25+52+2, 24, 52, 52);
lblNewLabel2.setFont(lblNewLabel2.getFont().deriveFont(42f));
lblNewLabel2.addMouseListener(new MouseHoverPiece());
panel.add(lblNewLabel2);
private static final Color HOVER_SHADOW = new Color(40, 80, 60, 50);
Swing components have problems with transparent backgrounds because you are breaking the painting rules which state that an opaque component will completely paint the background.
Check out Backgrounds With Transparency for more information and a couple of solutions to the problem. You can either:
do custom painting on the label to manually paint the background
use a wrapper component and have that component do the painting for you.
I think I've found the solution. Both bugs had disappeared. What I did was to add parent container's (in my case the panel with the board background) repaint:
#Override
public void mouseEntered(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(HOVER_SHADOW);
component.setCursor(CURSOR_HAND);
component.setOpaque(true);
Container container = component.getParent();
component.repaint();
container.repaint(); //fix
}
#Override
public void mouseExited(MouseEvent e) {
JLabel component = (JLabel)e.getComponent();
component.setBackground(null);
component.setCursor(CURSOR_DEFAULT);
component.setOpaque(false);
Container container = component.getParent();
component.repaint();
container.repaint(); //fix
}
Thanks everyone for your help ;)
Here's an 11x11 grid of JTextField objects I've made.
The colors are nice, but Shapes would be better (or both):
I don't think there is any way to add an existing shape (e.g., .PNG) to a JTextField or a JPanel, is there?
What should I do? I haven't yet ventured into Graphics class and I'd rather not, just now. But if that's the only way, OK.
I'm assuming that the user will be typing in the single character of each text field. There are a couple of possibilities:
1) Create an IconBorder. This would simply paint your supplied Icon on top of the text field. Simple proof of concept:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
public class IconBorder implements Border
{
private Icon icon;
private Insets borderInsets = new Insets(0, 0, 0, 0);
public IconBorder(Icon icon)
{
this.icon = icon;
}
//
// Implement the Border interface
//
#Override
public Insets getBorderInsets(Component c)
{
return borderInsets;
}
#Override
public boolean isBorderOpaque()
{
return false;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
icon.paintIcon(c, g, x+1, y+1);
}
private static void createAndShowUI()
{
JPanel panel = new JPanel();
panel.add( createTextField( new Ellipse2D.Double(0, 0, 30, 30) ) );
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
private static JTextField createTextField(Shape shape)
{
JTextField textField = new JTextField(1);
textField.setFont( new Font("Serif", Font.PLAIN, 18) );
OutlineIcon icon = new OutlineIcon(shape, Color.RED, 2);
CompoundBorder inner = new CompoundBorder( textField.getBorder(), new EmptyBorder(5, 10, 5, 10) );
CompoundBorder border = new CompoundBorder(new IconBorder(icon), inner);
textField.setBorder( border );
return textField;
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
The above code uses the OutlineIcon class found in Playing With Shapes to provide the Icon for the Border. Or you can use any "transparent" Icon that you have lying around.
But as you can see there is a lot of playing around with insets to try to get the text aligned properly. If you want to go with the Border approach, it may be better to create a "CircleBorder" and a "SquareBorder", then you can just paint the shape directly in the paintBorder(...) method and do the painting based on the size of the parent component.
2) Another approach would be to use a JLabel with an Icon. You can set the properties of the JLabel such that the text is both horizontally and vertically centered so that it paints on top of the label. In order to support keyboard input, you would then need to make each label focusable and add a KeyListener to listen for the key pressed and then set the text of the label. (I like this approach as the sizing of the components will be done easily based on the size of the Icon that you use).
3) Or finally you could use a JLabel with an Icon like above. But then you could set the layout manager to a BorderLayout and then add your JTextField to the label. You would need to make the JTextField non-opaque.
You can add an image to a JLabel. There is a constructor that takes an Icon as a parameter. This Icon could be something like an ImageIcon, which has a constructor that takes a filename to display.
I have a JTabbedPane object in my program and I override the getForegroundAt and getBackgroundAt methods in order to have different background colours when the tab is selected or not. I want to change the width and height of the tabs. I managed to do that using code similar as the following:
jtp.addTab("<html><body><table width='200'>Main</table></body></html>", mainPanel);
The problem is that if I use this html code to change the width of the tabs, the methods which I override are not longer called because the options are set with the html code. Is there a way to work around this problem? Is there html code that I can use in order to change the background colour of the tab depending on whether it is selected or not? Thanks.
Here's one way to change the width of the tabs by overriding calculateTabWidth(...) in the JTabbedPane's UI:
EDIT: MadProgrammer's comment is correct. I've changed the sample from BasicTabbedPaneUI to MetalTabbedPaneUI, since that's the default UI used for this sample. If you're specifiying a specific L&F for your app, then change the UI accordingly.
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.metal.*;
public class CustomTabWidthDemo implements Runnable
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new CustomTabWidthDemo());
}
public void run()
{
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.setUI(new MetalTabbedPaneUI()
{
#Override
protected int calculateTabWidth(int tabPlacement, int tabIndex,
FontMetrics metrics)
{
int width = super.calculateTabWidth(tabPlacement, tabIndex, metrics);
int extra = tabIndex * 50;
return width + extra;
}
});
tabbedPane.addTab("JTable", new JScrollPane(new JTable(5,5)));
tabbedPane.addTab("JTree", new JScrollPane(new JTree()));
tabbedPane.addTab("JSplitPane", new JSplitPane());
JPanel p = new JPanel();
p.add(tabbedPane);
JFrame frame = new JFrame();
frame.setContentPane(p);
frame.pack();
frame.setVisible(true);
}
}
I want to be able to Enter Text that will appear on the window and then if I were to re-size the window then the text could change with its size. The problem is that I'm not entirely sure how to implement this. Since I will to writing to the Jpanel I'm thinking I would have to use a Jlabel to add the text onto the window, but then resizing the window might not affect the Jlabel? Am I suppose to directly write on the Jpanel somehow, or get the size of the window(length and width) and use that to increase or decrease the size of the text? Any help would be appreciated.
If I understand your question correctly that you are trying to re-size some label type text you could try something along these lines:
public class ResizeTextTest extends JFrame{
public ResizeTextTest() {
add(new MyPanel());
}
public static void main(String[] args) {
ResizeTextTest t = new ResizeTextTest();
t.setSize(400,300);
t.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
t.setVisible(true);
}
class MyPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Using a multiplier like this on the width and height of the
//Panel will give you a text size that correlates with the size
//of the window.
Font f = new Font("Arial", Font.PLAIN,
(int) (.0005 * this.getWidth() * this.getHeight()));
g.setFont(f);
g.drawString("Hello World", 50, 100);
}
}
The paintComponent method is automatically called when the window is resized. You can also call it explicitly using the component's repaint() method.
Use a text area, not a label. Make sure the 'line wrap' property is selected/true.