9

在尝试移植 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:FallbackTransformRepaintMangerextendRepaintManager而不是DelegateRepaintManager. 但是,这会使使用该组件成为破坏性操作,因为RepaintManager之前的任何其他自定义设置都将被覆盖。

4

0 回答 0