java - 通过交换 UIDefaults 改变外观

标签 java swing look-and-feel uimanager uidefaults

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

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

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

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

代码如下:

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);
    }
}

如你所见here结果很好。左边是 MetalLaF 的外观,右边是它在我的应用程序中的呈现方式。渐变绘制正确,但边框和字体不正确。

所以我需要知道为什么不是 LaF 的所有元素都被应用到 Button 以及如何解决这个问题。

-

编辑: 我发现了一个丑陋的解决方案。 LookAndFeel 必须在 Button 创建之前更改,因为 Graphics 对象将在 Constructor 中创建。调用 super 构造函数后,您可以将 LookAndFeel 改回来。

接下来,您需要在绘制/重绘组件之前更改 LookAndFeel。我让它工作的唯一一点是在调用 super 之前在 paintComponent 中。您可以在调用 super 后将其改回。

代码:

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) {
        }
    }
}

编辑 2: 除非绝对必要,否则不要使用它!

这是非常不稳定的。经过大量测试后,我发现当我只是交换 UIDefaults 而不是整个 LookAndFeel 时,错误更少,但我不建议做任何这些。

编辑 3: 我找到的最佳解决方案是使用 JavaFX 作为 GUI。我将一个 swing 节点插入到编辑器区域,现在可以根据需要随意修改 swing 组件的外观,而不会产生任何明显的副作用。

咆哮: 如果您想修改应用程序的样式,则始终可以选择 JavaFX。 CSS 使它尽可能简单,没有任何副作用!


非常感谢

强尼

最佳答案

免责声明

Swing 的外观并非旨在在首次初始化后进行切换,它实际上是一种可能发生的偶然副作用。某些外观和感觉以及某些组件可能不喜欢您这样做,并且可能不会像正常情况下那样表现。

可能的解决方案

为了理智,不要在 paintComponent 方法中更改 UI 默认值(永远不要在任何 paint 方法中更改 UI 的状态,绘制绘制当前仅状态),这只会带来无穷无尽的麻烦。

相反,在需要时使用 UIManager.setLookAndFeel(,,,)SwingUtiltiies#updateComponentTreeUI 并传入最顶层容器

例如……

I have a bad feeling about this

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);
            }

        }

    }

}

关于java - 通过交换 UIDefaults 改变外观,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33617713/

相关文章:

java - 使用 JEditorPane 打开文本文件

java - 在 .form 项目中使用 .java 类中的方法

styles - 如何在 JavaFX 8 中保留 JavaFX 7 皮肤?

java - 如何在 Mac OS 上使用 Java 创建一个类似 Combobox 的简单外观

java - 如何使用 Apache Flink 删除 Cassandra 中的行?

java - Student.Graduate 类型的 arrayList 类型不正确

Java:更新TableModel数据的正确方法?

java - 在 JFrame 中使用 Darcula LAF

java - 如何获取音频文件并解码为 Base64 字符串?

java - Android 中的 JSON 解码