JRootPane 默认按钮在调整大小时未使用 Mac OS X LAF 正确呈现
JRootPane Default Button not Rendered Correctly with Mac OS X LAF when Resized
目标: 使用 mac OS X LAF 创建默认按钮。为此,我在 JFrame
s JRootPane
上使用了 setDefaultButton(JButton)
方法。
问题: 当我将 JFrame
的大小调整为大于默认按钮的首选大小时,该按钮看起来像一个普通的 JButton
。如果 JFrame
返回到其原始大小附近的某个位置,该按钮将恢复为默认按钮。 不会发生在任何其他默认 LAF 中。
示例 GIF:
金属: Mac OS X:
问题: 我怎样才能拥有一个带有 Mac OS X LAF 的默认按钮,它可以在不改变外观的情况下调整大小?
代码: A JOptionPane
将要求您选择一个已安装的 LAF。选择一个,将显示带有该 LAF 的默认按钮。
import java.awt.EventQueue;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
public class ResizingDeafultButtonMCVE {
public static void main(String[] args) {
LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
ArrayList<String> lafNames = new ArrayList<String>();
for(int i = 0; i < lafs.length; i++) {
lafNames.add(lafs[i].getName());
}
Object option = JOptionPane.showInputDialog(
null, "Choose a LAF", "", JOptionPane.DEFAULT_OPTION,
null, lafNames.toArray(new String[0]), 0);
if(option == null) {
System.exit(0);
}
try {
UIManager.setLookAndFeel(UIManager.getInstalledLookAndFeels()
[lafNames.indexOf(option)].getClassName());
} catch (UnsupportedLookAndFeelException | ClassNotFoundException
| InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
EventQueue.invokeLater(() -> {
final JFrame frame = new JFrame((String) option);
JButton defaultButton = new JButton("Default Button");
frame.getContentPane().add(defaultButton);
frame.getRootPane().setDefaultButton(defaultButton);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
了解以下示例为何有效的须知事项:
- 一个
AquaButtonBorder
绘制了一个由AquaButtonUI
控制的按钮的按钮部分
AquaBorder
的边框绘制代码由 AquaPainter
完成。具体来说,AquaSingleImagePainter
通过使用具有给定 JRSUIState
. 的 JRSUIControl
将图像绘制到 Graphics2D
上下文中
- 提供给
AquaSingleImagePainter
的 JRSUIState
是 JRSUIControl
用来确定原生绘制内容的
- A
JRSUIState
通过 JRSUIState#set(Property)
应用 Property
s 修改
- OS X 上的默认按钮必须是 push button(
Widget.BUTTON_PUSH
,Property
)
- 按钮样式 属性 应用于
JRSUIState
用于在 AquaButtonBorder.Dynamic#doButtonPaint(AbstractButton, ButtonModel, Graphics, int, int, int, int)
中绘制图像
- 按钮的样式
Property
(Widget
) 由
AquaButtonBorder.Dynamic#getStyleForSize(AbstractButton, Size, int, int)
- 按钮有最大尺寸
- 上述方法 returns
Widget.BUTTON_BEVEL_ROUND
用于按钮的最大高度以上的按钮(实现为 SizeConstants.fNormalButtonHeight + 3 = 32
)
因此,要使默认按钮无论大小如何始终显示为默认按钮,getStyleForSize(...)
对于默认按钮必须始终 return Widget.BUTTON_PUSH
。
解法:
子类 AquaButtonBorder.Dynamic
并将 getStyleForSize(...)
覆盖为 return Widget.BUTTON_PUSH
如果给定的 AbstractButton
是默认按钮(按钮的JRootPane
的默认按钮就是自己),如下图:
public class AlwaysDefaultAquaButtonBorder extends AquaButtonBorder.Dynamic {
@Override
protected JRSUIConstants.Widget getStyleForSize(final AbstractButton b,
final JRSUIConstants.Size size,
final int width,
final int height) {
if(b != null && b.getRootPane() != null
&& Objects.equals(b, b.getRootPane().getDefaultButton())) {
return JRSUIConstants.Widget.BUTTON_PUSH;
}
return super.getStyleForSize(b, size, width, height);
}
}
将按钮的边框设置为此自定义边框的新实例在将其添加到其父组件后,否则边框将重置为UI-AquaButtonUI
的默认值,因为在将其添加到其父级时发生层次结构更改
注意:生成的默认按钮边框不会填充按钮的边界,因为按钮具有最大高度。随着按钮高度的增加,绘制边框的高度将保持不变并垂直居中。
完整示例:
import apple.laf.JRSUIConstants;
import com.apple.laf.AquaButtonBorder;
import java.awt.EventQueue;
import java.util.Objects;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JFrame;
public class AquaAlwaysDefaultButtonExample {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
final JFrame frame = new JFrame("Aqua Always Default Button Example");
final JButton defaultButton = new JButton("Default Button");
frame.getContentPane().add(defaultButton);
defaultButton.setBorder(new AlwaysDefaultAquaButtonBorder());
frame.getRootPane().setDefaultButton(defaultButton);
frame.pack();
frame.setLocationByPlatform(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
public static class AlwaysDefaultAquaButtonBorder extends AquaButtonBorder.Dynamic {
@Override
protected JRSUIConstants.Widget getStyleForSize(final AbstractButton b,
final JRSUIConstants.Size size,
final int width,
final int height) {
if(b != null && b.getRootPane() != null
&& Objects.equals(b, b.getRootPane().getDefaultButton())) {
return JRSUIConstants.Widget.BUTTON_PUSH;
}
return super.getStyleForSize(b, size, width, height);
}
}
}
目标: 使用 mac OS X LAF 创建默认按钮。为此,我在 JFrame
s JRootPane
上使用了 setDefaultButton(JButton)
方法。
问题: 当我将 JFrame
的大小调整为大于默认按钮的首选大小时,该按钮看起来像一个普通的 JButton
。如果 JFrame
返回到其原始大小附近的某个位置,该按钮将恢复为默认按钮。 不会发生在任何其他默认 LAF 中。
示例 GIF:
金属: Mac OS X:
问题: 我怎样才能拥有一个带有 Mac OS X LAF 的默认按钮,它可以在不改变外观的情况下调整大小?
代码: A JOptionPane
将要求您选择一个已安装的 LAF。选择一个,将显示带有该 LAF 的默认按钮。
import java.awt.EventQueue;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
public class ResizingDeafultButtonMCVE {
public static void main(String[] args) {
LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
ArrayList<String> lafNames = new ArrayList<String>();
for(int i = 0; i < lafs.length; i++) {
lafNames.add(lafs[i].getName());
}
Object option = JOptionPane.showInputDialog(
null, "Choose a LAF", "", JOptionPane.DEFAULT_OPTION,
null, lafNames.toArray(new String[0]), 0);
if(option == null) {
System.exit(0);
}
try {
UIManager.setLookAndFeel(UIManager.getInstalledLookAndFeels()
[lafNames.indexOf(option)].getClassName());
} catch (UnsupportedLookAndFeelException | ClassNotFoundException
| InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
EventQueue.invokeLater(() -> {
final JFrame frame = new JFrame((String) option);
JButton defaultButton = new JButton("Default Button");
frame.getContentPane().add(defaultButton);
frame.getRootPane().setDefaultButton(defaultButton);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
了解以下示例为何有效的须知事项:
- 一个
AquaButtonBorder
绘制了一个由AquaButtonUI
控制的按钮的按钮部分
AquaBorder
的边框绘制代码由AquaPainter
完成。具体来说,AquaSingleImagePainter
通过使用具有给定JRSUIState
. 的 - 提供给
AquaSingleImagePainter
的JRSUIState
是JRSUIControl
用来确定原生绘制内容的 - A
JRSUIState
通过JRSUIState#set(Property)
应用 - OS X 上的默认按钮必须是 push button(
Widget.BUTTON_PUSH
,Property
) - 按钮样式 属性 应用于
JRSUIState
用于在AquaButtonBorder.Dynamic#doButtonPaint(AbstractButton, ButtonModel, Graphics, int, int, int, int)
中绘制图像
- 按钮的样式
Property
(Widget
) 由AquaButtonBorder.Dynamic#getStyleForSize(AbstractButton, Size, int, int)
- 按钮有最大尺寸
- 上述方法 returns
Widget.BUTTON_BEVEL_ROUND
用于按钮的最大高度以上的按钮(实现为SizeConstants.fNormalButtonHeight + 3 = 32
)
JRSUIControl
将图像绘制到 Graphics2D
上下文中
Property
s 修改
因此,要使默认按钮无论大小如何始终显示为默认按钮,getStyleForSize(...)
对于默认按钮必须始终 return Widget.BUTTON_PUSH
。
解法:
子类
AquaButtonBorder.Dynamic
并将getStyleForSize(...)
覆盖为 returnWidget.BUTTON_PUSH
如果给定的AbstractButton
是默认按钮(按钮的JRootPane
的默认按钮就是自己),如下图:public class AlwaysDefaultAquaButtonBorder extends AquaButtonBorder.Dynamic { @Override protected JRSUIConstants.Widget getStyleForSize(final AbstractButton b, final JRSUIConstants.Size size, final int width, final int height) { if(b != null && b.getRootPane() != null && Objects.equals(b, b.getRootPane().getDefaultButton())) { return JRSUIConstants.Widget.BUTTON_PUSH; } return super.getStyleForSize(b, size, width, height); } }
将按钮的边框设置为此自定义边框的新实例在将其添加到其父组件后,否则边框将重置为UI-
AquaButtonUI
的默认值,因为在将其添加到其父级时发生层次结构更改
注意:生成的默认按钮边框不会填充按钮的边界,因为按钮具有最大高度。随着按钮高度的增加,绘制边框的高度将保持不变并垂直居中。
完整示例:
import apple.laf.JRSUIConstants;
import com.apple.laf.AquaButtonBorder;
import java.awt.EventQueue;
import java.util.Objects;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JFrame;
public class AquaAlwaysDefaultButtonExample {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
final JFrame frame = new JFrame("Aqua Always Default Button Example");
final JButton defaultButton = new JButton("Default Button");
frame.getContentPane().add(defaultButton);
defaultButton.setBorder(new AlwaysDefaultAquaButtonBorder());
frame.getRootPane().setDefaultButton(defaultButton);
frame.pack();
frame.setLocationByPlatform(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
public static class AlwaysDefaultAquaButtonBorder extends AquaButtonBorder.Dynamic {
@Override
protected JRSUIConstants.Widget getStyleForSize(final AbstractButton b,
final JRSUIConstants.Size size,
final int width,
final int height) {
if(b != null && b.getRootPane() != null
&& Objects.equals(b, b.getRootPane().getDefaultButton())) {
return JRSUIConstants.Widget.BUTTON_PUSH;
}
return super.getStyleForSize(b, size, width, height);
}
}
}