4

我有一个摇摆应用程序,它在 JScrollPane 中包含一个 SVG 画布。应用程序修改了显示的 SVG 文档,这也导致了文档大小的变化。这种大小变化需要反映在应用程序中。调整 SVG 画布的大小并滚动 JScrollPane 的视口,以便它显示画布的正确部分。

但是,这会导致类似“视觉跳跃”的情况,因为用户首先看到画布大小的变化,然后看到滚动操作。

有没有办法告诉 java 停止处理给定组件(及其子组件)上的渲染事件,并且仅在我完成修改后才恢复以仅显示所有修改的结果?

这是我在伪代码中的想法:

myScrollPane.suspendRendering();
svgDocument.changeSize();
svgCanvas.changeSize();
myScrollPane.getViewport().scrollToCorrectPosition;
myScrollPane.resumeRendering();

我试过了myScrollPane.setIgnoreRepaint(true),但它似乎在这里没有任何效果(即使我再也没有设置ignoreRepaintfalse

这是一个尝试模拟效果的 SSCCE:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class Jumping extends JFrame {
  private JButton innerPanel= new JButton("Some silly, useless text, just for fun. And it goes on even longer. But that's not a problem.");
  private JScrollPane scrollPane= new JScrollPane(innerPanel);
  private JButton btnJump= new JButton("Jump");

  private int lastWidth= 1024;

  public Jumping(){
    this.setLayout(new BorderLayout());
    this.add(btnJump, BorderLayout.NORTH);
    this.add(scrollPane, BorderLayout.CENTER);

    this.scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    this.scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

    this.innerPanel.setPreferredSize(new Dimension(1024, 768));
    this.innerPanel.setSize(1024, 768);
    this.innerPanel.setMinimumSize(new Dimension(1024, 768));
    this.innerPanel.setMaximumSize(new Dimension(1024, 768));
    this.setSize(640, 480);

    this.btnJump.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        try{
          System.err.println("> actionPerfomed");
          //resize the canvas
          new Thread(){
            public void run() {
              System.err.println("> SwingWorker.doInBackground "+SwingUtilities.isEventDispatchThread());

              SwingUtilities.invokeLater(new Runnable(){
                public void run(){
                  System.err.println("> resize canvas "+SwingUtilities.isEventDispatchThread());
                  final int newWidth= (int) (lastWidth * 1.5);
                  innerPanel.setSize(newWidth, 768);
                  innerPanel.setPreferredSize(new Dimension(newWidth, 768));
                  innerPanel.setMinimumSize(new Dimension(newWidth, 768));
                  innerPanel.setMaximumSize(new Dimension(newWidth, 768));
                  lastWidth= newWidth;
                  System.err.println("< resize canvas "+SwingUtilities.isEventDispatchThread());
                }
              });

              //scroll to correct position
              SwingUtilities.invokeLater(new Runnable(){
                public void run(){
                  System.err.println("> scroll to pos "+SwingUtilities.isEventDispatchThread());

                  try {
                    System.err.println("< sleep "+SwingUtilities.isEventDispatchThread());
                    Thread.sleep(500);
                    System.err.println("> sleep "+SwingUtilities.isEventDispatchThread());
                  } catch (InterruptedException ex) {
                    ex.printStackTrace();
                  }

                  final Point viewPos= scrollPane.getViewport().getViewPosition();
                  scrollPane.getViewport().setViewPosition(new Point(viewPos.x + 50, viewPos.y));
                  System.err.println("< scroll to pos "+SwingUtilities.isEventDispatchThread());
                }
              });

              System.err.println("< SwingWorker.doInBackground "+SwingUtilities.isEventDispatchThread());
            }
          }.start();
          System.err.println("< actionPerfomed "+SwingUtilities.isEventDispatchThread());
        }catch(Exception ex){
          ex.printStackTrace();
        }
      }
    });
  }

  public static void main(String[] args){
    final Jumping frame= new Jumping();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
  }
}
4

1 回答 1

1

通常,一种可能性是使用自定义 RepaintManager。下面是一个简单且非常未优化的示例:

public class FreezableRepaintManager extends RepaintManager {
    final Set<Component> frozen = new HashSet<Component>();

    public void freeze(Container c) {
        frozen.add(c);
        for (Component child : c.getComponents()) {
            if (child instanceof Container) {
                freeze((Container) child);
            } else {
                frozen.add(child);
            }
        }
    }

    public void thaw(final Container c) {
        frozen.remove(c);
        for (Component child : c.getComponents()) {
            if (child instanceof Container) {
                thaw((Container) child);
            } else {
                frozen.remove(child);
            }
        }
        c.repaint();
    }


    @Override
    public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
        if (!frozen.contains(c)) {
            super.addDirtyRegion(c, x, y, w, h);
        }
    }
}

在代码的早期位置设置重绘管理器RepaintManager.setCurrentManager(),然后freeze(componentTree)在开始一系列操作之前使用,并thaw(componentTree)在完成后使用。

这适用于大多数组件,但不幸的是 JScrollPane 是不够的,因为它比大多数组件更复杂的绘图。所以你可能需要一个 JScrollPane ,它的 createViewport() 返回一个可以抑制 blits 的视口,例如:

class FreezableViewport extends JViewport {
    private boolean frozen;

    public void freeze() {
        frozen = true;
    }

    public void thaw() {
        frozen = false;
    }

    @Override
    protected boolean computeBlit(int dx, int dy, Point blitFrom,
        Point blitTo, Dimension blitSize, Rectangle blitPaint) {
        if (frozen) {
            return false;
        }
        return super.computeBlit(dx, dy, blitFrom, blitTo, blitSize, blitPaint);
    }
}

这也需要在一系列修改之前冻结,并且使用上面的重绘管理器,在调用重绘管理器的 thaw 方法之前解冻。

于 2013-06-12T18:10:04.000 回答