周六早上的有趣问题:-)
没有完整的解决方案,只有几条评论和替代方法的概述:
-
依赖鼠标/Motion/listener是不完整的,因为它不能处理键盘控制的移动
-
每个internalframe组件救援侦听器:如果
不
处于大纲模式
-
在大纲模式下,重新验证无论如何都无法工作,因为它依赖于
真实的
拖动过程中不变的帧位置
所以真正的问题是大纲模式,需要
-
跟踪拖动帧的中间边界
-
让桌面的prefSize计算将这些中间边界考虑在内
-
轮廓图(对我来说,出乎意料,见下文[*])
负责移动框架的合作者是DesktopManager.dragFrame:如果不处于轮廓模式,它的默认实现会重置框架边界,或者如果处于轮廓模式则会跟踪中间位置并绘制轮廓矩形。
显而易见的想法是一个自定义桌面管理器,它可以覆盖dragFrame:
-
让super做自己的事情
-
在大纲模式下,获取帧的中间位置,并将其存储在帧本身的某个位置,例如作为clientProperty
现在,某人,例如PropertyChangeListener,可以监听中间位置的更改并触发重新验证。桌面窗格的prefSize计算可以考虑除了实际边界之外的中间边界,比如
public static class MyDesktopManager extends DefaultDesktopManager {
private Point currentLoc;
@Override
public void dragFrame(JComponent f, int newX, int newY) {
// let super handle outline drawing
super.dragFrame(f, newX, newY);
if (isOutline(f)) {
// take over the drawing
currentLoc = new Point(newX, newY);
Rectangle bounds = new Rectangle(currentLoc, f.getSize());
f.putClientProperty("outlineBounds", bounds);
} else {
// call super only if not outline
// handle outline drawing ourselves
// super.dragFrame(f, newX, newY);
}
}
@Override
public void beginDraggingFrame(JComponent f) {
super.beginDraggingFrame(f);
if (isOutline(f)) {
currentLoc = f.getLocation();
RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
// do the painting in the glassPane
// r.getGlassPane().setVisible(true);
}
}
@Override
public void endDraggingFrame(JComponent f) {
super.endDraggingFrame(f);
f.putClientProperty("outlineBounds", null);
if (isOutline(f)) {
RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
r.getGlassPane().setVisible(false);
}
}
protected boolean isOutline(JComponent f) {
return ((JInternalFrame) f).getDesktopPane().getDragMode() ==
JDesktopPane.OUTLINE_DRAG_MODE;
}
}
用法:
final JDesktopPane dt = new JDesktopPane() {
@Override
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : this.getAllFrames()) {
max.add(jif.getNormalBounds());
Rectangle outline = (Rectangle) jif.getClientProperty("outlineBounds");
if (outline != null) {
max.add(outline);
}
}
int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
: max.height + (max.y * 2);
return new Dimension(x1, y1);
}
};
dt.setDesktopManager(new MyDesktopManager());
dt.setAutoscrolls(true);
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;
// oer-internalframe componentListener
ComponentListener il = new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
dt.revalidate();
}
};
// per-internalframe outlineListener
PropertyChangeListener propertyL = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
dt.revalidate();
}
};
for (int ii = 0; ii < 3; ii++) {
JInternalFrame jif = new JInternalFrame(
"Internal Frame " + (ii + 1),
true,
true,
true);
dt.add(jif);
jif.addComponentListener(il);
jif.addPropertyChangeListener("outlineBounds", propertyL);
jif.setLocation(xx, yy);
xx += vStep;
yy += yStep;
jif.setSize(200, 75);
jif.setVisible(true);
}
[*]默认的轮廓绘制会闪烁(在一定程度上是不可见的)——原因是默认实现使用。。。获取图形()。。。因此,我们需要接管轮廓绘制,例如在专用的玻璃窗格中(由注释代码完成),或者更好的是在桌面上使用LayerUI。
一个粗糙的玻璃窗格,就像一个没有正确剪辑的poc,当框架移回可见的矩形时会出现一些问题:
public static class OutlinePanel extends JPanel {
private JDesktopPane desktop;
public OutlinePanel(JDesktopPane desktop) {
this.desktop = desktop;
}
@Override
public boolean isOpaque() {
return false;
}
@Override
protected void paintComponent(Graphics g) {
JInternalFrame selected = desktop.getSelectedFrame();
Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
if (outline == null) return;
Rectangle bounds = SwingUtilities.convertRectangle(desktop, outline, this);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
使现代化
带有LayerUI的版本-现在我们将全新的行为(侦听器注册、绘制轮廓(如果需要)、安装管理器)留给装饰。优势:
LayerUI:
public class DesktopLayerUI extends LayerUI<JDesktopPane> {
@Override
public void installUI(JComponent c) {
super.installUI(c);
final JDesktopPane dt = getDesktopPane(c);
//dt.setBorder(BorderFactory.createLineBorder(Color.RED));
dt.setDesktopManager(new MyDesktopManager());
// per-internalframe componentListener
ComponentListener il = new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
dt.revalidate();
}
};
// per-internalframe outlineListener
PropertyChangeListener propertyL = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
dt.revalidate();
}
};
for (JInternalFrame jif : dt.getAllFrames()) {
jif.addComponentListener(il);
jif.addPropertyChangeListener("outlineBounds", propertyL);
}
// TBD: register container listener to update frame listeners on adding/removing
// TBD: componentListener on desktop that handles maximizing frame
// (JW: didn't really understand what that one is doing in the original)
}
@Override
public Dimension getPreferredSize(JComponent c) {
JDesktopPane dt = getDesktopPane(c);
Dimension prefSize = super.getPreferredSize(c);
//System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = dt.getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : dt.getAllFrames()) {
max.add(jif.getNormalBounds());
Rectangle outline = (Rectangle) jif
.getClientProperty("outlineBounds");
if (outline != null) {
max.add(outline);
}
}
// TBD: cope with frames at negative locations
//System.out.println("maxBounds(): " + max);
int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
: max.height + (max.y * 2);
//System.out.println("x,y: " + x1 + "," + y1);
return new Dimension(x1, y1);
}
@Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
JDesktopPane desktop = getDesktopPane(c);
JInternalFrame selected = desktop.getSelectedFrame();
if (selected == null) return;
Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
if (outline == null) return;
Rectangle bounds = outline; //SwingUtilities.convertRectangle(, outline, this);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
protected JDesktopPane getDesktopPane(JComponent c) {
JDesktopPane desktop = ((JLayer<JDesktopPane>) c).getView();
return desktop;
}
public static class MyDesktopManager extends DefaultDesktopManager {
private Point currentLoc;
@Override
public void dragFrame(JComponent f, int newX, int newY) {
if (isOutline(f)) {
// take over the outline drawing
currentLoc = new Point(newX, newY);
Rectangle bounds = new Rectangle(currentLoc, f.getSize());
f.putClientProperty("outlineBounds", bounds);
} else {
// call super only if not outline
// handle outline drawing ourselves
super.dragFrame(f, newX, newY);
}
}
@Override
public void beginDraggingFrame(JComponent f) {
super.beginDraggingFrame(f);
if (isOutline(f)) {
currentLoc = f.getLocation();
f.putClientProperty("outlineBounds", f.getBounds());
}
}
@Override
public void endDraggingFrame(JComponent f) {
if (isOutline(f) && currentLoc != null) {
setBoundsForFrame(f, currentLoc.x, currentLoc.y, f.getWidth(), f.getHeight() );
f.putClientProperty("outlineBounds", null);
} else {
super.endDraggingFrame(f);
}
}
protected boolean isOutline(JComponent f) {
return ((JInternalFrame) f).getDesktopPane().getDragMode() ==
JDesktopPane.OUTLINE_DRAG_MODE;
}
}
}
用法:
JDesktopPane dt = new JDesktopPane();
// add internalframes
...
// decorate the pane with the layer
JLayer<JDesktopPane> layer = new JLayer<>(dt, new DesktopLayerUI());
gui.add(new JScrollPane(layer), BorderLayout.CENTER);
有一个小问题(阅读:还不知道如何修复它):JLayer实现了Scrollable-它的实现为tracksXX返回false(如果装饰的组件本身不是Scrollable,JDesktopPane就不是),这意味着滚动窗格内的桌面总是按其prefSize调整大小,如果滚动窗格较大,则在尾部/底部区域显示灰色视口。