Изменение внешнего вида путем замены UIDefaults
Я пишу GUI Builder и хочу, чтобы пользователь мог изменять LookAndFeel GUI, который он создает. LookAndFeel следует изменять только для компонентов внутри области редактора. Остальная часть приложения должна оставаться с SystemLookAndFeel.
Большая проблема в том, что LookAndFeel реализован в виде Singleton, и многократное изменение LookAndFeel во время работы приложения приводит к множеству ошибок.
Я начал экспериментировать с кнопками: я пытался установить ButtonUI в MetalButtonUI, но они не отображались должным образом. Поэтому я отладил метод по умолчанию paintComponent в JButton и увидел, что ButtonUI все еще нужны UIDefaults, которые не были завершены, поскольку они были WindowsUIDefaults.
Мое текущее решение состоит в том, чтобы установить MetalLookAndFeel, сохранить UIDefaults, затем изменить LookAndFeel на SystemLookAndFeel и сохранить эти UIDefaults так же, и каждый раз, когда я рисую кнопку внутри редактора, я меняю 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);
}
}
Как видите, результат довольно хороший. Слева находится MetalLaF, как он должен выглядеть, а справа - как он отображается в моем приложении. Градиент нарисован правильно, а Граница и Шрифт - нет.
Поэтому мне нужно знать, почему не все элементы LaF применяются к кнопке, и как это исправить.
-
Изменить: я нашел уродливое решение. LookAndFeel должен быть изменен перед созданием кнопки, потому что графический объект будет создан в конструкторе. После вызова супер-конструктора вы можете изменить LookAndFeel обратно.
Затем вам нужно изменить LookAndFeel, прежде чем компонент будет окрашен / перекрашен. Единственное, что я получил, это работало, было в 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 в качестве графического интерфейса. Я вставил узел свинга в область редактора и теперь могу изменять внешний вид компонентов свинга так часто, как я хочу, без каких-либо заметных побочных эффектов.
Rant: Если вы всегда можете выбрать JavaFX, если хотите изменить стиль своего приложения. CSS делает это максимально простым без каких-либо побочных эффектов!
Большое спасибо
Jhonny
1 ответ
Disclarimer
Swing's Look And Feel не предназначен для переключения после первой инициализации, на самом деле это своего рода случайный побочный эффект, который возможен. Некоторые выглядят и чувствуют, а некоторым компонентам может не нравиться, что вы делаете это, и они могут вести себя не так, как в других условиях в нормальных условиях.
Возможное решение
Ради здравомыслия, НЕ меняйте настройки интерфейса по умолчанию в методе paintComponent (не изменяйте состояние интерфейса вообще из любого paint
метод КОГДА-ЛИБО рисует только текущее состояние), вот только прошу без конца хлопот.
Вместо этого, когда требуется использовать UIManager.setLookAndFeel(,,,)
а также SwingUtiltiies#updateComponentTreeUI
и пройти в самый верхний уровень контейнера
Например...
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);
}
}
}
}