我想要实现的是在框架右侧显示一个弹出窗口:
此弹出窗口的目标是模仿
ComboBox
下拉菜单,但以更灵活的方式。例如。它可以扩展额外的内容。
另外,我试图使弹出窗口保留在主 JFrame
的范围内,因为触发器组件位于 JFrame
的右侧,所以弹出窗口必须在其左侧展开。
我有一段代码的行为符合预期,即:弹出窗口在左侧展开(显示以前不可见的
JPanel
)或缩小(附加面板设置为不可见)。
但是,当它重新定位(扩大或缩小)时,我注意到弹出窗口闪烁。
在屏幕录制中逐帧显示弹出窗口闪烁时会发生什么:
下面的屏幕截图来自在 QuickTime 中打开的屏幕录制,屏幕截图被裁剪以集中在弹出窗口上,显然父 JFrame 没有调整大小。
该功能是以这种方式实现的,使用由
locationAtBottomRight
计算的坐标在按钮的右下角创建弹出窗口:
var popupLocation = locationAtBottomRight(dropdownButton, popupContent);
popup.ref = PopupFactory.getSharedInstance().getPopup(
dropdownButton,
popupContent,
popupLocation.x,
popupLocation.y
);
popup.ref.show();
当单击弹出窗口中的“更多”按钮时,它会设置可见面板并更新父面板大小
moreButton.addActionListener(ae -> {
secondary.setVisible(!secondary.isVisible());
moreButton.setText(secondary.isVisible() ? "Less..." : "More...");
var newSize = new Dimension(main.getPreferredSize());
if (secondary.isVisible()) {
newSize.width += secondary.getPreferredSize().width;
}
popupContent.setSize(newSize);
});
发生这种情况时,
popupContent
上的组件侦听器将被调用并更新弹出窗口的大小和位置。
popupContent.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
resizeAndRelocatePopup(dropdownButton, popupContent);
System.out.println("componentResized: " +popupContent.getPreferredSize());
}
});
设置弹出窗口的位置
Window::setLocation
和大小 Window::setSize
在弹出窗口的 Window
上调用。
private static void resizeAndRelocatePopup(JComponent owner, JComponent popupContent) {
var popupWindow = SwingUtilities.getWindowAncestor(popupContent);
var newPopupLocation = locationAtBottomRight(owner, popupContent);
popupWindow.setLocation(newPopupLocation);
popupWindow.setSize(popupContent.getPreferredSize());
// failed attempt to avoid the popup getting painted too early
popupWindow.revalidate();
}
我相信这是导致闪烁的最后一段代码。我尝试过使用常用技巧,例如
revalidate
/ repaint
,但这没有帮助。
这是重现该问题的完整代码。示例在 JDK 21 上运行,但也应该在 JDK 11 上运行。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PopupStuffJ {
/**
* Resize and relocate the popup window, according to the preferred size of the popup content.
*/
private static void resizeAndRelocatePopup(JComponent owner, JComponent popupContent) {
var popupWindow = SwingUtilities.getWindowAncestor(popupContent);
var newPopupLocation = locationAtBottomRight(owner, popupContent);
popupWindow.setLocation(newPopupLocation);
popupWindow.setSize(popupContent.getPreferredSize());
// failed attempt to avoid the popup getting painted too early
popupWindow.revalidate();
}
/**
* Compute location at bottom right of owner component, using the preferred width of the popup content.
*/
private static Point locationAtBottomRight(JComponent owner, JComponent popupContent) {
var popupLocation = owner.getLocationOnScreen();
popupLocation.x = popupLocation.x + owner.getWidth() - popupContent.getPreferredSize().width;
popupLocation.y += owner.getHeight();
return popupLocation;
}
private static JButton buttonWithDropDown() {
var popup = new Object() {
Popup ref = null;
};
// This button is merely here to trigger the popup
// In actual code, this triggered by a third party component
var dropdownButton = new JButton("Dropdown Menu");
dropdownButton.addActionListener(ae -> {
if (popup.ref != null) {
popup.ref.hide();
popup.ref = null;
return;
}
// Computes the location of the popup according to the button and the popup content
var popupContent = popupContent();
// This code that relocates and resizes the popup
popupContent.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
resizeAndRelocatePopup(dropdownButton, popupContent);
System.out.println("componentResized: " +popupContent.getPreferredSize());
}
});
// When first displayed show popup at the right location
var popupLocation = locationAtBottomRight(dropdownButton, popupContent);
popup.ref = PopupFactory.getSharedInstance().getPopup(
dropdownButton,
popupContent,
popupLocation.x,
popupLocation.y
);
popup.ref.show();
});
return dropdownButton;
}
private static JComponent popupContent() {
var secondary = new JPanel(new BorderLayout());
secondary.setPreferredSize(new Dimension(100, 100));
secondary.setVisible(false);
secondary.setOpaque(false);
secondary.add(new JTextArea("Expanded"), BorderLayout.CENTER);
var main = new JPanel();
main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
main.setPreferredSize(new Dimension(100, 100));
var textArea = new JTextArea("Click more to expand on the left");
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
main.add(textArea);
var moreButton = new JButton("More...");
main.add(moreButton);
main.setOpaque(false);
var popupContent = new JPanel();
popupContent.setLayout(new BoxLayout(popupContent, BoxLayout.X_AXIS));
popupContent.add(secondary);
popupContent.add(main);
// update the size of the popupContent panel,
// to either expand or shrink the popup
// when button More... is clicked
moreButton.addActionListener(ae -> {
secondary.setVisible(!secondary.isVisible());
moreButton.setText(secondary.isVisible() ? "Less..." : "More...");
var newSize = new Dimension(main.getPreferredSize());
if (secondary.isVisible()) {
newSize.width += secondary.getPreferredSize().width;
}
popupContent.setSize(newSize);
});
return popupContent;
}
private static JComponent frameContent() {
var toolbar = new JPanel(new FlowLayout(FlowLayout.RIGHT));
toolbar.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
toolbar.add(new JLabel("xxxx"));
toolbar.add(buttonWithDropDown());
toolbar.setOpaque(false);
var frameContent = new JPanel(new BorderLayout());
frameContent.add(toolbar, BorderLayout.NORTH);
frameContent.setOpaque(false);
return frameContent;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
var jFrame = new JFrame();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
var contentPane = jFrame.getContentPane();
contentPane.setBackground(new Color(0xFFC499));
contentPane.add(frameContent());
jFrame.setSize(new Dimension(600, 400));
jFrame.setVisible(true);
});
}
}
但是接触主框架的代码在哪里?
我不知道。
但是,当我运行您的代码并单击“下拉菜单”按钮时,框架的大小会调整得更小。这是我看到的:
当我调整框架大小时,我会看到以下内容:
为什么镜框变小了?我不知道。看来您有一个 setSize() 方法,该方法适用于框架而不是弹出窗口。在解决这个初始问题之前,您无法解决调整大小/重塑问题。请注意,在我提供的 3 个示例中,框架的大小没有改变。这就是为什么我建议您从工作代码开始。在弹出窗口最初正确显示之前,我什至无法开始帮助处理“更多/更少”逻辑。
我已查看链接的答案
答案展示了 3 种不同的显示弹出窗口的方法。其中之一使用 JWindow。您还可以使用未修饰的 JDialog。
但是这如何帮助我重塑已经显示的弹出窗口呢?
上述方法将允许您像任何其他 JFrame 一样创建弹出组件。然后您可以根据需要调整窗口的大小/形状。
对我来说,你所需要的只是 ActionListeners。在“下拉菜单”按钮上,ActionListener 将创建并显示弹出窗口。在弹出窗口上,您可能有两个按钮“更多...”和“更少...”。根据窗口的当前状态,一次只能看到一个。然后为每个按钮添加一个 ActionListener。 “更多...”侦听器将通过向窗口添加更多内容来调整窗口大小,然后根据新添加内容的大小重置位置。 “Les...”监听器将删除内容并重置大小/位置。
我还在按钮上看到“Mo...”,这表明有问题。您的代码有太多 setPreferredSize(...) 语句。应该不需要它们中的任何一个。只需创建组件并将它们添加到窗口即可。每个组件都会确定自己的首选尺寸。