How to make JFrame look and feel aware - java

When I set a look and feel through the UIManager.setLookAndFeel the frame's content changes its look and feel as expected. E.g.
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
JFrame frame = new JFrame();
Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());
contentPane.add(new JButton("Some Button"));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(200, 80);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
But the frame is still painted using the OS default (Windows in my case).
How can I make the frame look and feel aware so that it looks like the e.g. the nimbus look and feel?
Usually look and feel is implemented using the ComponentUI, but a JFrame is not a JComponent so I can't implement an own ComponentUI and set it.
1. Solution
My first thought was to use an undecorated JFrame with a JInternalFrame as its main component.
public class LAFAwareJFrame extends JFrame {
private JInternalFrame lafFrame = new JInternalFrame("", true, true, true, true);
private JDesktopPane desktopPane = new JDesktopPane();
public LAFAwareJFrame() {
super.setUndecorated(true);
desktopPane.add(lafFrame);
lafFrame.setVisible(true);
Container contentPane = super.getContentPane();
contentPane.add(desktopPane);
}
#Override
public void setUndecorated(boolean undecorated) {
throw new UnsupportedOperationException("Can't change the undecorated property for a LAFAwareJFrame");
}
#Override
public void setSize(int width, int height) {
super.setSize(width, height);
lafFrame.setSize(width, height);
}
#Override
public Container getContentPane() {
return lafFrame.getContentPane();
}
#Override
public void setTitle(String title) {
lafFrame.setTitle(title);
}
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
JFrame frame = new LAFAwareJFrame();
Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());
contentPane.add(new JButton("Some Button"));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(200, 80);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
But then I have a lot to do with event handling and delegation. E.g when the JInternalFrame gets moved I don't really want to move the internal frame. Instead I want to move the undecorated frame.
2. Solution
Use the JInternalFrame only as a renderer. Like a ListCellRender.
Since all solutions require a lot of work I want to ask you first if there is a better solution. E.g. a library or maybe it is already possible with standard Java and I missed something.
EDIT
I tried to use setDefaultLookAndFeelDecorated but it doesn't work with nimbus and not with motif.

Related

paintComponent not called when adding custom JComponent

Why is paintComponent(Graphics) not called when adding a custom JComponent?
public class Test {
public static void main(String[] args) {
JFrame frame = new JFrame("Paint Component Example");
frame.setPreferredSize(new Dimension(750, 750));
frame.setLocationByPlatform(true);
JPanel panel = new JPanel();
panel.add(new CustomComponent());
frame.add(panel, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}
public class CustomComponent extends JComponent {
public CustomComponent() {
super();
}
#Override
protected void paintComponent(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(10, 10, 10, 10);
}
}
I know there is no reason for creating a custom component in this instance of but it is an extremely simplified version of another issue I can't figure out.
JPanel panel = new JPanel();
panel.add(new CustomComponent());
The default layout manager of a JPanel is a FlowLayout. A FlowLayout will respect the preferred size of any component added to it. By default the preferred size of a JComponent is (0, 0) so there is nothing to paint so the paintComponent() method never gets called.
Override the getPreferredSize() method of your CustomComponent class to return the preferred size of your component.
Also, don't forget to invoke super.paintComponent(...) at the start of the method.
Read the section from the Swing tutorial on Custom Painting for more information and working examples.

Text Box does not immediately pop up in JFrame [duplicate]

I have a simple Swing GUI. (and not only this, all swing GUI I have written). When run it, it doesn't show anything except blank screen, until I resize the main frame, so every components have painted again, and I can show them.
Here is my simple code :
public static void main(String[] args) {
JFrame frame = new JFrame("JScroll Pane Test");
frame.setVisible(true);
frame.setSize(new Dimension(800, 600));
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);
}
So, my question is : how can when I start this class, the frame will appear all components I have added, not until I resize frame.
Thanks :)
Do not add components to JFrame after the JFrame is visible (setVisible(true))
Not really good practice to call setSize() on frame rather call pack() (Causes JFrame to be sized to fit the preferred size and layouts of its subcomponents) and let LayoutManager handle the size.
Use EDT (Event-Dispatch-Thread)
call JFrame#setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) as said by #Gilbert Le Blanc (+1 to him) or else your EDT/Initial thread will remain active even after JFrame has been closed
Like so:
public static void main(String[] args) {
//Create GUI on EDT Thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("JScroll Pane Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);//add components
frame.pack();
frame.setVisible(true);//show (after adding components)
}
});
}
Your simple code is missing a few things.
You have to invoke SwingUtilities to put the Swing components on the event dispatch thread.
You should call the setDefaultCloseOperation on the JFrame.
You have to call the JFrame methods in the correct order. The setSize or pack method is called, then the setVisible method is called last.
public class SimpleFrame implements Runnable {
#Override
public void run() {
JFrame frame = new JFrame("JScroll Pane Test");
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(800, 600));
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleFrame());
}
}

Set all JLabels to opaque using look and feel

I am trying to make all JLabels opaque by default with a custom look and feel. I can set things like foreground (Label.foreground) but how do you set the opaque property?
EDIT#1:
I am creating a custom look & feel that extends MetalLookAndfeel. I override the initComponentDefaults(UIDefaults) method. However, I am open to other ways that involve using a look & feel.
EDIT#2:
I tried Vince Emigh's suggestion but it did not work. It appears that the opaque property on a JLabel has a bug - see make-jlabel-backround-transparent-again.
EDIT#3:Code sample
class Demo {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
UIManager.put("Label.opaque", Boolean.valueOf(true));
UIManager.put("Label.background", new ColorUIResource(Color.RED));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(3);
JLabel yoyo = new JLabel("YOYOYOYO");
yoyo.setOpaque(true);// try once with this commented out and note difference
JPanel panel = new JPanel();
panel.setSize(200, 200);
panel.setBackground(Color.BLUE);
panel.add(yoyo);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
I also tried to set the opaque property of a JPanel - same results. It appears the setting the opaque property using UIManager has no effect on any object. :(
Is there another way to do this?
UIManager.put("Label.opaque", Boolean.valueOf(true));
It's the same as any other property
class Demo {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
UIManager.put("Label.opaque", Boolean.valueOf(true));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(3);
JPanel panel = new JPanel();
panel.setSize(200, 200);
panel.setBackground(Color.BLUE);
panel.add(new JLabel("YOYOYOYO");
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}

Java Swing : why must resize frame, so that can show components have added

I have a simple Swing GUI. (and not only this, all swing GUI I have written). When run it, it doesn't show anything except blank screen, until I resize the main frame, so every components have painted again, and I can show them.
Here is my simple code :
public static void main(String[] args) {
JFrame frame = new JFrame("JScroll Pane Test");
frame.setVisible(true);
frame.setSize(new Dimension(800, 600));
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);
}
So, my question is : how can when I start this class, the frame will appear all components I have added, not until I resize frame.
Thanks :)
Do not add components to JFrame after the JFrame is visible (setVisible(true))
Not really good practice to call setSize() on frame rather call pack() (Causes JFrame to be sized to fit the preferred size and layouts of its subcomponents) and let LayoutManager handle the size.
Use EDT (Event-Dispatch-Thread)
call JFrame#setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) as said by #Gilbert Le Blanc (+1 to him) or else your EDT/Initial thread will remain active even after JFrame has been closed
Like so:
public static void main(String[] args) {
//Create GUI on EDT Thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("JScroll Pane Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);//add components
frame.pack();
frame.setVisible(true);//show (after adding components)
}
});
}
Your simple code is missing a few things.
You have to invoke SwingUtilities to put the Swing components on the event dispatch thread.
You should call the setDefaultCloseOperation on the JFrame.
You have to call the JFrame methods in the correct order. The setSize or pack method is called, then the setVisible method is called last.
public class SimpleFrame implements Runnable {
#Override
public void run() {
JFrame frame = new JFrame("JScroll Pane Test");
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(800, 600));
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleFrame());
}
}

Newbie JLayeredPane issue

I just can't get past square one on JLayeredPanes. (See my original question of yesterday. I have been studying the JLayeredPane tutorial and API. These tutorials are geared somewhat differently to what I am ultimately trying to produce.
Going back to square one, I took Oracle's JFrame Example and modified it to include Layered panes.
Here is the code:
package components;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/* FrameDemo.java requires no other files. */
public class FrameDemo {
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("FrameDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainLayer = new JPanel(new BorderLayout());
mainLayer.setPreferredSize(new Dimension(640, 480));
frame.setContentPane(mainLayer);
frame.getLayeredPane().add(mainLayer, JLayeredPane.DEFAULT_LAYER, 0);
JLabel emptyLabel = new JLabel("LABEL");
emptyLabel.setPreferredSize(new Dimension(320, 240));
mainLayer.add(emptyLabel, BorderLayout.NORTH);
JPanel subLayer = new JPanel(new BorderLayout());
JLabel subLabel = new JLabel("SUBLABEL");
subLabel.setPreferredSize(new Dimension( 200, 100));
subLabel.setBackground(Color.YELLOW);
subLayer.add(subLabel, BorderLayout.SOUTH);
subLayer.setVisible(true);
subLabel.setVisible(true);
frame.getLayeredPane().add(subLayer, JLayeredPane.PALETTE_LAYER, 0);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
Why doesn't it work? IOW, why doesn't the sublabel show up? It's at a higher level than the main layer.
One thought is why am I adding mainLayer to both the Content Pane and the Layered Pane? If I don't do that, nothing shows up. I.e, by commenting out this line, I just get a blank frame.
// frame.setContentPane(mainLayer);
Obviously, I'm not understanding something. But what is it?
I should add that obviously, this simple demo can be done without Layered Panes. But my ultimate goal is to have a layer that can be turned on and off programatically. But I can't even get this simple case to work. If I can get over this hump, I think the rest will be easier.
ADDENDUM:
What I want to acheive is illustrated by the following Code, which is very similar to what TrashGod set up below and it works. There is a JLayeredPane with a constant layer (layered at Integer(0)) and a floating layer layered initially at Integer(-1) but togglable by the F7 and F8 keystrokes between the Integer(-1) layer and the Integer(1) layer, thereby allowing it to float above or below the constant layer.
package components;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/* MyLayeredPaneDemo.java requires no other files. */
public class MyLayeredPaneDemo {
private JFrame frame;
private JLayeredPane mainPanel;
private JPanel constantLayer;
private JPanel floatingLayer;
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private MyLayeredPaneDemo() {}
private void createAndShowGUI() {
//Create and set up the window.
this.frame = new JFrame("MyLayeredPaneDemo");
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame.setPreferredSize(new Dimension(640, 480));
mainPanel = new JLayeredPane();
constantLayer = new JPanel(new BorderLayout(0,0));
floatingLayer = new JPanel(new BorderLayout(0,0));
// constantLayer.setPreferredSize();
constantLayer.setOpaque(true);
constantLayer.setBackground(Color.BLUE);
JLabel constantLabel = new JLabel("MAIN LAYER");
constantLayer.setPreferredSize(new Dimension(640, 480));
constantLayer.add(constantLabel, BorderLayout.CENTER);
JLabel subLabel = new JLabel("SUB LAYER");
floatingLayer.setBackground(Color.YELLOW);
floatingLayer.add(subLabel, BorderLayout.SOUTH);
floatingLayer.setOpaque(true);
floatingLayer.setVisible(true);
floatingLayer.setVisible(true);
subLabel.setBackground(Color.YELLOW);
mainPanel.add(constantLayer, new Integer(0), 0);
constantLayer.setBounds(0,0,640,480);
mainPanel.add(floatingLayer, new Integer(-1), 0);
floatingLayer.setBounds(100, 360, 300, 90 );
frame.add(mainPanel, BorderLayout.CENTER);
//Display the window.
mapKeyToAction(frame.getRootPane(),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
KeyStroke.getKeyStroke(KeyEvent.VK_F7, 0),
"Hide Layer",
new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("F7 pressed");
mainPanel.setLayer(floatingLayer, new Integer(-1));
}
});
mapKeyToAction(frame.getRootPane(),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0),
"Show Layer",
new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("F8 pressed");
mainPanel.setLayer(floatingLayer, new Integer(1));
}
});
frame.pack();
frame.setVisible(true);
frame.getRootPane().setFocusable(true);
boolean ok = frame.getRootPane().requestFocusInWindow();
System.out.println("focus ok: " + ok);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MyLayeredPaneDemo().createAndShowGUI();
}
});
}
private static void mapKeyToAction(JComponent component,
int whichMap, KeyStroke keystroke,String key, Action action) {
component.getInputMap(whichMap).put(keystroke, key);
component.getActionMap().put(key, action);
}
}
However, I am having trouble getting this to work in my real case. The difference between the two is that here, my JLayeredPane is owned by the Frame, whereas in my real application, I want the JLayeredPane to be owned by a JPanel is that some levels down in the containment hierarchy from the Frame, and whose size is set by a GridBagLoyout in its parent, and the size is therefore unknowable at the time its constructor is called, making it difficult to call setBounds() which I need to do on a child of a JLayeredPane.
FURTHER ADDENDUM. I know that the Oracle Tutorials mention a case where Layouts rather than absolute positioning is used with a JLayeredPane. The difference between this case and mine is that in my case the layers occupy the same horizontal space on different layers, whereas in this case, the components on different layrers occupy different horizontal spaces. It's almost as if we need a 3D Layout Manager!
"By default, a layered pane has no layout manager."—How to Use Layered Panes
Addendum: I need to avoid using the Frame's layered pane and instead add a layered pane to the window.
Yes, The Root Pane is an instance of JRootPane, which contains a JLayeredPane. In particular, "The layered pane contains the menu bar and content pane, and enables Z-ordering of other components."
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class FrameDemo {
private static void createAndShowGUI() {
JFrame frame = new JFrame("FrameDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLayeredPane mainLayer = new JLayeredPane();
frame.add(mainLayer, BorderLayout.CENTER);
JLabel label = new JLabel("LABEL", JLabel.CENTER);
label.setBounds(100, 100, 200, 100);
label.setOpaque(true);
label.setBackground(Color.cyan);
mainLayer.add(label, 1);
JPanel subLayer = new JPanel(new BorderLayout());
JLabel subLabel = new JLabel("SUBLABEL", JLabel.CENTER);
subLabel.setBounds(20, 20, 200, 100);
subLabel.setOpaque(true);
subLabel.setBackground(Color.yellow);
subLayer.add(subLabel, BorderLayout.SOUTH);
mainLayer.add(subLabel, 2);
frame.pack();
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}
The solution I came up and thanks to trashgod which I expect is good advice too is to implement ComponentListener and capture the component resize event. At that point you can get the actual bounds of the container and use it to set the actual bounds of the layer JPanels which are always in some fixed relation to the bounds of the component that contains them. It works.
Trashgod's solution would also work I believe but I have not tried it.

Categories

Resources