7

我正在使用此处JScrollNavigator描述的组件,以便在我嵌入在.JScrollPane

我试图调整JScrollNavigator以绘制画布的缩略图,以便为用户提供一些额外的上下文。但是,这样做会导致我的应用程序主框架的呈现损坏。具体来说,是调用paint(Graphics)视口组件(即我的主画布)的动作,传入Graphics由创建的对象BufferedImage导致后续显示损坏;如果我将此行注释掉,一切正常。

下面是JScrollNavigator的覆盖paintComponent方法:

@Override
protected void paintComponent(Graphics g) {
    Component view = jScrollPane.getViewport().getView();
    BufferedImage img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = img.createGraphics();

    // Paint JScrollPane view to off-screen image and then scale.
    // It is this action that causes the display corruption!
    view.paint(g2d);
    g2d.drawImage(img, 0, 0, null);
    Image scaled = img.getScaledInstance(getWidth(), getHeight(), 0);

    super.paintComponent(g);
    g.drawImage(scaled, 0, 0, null);
}

有人对腐败的原因有什么建议吗?我会认为绘制到屏幕外图像应该对现有的绘制操作没有影响。

编辑

提供一些额外的细节:JScrollNavigator表单左侧的子面板JSplitPane。与导航器JScrollPane关联的位于右侧。“损坏”导致拆分器不再被渲染并且滚动条不可见(它们看起来是白色的)。如果我调整大小JFrame,该JMenu部分也会变成白色。如果我尝试使用导航器或与滚动条交互,它们会变得可见,但拆分器仍然是白色的。就好像各种组件的不透明设置受到了将视口视图渲染到屏幕外图像的影响。

此外,如果我将其JScrollNavigator显示在一个完全独立的JDialog中,则一切正常。

编辑 2

我可以通过执行以下操作来一致地重现该问题:

添加JMenuBarmFrame

JMenuBar bar = new JMenuBar();
bar.add(new JMenu("File"));
mFrame.setJMenuBar(bar);

在替换main()方法中:JScrollNavigator

jsp.setViewportView(textArea);

... 和:

jsp.setViewportView(new JPanel() {
  {
    setBackground(Color.GREEN);
    setBorder(BorderFactory.createLineBorder(Color.BLACK, 5));
  }
});

确保JScrollNavigator作为面板嵌入其中mFrame,而不是单独显示JDialog

mFrame.add(jsp, BorderLayout.CENTER);
mFrame.add(nav, BorderLayout.NORTH);

现在当应用程序运行时JMenuBar 不再可见;将视图(即JPanel带有粗黑边框的绿色)绘制到Graphics2D返回的行为BufferedImage.createGraphics()实际上似乎是在屏幕上渲染它,可能是从 JFrame 的左上角开始,从而掩盖了其他组件。这似乎只在 aJPanel用作视口视图时才会发生,而不是其他组件,例如JTextArea,JTable等。

编辑 3

看起来这个人遇到了同样的问题(虽然没有发布解决方案):http ://www.javaworld.com/community/node/2894/

编辑 4

以下是导致编辑 2 中描述的可重现错误的方法main和方法:paintComponent

public static void main(String[] args) {
    JScrollPane jsp = new JScrollPane();
    jsp.setViewportView(new JPanel() {
        {
            setBackground(Color.GREEN);
            setBorder(BorderFactory.createLineBorder(Color.BLACK, 5));
        }
    });

    JScrollNavigator nav = new JScrollNavigator();
    nav.setJScrollPane(jsp);

    JFrame mFrame = new JFrame();

    JMenuBar bar = new JMenuBar();
    bar.add(new JMenu("File"));
    mFrame.setJMenuBar(bar);

    mFrame.setTitle("JScrollNavigator Test");

    mFrame.setSize(800, 600);

    mFrame.setLayout(new GridLayout(1, 2));

    mFrame.add(jsp);
    mFrame.add(nav);
    Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
    mFrame.setLocation((screenDim.width - mFrame.getSize().width) / 2, (screenDim.height - mFrame.getSize().height) / 2);

    mFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    mFrame.setVisible(true);
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    Component view = jScrollPane.getViewport().getView();

    if (img == null) {
        GraphicsConfiguration gfConf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB);
    }

    Graphics2D g2d = img.createGraphics();
    view.paint(g2d);

    Image scaled = img.getScaledInstance(getWidth(), getHeight(), 0);

    g.drawImage(scaled, 0, 0, null);
}

编辑 5

似乎其他人在重现确切问题时遇到了麻烦。我会要求人们运行粘贴在这里的代码。当我第一次运行此示例时,我看到以下内容:

损坏的图像 1

JScrollNavigator 和 JMenuBar 都没有被绘制;这些框架区域是透明的。

调整大小后,我看到以下内容:

损坏的图像 2

JMenuBar仍未绘制,并且似乎在JPanel某个点呈现在(0,0)(JMenuBar应该在哪里)。里面的view.paint呼唤paintComponent是造成这种情况的直接原因。

4

3 回答 3

9

摘要:原版JScrollNavigator使用 Swing属性在相邻的组件的缩放缩略图上opacity呈现方便的绿色。因为它 extends ,所以(共享的)UI 委托的使用与可滚动组件的使用冲突。在上面的编辑 5 中看到的图像代表了相关的渲染工件,也显示在此处。解决方案是 let和可滚动组件 extend ,如下面的第二个附录中所建议的那样。然后每个组件可以单独管理它自己的属性。NavBoxJScrollPaneJPanelopacityNavBoxJScrollNavigatorJComponent

在此处输入图像描述

在我的平台 Mac OS X、Java 1.6 上发布的代码中,我没有发现异常的渲染工件。抱歉,我没有看到任何明显的可移植性违规行为。

图一

一些可能不相关但可能有用的观察结果。

  • 即使您setSize()在这种情况下适当地使用 ,您仍然应该pack()使用Window.

    f.pack();
    f.setSize(300, 200);
    
  • 为方便起见,add()将组件转发到内容窗格。

    f.add(nav, BorderLayout.WEST);
    
  • StringBuilder更喜欢StringBuffer.

  • 考虑ComponentAdapter代替ComponentListener.

附录:正如这里所建议的,我得到了一些更灵活的结果,RenderingHints而不是getScaledInstance()如下所示。添加一些图标可以更轻松地查看图像和文本的不同效果。

图二

editPane.insertIcon(UIManager.getIcon("OptionPane.errorIcon"));
editPane.insertIcon(UIManager.getIcon("OptionPane.warningIcon"));
...
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Component view = jScrollPane.getViewport().getView();
    BufferedImage img = new BufferedImage(view.getWidth(),
        view.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D off = img.createGraphics();
    off.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    off.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
        RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    view.paint(off);
    Graphics2D on = (Graphics2D)g;
    on.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    on.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
        RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    on.drawImage(img, 0, 0, getWidth(), getHeight(), null);
}

附录 secundum:看起来JPanelUI 委托不合作。一种解决方法是扩展JComponent以便您可以控制opacity。管理backgroundColor. NavBox并且JScrollNavigator也是类似治疗的候选者。

在此处输入图像描述

jsp.setViewportView(new JComponent() {

    {
        setBackground(Color.red);
        setBorder(BorderFactory.createLineBorder(Color.BLACK, 16));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(300, 300);
    }
});
于 2012-07-31T16:39:33.823 回答
2

我也不确定您所说的损坏是什么意思,但我注意到如果您指定 Image.SCALE_SMOOTH 作为重新缩放提示,重新采样的图像会更好:

Image scaled = img.getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH);

也许这就是你正在寻找的......

于 2012-07-31T17:15:59.030 回答
2

我能够重现您的问题并为您提供所需的结果。问题是,当您再次重新绘制时,图像的绘制尚未完成,因此只有部分图像被绘制。要解决此问题,请将此字段添加到您的JScrollNavigator类中(作为锁):

/** Lock to prevent trying to repaint too many times */
private boolean blockRepaint = false;

当我们重绘组件时,这个锁将被激活。在我们成功绘制面板之前,它不会发布 - 然后可以执行另一个绘制。

paintComponent需要更改以遵守锁定并在ImageObserver绘制导航面板时使用。

@Override
protected void paintComponent(final Graphics g) {
    super.paintComponent(g);
    if(!blockRepaint){
        final Component view = (Component)jScrollPane.getViewport().getView();
        BufferedImage img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // Paint JScrollPane view to off-screen image and then scale.
        // It is this action that causes the display corruption!
        view.paint(g2d);
        ImageObserver io = new ImageObserver() {

            @Override
            public boolean imageUpdate(Image img, int infoflags, int x, int y,int width, int height) {
                boolean result = true;
                g.drawImage(img, 0, 0, null);
                if((infoflags & ImageObserver.FRAMEBITS) == ImageObserver.FRAMEBITS){
                    blockRepaint = false;
                    result = false;
                }

                return result;
            }
        };

        Image scaled = img.getScaledInstance(getWidth(), getHeight(), 0);
        blockRepaint = g.drawImage(scaled, 0, 0, io);
    }
}
于 2012-08-03T20:38:05.577 回答