通过交换 UIDefaults 改变外观 [英] Changing Look And Feel by Swapping UIDefaults

查看:41
本文介绍了通过交换 UIDefaults 改变外观的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个 GUI 构建器,并希望用户能够更改他构建的 GUI 的 LookAndFeel.LookAndFeel 应该只针对编辑器区域内的组件进行更改.应用程序的其余部分应保留在 SystemLookAndFeel 中.

最大的问题是,LookAndFeel 是作为单例实现的,并且在应用程序期间多次更改 LookAndFeel 会导致很多错误.

我开始尝试使用按钮:我尝试将 ButtonUI 设置为 MetalButtonUI,但它们没有正确呈现.于是调试了JButton的默认paintComponent方法,发现ButtonUI还需要UIDefaults,因为是WindowsUIDefaults,所以不完整.

我目前的解决方案是设置 MetalLookAndFeel,保存 UIDefaults,然后将 LookAndFeel 更改为 SystemLookAndFeel 并保存这些 UIDefaults,并且每次我在编辑器中绘制一个按钮时,我都会交换 UIDefaults.

代码如下:

公共类 MainClass{公共静态哈希表 systemUI;公共静态哈希表 metalUI;公共静态无效主(字符串 [] args){EventQueue.invokeLater(new Runnable() {公共无效运行(){尝试 {UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());metalUI = 新哈希表();metalUI.putAll(UIManager.getDefaults());UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());systemUI = new Hashtable();systemUI.putAll(UIManager.getDefaults());} 捕获(异常 e){e.printStackTrace();}/* ...* 生成 JFrame 和其他东西* ...*/}});}}公共类 MyButton 扩展 JButton {公共我的按钮(字符串文本){超级(文本);ui = MetalButtonUI.createUI(this);}@Override public synchronized void paintComponent(Graphics g) {UIManager.getDefaults().putAll(Application.metalUI);super.paintComponent(g);UIManager.getDefaults().putAll(Application.systemUI);}}

正如您在

import java.awt.Component;导入 java.awt.EventQueue;导入 java.awt.GridBagConstraints;导入 java.awt.GridBagLayout;导入 java.awt.Insets;导入 java.awt.event.ActionEvent;导入 java.awt.event.ActionListener;导入 javax.swing.DefaultComboBoxModel;导入 javax.swing.DefaultListCellRenderer;导入 javax.swing.JComboBox;导入 javax.swing.JFrame;导入 javax.swing.JLabel;导入 javax.swing.JList;导入 javax.swing.JPanel;导入 javax.swing.JTextField;导入 javax.swing.SwingUtilities;导入 javax.swing.UIManager;导入 javax.swing.UnsupportedLookAndFeelException;公共类 LookAndFeelSwitcher {公共静态无效主(字符串 [] args){new LookAndFeelSwitcher();}公共 LookAndFeelSwitcher() {EventQueue.invokeLater(new Runnable() {@覆盖公共无效运行(){尝试 {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {ex.printStackTrace();}JFrame frame = new JFrame(测试");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.add(new TestPane());框架.pack();frame.setLocationRelativeTo(null);frame.setVisible(true);}});}公共类 TestPane 扩展 JPanel {公共测试窗格(){setLayout(new GridBagLayout());GridBagConstraints gbc = new GridBagConstraints();gbc.gridx = 0;gbc.gridwidth = GridBagConstraints.REMAINDER;gbc.fill = GridBagConstraints.HORIZONTAL;gbc.insets = new Insets(2, 2, 2, 2);add(new JLabel(我有种不好的预感"), gbc);add(new JTextField(当这件事在你脸上爆炸时,别怪我"), gbc);UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();DefaultComboBoxModel 模型 = 新 DefaultComboBoxModel(lafs);JComboBox cb = new JComboBox(model);cb.setRenderer(new LookAndFeelInfoListCellRenderer());添加(CB,GBC);字符串名称 = UIManager.getLookAndFeel().getName();for (int index = 0; index  list, Object value, int index, boolean isSelected, boolean cellHasFocus) {如果(UIManager.LookAndFeelInfo 的值实例){UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) 值;值 = info.getName();}return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);}}}}

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

解决方案

Disclarimer

Swing's Look And Feel isn't designed to be switched after it's first initalised, it's actually a kind of fluky side effect that it's possible. Some look and feels and some components might not like you doing this and may not behave as they might other wise under normal conditions.

Possible solution

For the love of sanity, DON'T change the UI defaults in the paintComponent method (don't change the state of the UI at all from within any paint method EVER, painting paints the current state only), that's just asking for no end of trouble.

Instead, when required use UIManager.setLookAndFeel(,,,) and SwingUtiltiies#updateComponentTreeUI and pass in the most top level container

For example...

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LookAndFeelSwitcher {

    public static void main(String[] args) {
        new LookAndFeelSwitcher();
    }

    public LookAndFeelSwitcher() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            gbc.insets = new Insets(2, 2, 2, 2);

            add(new JLabel("I have a bad feeling about this"), gbc);
            add(new JTextField("When this blows up in your face, don't blame me"), gbc);

            UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
            DefaultComboBoxModel model = new DefaultComboBoxModel(lafs);
            JComboBox cb = new JComboBox(model);
            cb.setRenderer(new LookAndFeelInfoListCellRenderer());
            add(cb, gbc);

            String name = UIManager.getLookAndFeel().getName();
            for (int index = 0; index < model.getSize(); index++) {
                UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) model.getElementAt(index);
                if (info.getName().equals(name)) {
                    model.setSelectedItem(info);
                    break;
                }
            }

            cb.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) cb.getSelectedItem();
                    String className = info.getClassName();
                    try {
                        UIManager.setLookAndFeel(className);
                        SwingUtilities.updateComponentTreeUI(SwingUtilities.windowForComponent(TestPane.this));
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }

        public class LookAndFeelInfoListCellRenderer extends DefaultListCellRenderer {

            @Override
            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                if (value instanceof UIManager.LookAndFeelInfo) {
                    UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo) value;
                    value = info.getName();
                }
                return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            }

        }

    }

}

这篇关于通过交换 UIDefaults 改变外观的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆