I am writing a GUI Builder and want the user to be able to change the LookAndFeel of the GUI he builds. The LookAndFeel should only be changed for the Components inside the editor area. The rest of the Application should remain with the SystemLookAndFeel.
The great problem is, that the LookAndFeel is implemented as a Singleton and changing the LookAndFeel multiple times during the Application causes a lot of bugs.
I started experimenting with Buttons: I tried setting the ButtonUI to MetalButtonUI, but they didn't render properly. So I debugged the default paintComponent method of JButton and saw that the ButtonUI still needed the UIDefaults, which were not complete since they were the WindowsUIDefaults.
My current solution is to set the MetalLookAndFeel, save the UIDefaults, then change the LookAndFeel to SystemLookAndFeel and save those UIDefaults aswell and everytime I draw a Button inside the editor I swap the UIDefaults.
Here is the Code:
public class MainClass{
public static Hashtable systemUI;
public static Hashtable metalUI;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
metalUI = new Hashtable();
metalUI.putAll(UIManager.getDefaults());
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
systemUI = new Hashtable();
systemUI.putAll(UIManager.getDefaults());
} catch (Exception e) {
e.printStackTrace();
}
/* ...
* Generate JFrame and other stuff
* ...
*/
}
});
}
}
public class MyButton extends JButton {
public MyButton(String text) {
super(text);
ui = MetalButtonUI.createUI(this);
}
@Override public synchronized void paintComponent(Graphics g) {
UIManager.getDefaults().putAll(Application.metalUI);
super.paintComponent(g);
UIManager.getDefaults().putAll(Application.systemUI);
}
}
As you can see here the result is pretty good. On the left is the MetalLaF as it should look and on the right, how it gets rendered in my application. The gradient is painted correctly, but the Border and the Font aren't.
So I need to know why not all elements of the LaF are beeing applied to the Button and how to fix that.
-
Edit: I found an ugly solution. The LookAndFeel has to be changed before Button creation, because the Graphics object will be created in the Constructor. After the super constructor was called you can change the LookAndFeel back.
Next you need to change the LookAndFeel before the Component is painted/repainted. The only point I got it working was in paintComponent before super is called. You can change it back after super is called.
Code:
import javax.swing.*;
import javax.swing.plaf.metal.MetalButtonUI;
import java.awt.*;
public class MainClass {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(f.getExtendedState() | JFrame.EXIT_ON_CLOSE);
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
}
f.setLayout(new FlowLayout());
f.add(new MyButton("MetalButton"));
f.add(new JButton("SystemButton"));
f.pack();
f.setVisible(true);
}
});
}
}
class MyButton extends JButton {
public MyButton(String text) {
super(text);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
ui = MetalButtonUI.createUI(this);
}
@Override public synchronized void paintComponent(Graphics g) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
super.paintComponent(g);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
}
}
Edit 2: Do not use this unless absolutely necessery!
This is extremely unstable. After a lot of testing I found it less buggy when I just swapped the UIDefaults instead of the whole LookAndFeel, but I do not recommend doing any of those.
Edit 3: The best solution I found was using JavaFX as a GUI. I inserted a swing node into the Editor area and now can modify the Look and Feel of the swing components as often as I want without any noticeable side effects.
Rant: If you can always choose JavaFX if you want to modify the style of your application. CSS makes it as easy as possible without any side effects ever!
Much Thanks
Jhonny