在尝试移植 swing 代码以符合 Java 模块系统时,我在尝试替换SwingUtilities3.setDelegateRepaintManager
.
我有一个组件,当它的任何孩子请求重绘时,我需要转换该区域(特别是这试图org.pbjar.jxlayer.plaf.ext.TransformUI
为任何知道这一点的人移植代码)。目前,这是通过设置组件的委托重绘管理器并拦截对addDirtyRegion
.
现在使用 Java 9,这样做的方法不再作为公共 api 可用。原始代码提供了一种替代方法,该方法最初用于不可用的旧版 Java SwingUtilities3.setDelegateRepaintManager
,它只是用RepaintManager
委托实现替换了全局。如果组件包含在需要转换的实际组件中,它会检查每个调用。然而,此解决方案会丢弃所有内部数据,RepaintManager
并在调整框架大小时导致严重闪烁。
这是当前使用的代码的精简版:
SwingUtilities3.setDelegateRepaintManager(component, new TransformRepaintManger());
...
class TransformRepaintManager extends RepaintManager {
@Override
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
if (c.isShowing()) {
Point point = c.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, c);
Rectangle transformPortRegion = transform(new Rectangle(x + point.x, y + point.y, w, h), c);
RepaintManager.currentManager(c)
.addDirtyRegion(c,
transformPortRegion.x, transformPortRegion.y,
transformPortRegion.width, transformPortRegion.height);
}
}
}
以及导致闪烁的替代方法(DelegateRepaintManager
只需采用原始方法RepaintManager
并将所有调用转发给它):
class TransformRPMFallBack extends DelegateRepaintManager {
@Override
public void addDirtyRegion(JComponent aComponent,int x, int y, int w, int h) {
if (aComponent.isShowing()) {
JComponent targetParent = findTargetParent(aComponent);
if (targetParent != null) {
Point point = aComponent.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, targetParent);
Rectangle transformPortRegion = transform(new Rectangle(x + point.x, y + point.y, w, h));
addDirtyRegion(targetParent,
transformPortRegion.x, transformPortRegion.y,
transformPortRegion.width, transformPortRegion.height);
return;
}
}
super.addDirtyRegion(aComponent, x, y, w, h);
}
}
我知道一种选择是简单地添加--add-exports java.desktop/com.sun.java.swing=<module name>
到启动参数中,但由于这是一个库,迫使每个人都使用它,所以我认为这不是最好的选择。
更新:
这是一个演示上述两种方法的示例。它由一个面板组成,该面板在JLayer
. 切换复选框以不同的颜色绘制组件的左侧(视觉顶部)或右侧(视觉底部)部分。可以通过在 中设置静态变量来更改不同的方法TransformLayerUI
。可以观察到以下行为:
SolutionApproach.FLICKERING
:单击复选框的行为与预期相同。调整窗口大小会导致闪烁(对于这样一个小例子,它不是很基础,但对于较大的应用程序,它会变得更糟)SolutionApproach.ILLEGAL
: 相同,SolutionApproach.FLICKERING
但没有闪烁。SolutionApproach.NONE
:单击复选框仅重新绘制应更改区域的四分之一。这是需要解决的问题。如果TestPanel
(或任何可能的孩子)请求重绘,则JLayer
应该重绘正确的区域。
public class TransformTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Transform Test");
JPanel contentPanel = new JPanel(new BorderLayout());
TestPanel testPanel = new TestPanel();
JLayer<TestPanel> testLayer = new JLayer<>(testPanel, new TransformLayerUI());
contentPanel.add(testLayer);
frame.getContentPane().add(contentPanel, BorderLayout.CENTER);
JCheckBox leftCheck = new JCheckBox("Left active");
leftCheck.addActionListener(e -> testPanel.setLeftActive(leftCheck.isSelected()));
JCheckBox rightCheck = new JCheckBox("Right active");
rightCheck.addActionListener(e -> testPanel.setRightActive(rightCheck.isSelected()));
JComponent buttonPanel = Box.createHorizontalBox();
buttonPanel.add(leftCheck);
buttonPanel.add(rightCheck);
frame.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
@SuppressWarnings("unchecked")
static class TransformLayerUI extends LayerUI<TestPanel> {
enum SolutionApproach {
ILLEGAL,
FLICKERING,
NONE
}
static SolutionApproach approach = SolutionApproach.ILLEGAL;
@Override
public void installUI(JComponent c) {
super.installUI(c);
switch (approach) {
case ILLEGAL:
SwingUtilities3.setDelegateRepaintManager(((JLayer<? extends JComponent>) c).getView(),
new TransformRepaintManager());
break;
case FLICKERING:
if (!(RepaintManager.currentManager(c) instanceof FallbackTransformRepaintManger)) {
RepaintManager.setCurrentManager(
new FallbackTransformRepaintManger(RepaintManager.currentManager(c)));
}
break;
case NONE:
break;
}
}
private AffineTransform calcTransform(Dimension size) {
AffineTransform at = new AffineTransform();
Point2D center = new Point2D.Double(size.getWidth() / 2f, size.getHeight() / 2f);
at.translate(center.getX(), center.getY());
at.quadrantRotate(1);
at.translate(-center.getX(), -center.getY());
return at;
}
private Rectangle transform(Dimension size, final Rectangle rect) {
Area area = new Area(rect);
area.transform(calcTransform(size));
return area.getBounds();
}
@Override
public void paint(Graphics g, JComponent c) {
((Graphics2D) g).transform(calcTransform(c.getSize()));
super.paint(g, c);
}
@Override
public void doLayout(JLayer<? extends TestPanel> l) {
l.getView().setBounds(transform(l.getSize(), new Rectangle(l.getSize())));
}
@Override
public Dimension getPreferredSize(JComponent c) {
return transform(((JLayer<? extends JComponent>) c).getView().getPreferredSize());
}
@Override
public Dimension getMaximumSize(JComponent c) {
return transform(((JLayer<? extends JComponent>) c).getView().getMaximumSize());
}
@Override
public Dimension getMinimumSize(JComponent c) {
return transform(((JLayer<? extends JComponent>) c).getView().getMinimumSize());
}
private Dimension transform(final Dimension size) {
Area area = new Area(new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight()));
area.transform(calcTransform(size));
Rectangle2D bounds = area.getBounds2D();
size.setSize(bounds.getWidth(), bounds.getHeight());
return size;
}
class TransformRepaintManager extends RepaintManager {
@Override
public void addInvalidComponent(JComponent invalidComponent) {
Container layer = SwingUtilities.getAncestorOfClass(JLayer.class, invalidComponent);
RepaintManager.currentManager(layer).addInvalidComponent((JComponent) layer);
}
@Override
public void addDirtyRegion(JComponent comp, int x, int y, int w, int h) {
if (comp.isShowing()) {
Container layer = SwingUtilities.getAncestorOfClass(JLayer.class, comp);
dispatchRepaint(comp, layer, TransformLayerUI.this, new Rectangle(x, y, w, h));
}
}
}
static void dispatchRepaint(Component comp, Component layer, TransformLayerUI ui, Rectangle rect) {
Point point = comp.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, layer);
Rectangle transformPortRegion =
ui.transform(layer.getSize(),
new Rectangle(rect.x + point.x, rect.y + point.y, rect.width, rect.height));
RepaintManager.currentManager(layer).addDirtyRegion((JComponent) layer,
transformPortRegion.x, transformPortRegion.y,
transformPortRegion.width, transformPortRegion.height);
}
static class FallbackTransformRepaintManger extends DelegateRepaintManager {
FallbackTransformRepaintManger(RepaintManager delegate) {
super(delegate);
}
@Override
public void addDirtyRegion(JComponent aComponent, int x, int y, int w, int h) {
if (aComponent.isShowing()) {
JLayer<?> layer = (JLayer<?>) SwingUtilities.getAncestorOfClass(JLayer.class, aComponent);
if (layer != null) {
LayerUI<?> layerUI = layer.getUI();
if (layerUI instanceof TransformLayerUI) {
TransformLayerUI ui = (TransformLayerUI) layerUI;
dispatchRepaint(aComponent, layer, ui, new Rectangle(x, y, w, h));
return;
}
}
}
super.addDirtyRegion(aComponent, x, y, w, h);
}
}
static class DelegateRepaintManager extends RepaintManager {
private final RepaintManager delegate;
DelegateRepaintManager(RepaintManager delegate) {
this.delegate = delegate;
}
@Override
public void addInvalidComponent(JComponent invalidComponent) {
delegate.addInvalidComponent(invalidComponent);
}
@Override
public void removeInvalidComponent(JComponent component) {
delegate.removeInvalidComponent(component);
}
@Override
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
delegate.addDirtyRegion(c, x, y, w, h);
}
@Override
public void addDirtyRegion(Window window, int x, int y, int w, int h) {
delegate.addDirtyRegion(window, x, y, w, h);
}
@Override
@Deprecated
public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
delegate.addDirtyRegion(applet, x, y, w, h);
}
@Override
public Rectangle getDirtyRegion(final JComponent c) {
return delegate.getDirtyRegion(c);
}
@Override
public void markCompletelyDirty(final JComponent c) {
delegate.markCompletelyDirty(c);
}
@Override
public boolean isCompletelyDirty(final JComponent c) {
return delegate.isCompletelyDirty(c);
}
@Override
public Dimension getDoubleBufferMaximumSize() {
return delegate.getDoubleBufferMaximumSize();
}
@Override
public void markCompletelyClean(final JComponent c) {
delegate.markCompletelyClean(c);
}
public RepaintManager getDelegateManager() {
return delegate;
}
@Override
public void setDoubleBufferMaximumSize(final Dimension d) {
delegate.setDoubleBufferMaximumSize(d);
}
@Override
public void validateInvalidComponents() {
delegate.validateInvalidComponents();
}
@Override
public void paintDirtyRegions() {
delegate.paintDirtyRegions();
}
@Override
public Image getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
}
@Override
public Image getVolatileOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
return delegate.getVolatileOffscreenBuffer(c, proposedWidth, proposedHeight);
}
@Override
public boolean isDoubleBufferingEnabled() {
return delegate.isDoubleBufferingEnabled();
}
@Override
public void setDoubleBufferingEnabled(final boolean flag) {
delegate.setDoubleBufferingEnabled(flag);
}
}
}
static class TestPanel extends JPanel {
private boolean leftActive;
private boolean rightActive;
TestPanel() {
setPreferredSize(new Dimension(600, 300));
setMinimumSize(new Dimension(600, 300));
setMaximumSize(new Dimension(600, 300));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (leftActive) {
g.setColor(Color.RED);
g.fillRect(0, 0, getWidth() / 2, getHeight());
}
if (rightActive) {
g.setColor(Color.GREEN);
g.fillRect(getWidth() / 2, 0, getWidth() / 2, getHeight());
}
g.setColor(Color.BLACK);
g.drawString("Left", (getWidth() - 50) / 4, getHeight() / 2 + 10);
g.drawString("Right", getWidth() / 2 + (getWidth() - 50) / 4, getHeight() / 2 + 10);
}
public void setLeftActive(boolean leftActive) {
this.leftActive = leftActive;
repaint(0, 0, getWidth() / 2, getHeight());
}
public void setRightActive(boolean rightActive) {
this.rightActive = rightActive;
repaint(getWidth() / 2, 0, getWidth() / 2, getHeight());
}
}
}
更新 2:
有FallbackTransformRepaintManger
extendRepaintManager
而不是DelegateRepaintManager
. 但是,这会使使用该组件成为破坏性操作,因为RepaintManager
之前的任何其他自定义设置都将被覆盖。