根据 awheel 的建议,我编写了以下使用 Swing 的玻璃窗格功能的示例。这种方法有点混乱,但是当您在 Swing 中尝试一些中等高级的东西时,这种情况并不少见。
这个想法是在单击按钮时显示一个透明的覆盖面板(覆盖整个窗口内容的玻璃面板),并在用户单击窗口中的任意位置或按下某个键时将其丢弃。
在这个玻璃窗格的顶部,我显示了另一个 JPanel(“弹出窗口”)并尝试将其放置在触发其可见性的按钮上方。
这种方法有一个您原来的基于对话框的解决方案没有的限制:在玻璃窗格顶部绘制的任何内容都必须适合框架的内容区域(毕竟,它不是窗口)。这就是为什么我在下面的代码中进行一些调整以确保 popup< 的坐标在内容窗格的范围内(否则 JLabel 将简单地在框架的边缘被裁剪)。
它还有一个限制,即被玻璃窗格捕获的鼠标按下不会委托给任何底层组件。因此,如果您在玻璃窗格可见时单击按钮,玻璃窗格将消失但也会消耗单击,并且您认为单击的按钮不会做出反应。如果愿意,可以绕过这个,但它会变得更加混乱,我想让我的例子保持相对简单。:-)
导入 java.awt.Color;
导入 java.awt.Container;
导入 java.awt.FlowLayout;
导入 java.awt.KeyEventDispatcher;
导入 java.awt.KeyboardFocusManager;
导入 java.awt.Point;
导入 java.awt.Window;
导入 java.awt.event.ActionEvent;
导入 java.awt.event.ActionListener;
导入 java.awt.event.KeyEvent;
导入 java.awt.event.MouseAdapter;
导入 java.awt.event.MouseEvent;
导入 javax.swing.JButton;
导入 javax.swing.JDialog;
导入 javax.swing.JFrame;
导入 javax.swing.JLabel;
导入 javax.swing.JPanel;
导入 javax.swing.JRootPane;
导入 javax.swing.SwingUtilities;
导入 javax.swing.border.BevelBorder;
导入 javax.swing.border.CompoundBorder;
导入 javax.swing.border.EmptyBorder;
公共类 GlassPaneTest 扩展 JFrame {
公共静态类 PropertiesButton 扩展 JButton {
/** 当前显示的玻璃窗格。
* 如果不显示任何内容,则应为 null。*/
私人JPanel theGlassPane;
/** 连接窗口的根窗格。用于固定玻璃板。*/
私有最终 JRootPane rootPane;
/** 连接窗口的内容窗格。用于坐标计算。*/
私有最终容器内容窗格;
/* 一个“key hook”,允许我们在玻璃窗格可见时拦截任何按键,
* 所以我们可以隐藏玻璃窗格。*/
私有最终 KeyEventDispatcher keyHook = new KeyEventDispatcher() {
公共布尔dispatchKeyEvent(KeyEvent e){
if (theGlassPane == null || e.getID() != KeyEvent.KEY_PRESSED) {
返回假;
}
setGlassPaneVisible(false);
返回真;
}
};
公共属性按钮(窗口父窗口){
if (!(parentWindow instanceof JFrame || parentWindow instanceof JDialog)) {
throw new IllegalArgumentException("只接受 JFrame 或 JDialog 实例");
}
if (parentWindow instanceof JDialog) {
rootPane = ((JDialog) parentWindow).getRootPane();
contentPane = ((JDialog) parentWindow).getContentPane();
} 别的 {
rootPane = ((JFrame) parentWindow).getRootPane();
contentPane = ((JFrame) parentWindow).getContentPane();
}
addActionListener(new ActionListener() {
公共无效actionPerformed(ActionEvent e){
setGlassPaneVisible(theGlassPane == null);
}
});
}
私人JPanel createGlassPane(){
// 将玻璃窗格创建为透明的、无布局的面板
//(允许绝对定位),覆盖整个内容窗格。
// 让它在任何鼠标按下时消失。
JPanel gp = new JPanel();
gp = 新的 JPanel();
gp.setOpaque(false);
gp.setLayout(null);
gp.setBounds(contentPane.getBounds());
gp.addMouseListener(new MouseAdapter() {
@覆盖
公共无效鼠标按下(鼠标事件e){
setGlassPaneVisible(false);
}
});
// 创建“popup” - 透明组件上显示的组件
// 覆盖。
JPanel 弹出窗口 = 新 JPanel();
popup.setBorder(新的 CompoundBorder(
新的 BevelBorder(BevelBorder.RAISED),
新的 EmptyBorder(5, 5, 5, 5)));
popup.setBackground(Color.YELLOW);
popup.add(new JLabel("Some info for \"" + getText() + "\"."));
// 需要,因为玻璃窗格没有布局管理器。
popup.setSize(popup.getPreferredSize());
// 将弹出窗口定位在触发按钮的正上方
// 它的可见性。
点 buttonLocationInContentPane = SwingUtilities.convertPoint(this, 0, 0, contentPane);
int x = buttonLocationInContentPane.x;
int horizOverlap = x + popup.getWidth() - contentPane.getWidth();
if (horizOverlap > 0) {
x -= 水平重叠;
}
int y = buttonLocationInContentPane.y - popup.getHeight();
如果(y < 0){
y = 0;
}
popup.setLocation(x, y);
gp.add(弹出);
返回 gp;
}
私人无效 setGlassPaneVisible(布尔可见){
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
如果(可见){
theGlassPane = 创建GlassPane();
rootPane.setGlassPane(theGlassPane);
theGlassPane.setVisible(true);
kfm.addKeyEventDispatcher(keyHook);
} 别的 {
theGlassPane.setVisible(false);
kfm.removeKeyEventDispatcher(keyHook);
theGlassPane = null;
}
}
}
// 一个简单的测试程序
公共 GlassPaneTest() {
setTitle("玻璃窗格示例");
setLayout(new FlowLayout(FlowLayout.CENTER));
for (int i = 1; i <= 10; ++i) {
PropertiesButton pb = new PropertiesButton(this);
pb.setText("属性按钮" + i);
添加(铅);
}
设置大小(400、300);
}
公共静态无效主要(字符串[]参数){
SwingUtilities.invokeLater(new Runnable() {
公共无效运行(){
JFrame f = new GlassPaneTest();
f.setDefaultCloseOperation(EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}