Хобрук: Ваш путь к мастерству в программировании

Атомарный рендеринг в Swing

У меня есть приложение Swing, которое содержит холст SVG внутри JScrollPane. Приложение изменяет отображаемый документ SVG, что также приводит к изменению размера документа. Это изменение размера должно быть отражено в приложении. Размер холста SVG изменяется, а область просмотра JScrollPane прокручивается, чтобы отображалась правильная часть холста.

Однако это приводит к чему-то вроде «визуального прыжка», потому что пользователь сначала видит изменение размера холста, а уже после этого видит операцию прокрутки.

Есть ли способ сказать java, чтобы он прекратил обработку событий рендеринга для данного компонента (и его подкомпонентов) и возобновил работу только после того, как я закончил свои модификации, чтобы отображать только результат всех модификаций?

Вот моя идея в псевдокоде:

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

Я пробовал с myScrollPane.setIgnoreRepaint(true), но, похоже, здесь это не имеет никакого эффекта (даже если я никогда больше не устанавливал ignoreRepaint в false.

А вот 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);
  }
}
12.06.2013

  • Чтобы быстрее получить помощь, опубликуйте SSCCE. 12.06.2013
  • Помните, что все изменения в пользовательском интерфейсе должны выполняться в контексте потока диспетчеризации событий. 12.06.2013
  • @AndrewThompson Это не так просто. Я пытался, но с очень простым SSCCE этого не происходит (или, по крайней мере, не видно). Я использую Batik в качестве инструментария SVG, и, поскольку он работает только в высокой степени асинхронно, он добавляет много сложности сам по себе. На самом деле изменение размера svgDocument и svgCanvas поэтому происходит в другом потоке, чем прокрутка до новой позиции. 12.06.2013
  • @MadProgrammer Я знаю об этом, но это тоже не проблема (возможно, это так, но этого нельзя избежать из-за асинхронного характера набора инструментов Batik). 12.06.2013
  • Это не так просто. Я никогда не говорил, что это будет легко, но ведь именно поэтому они платят нам «большие деньги». ;) 12.06.2013
  • @AndrewThompson Я добавил SSCCE. Конечно, это не так, как в моем реальном приложении, но вы, по крайней мере, можете увидеть прыжки, так как я немного задерживаю операцию прокрутки. 12.06.2013
  • @radlan Проблема в том, что вы не вносите изменения в пользовательский интерфейс в EDT. Изменения в пользовательском интерфейсе должны вноситься в EDT. Если вы хотите, чтобы изменения выглядели атомарно, убедитесь, что все они происходят в рамках одного события (SwingUtilities.invokeLater(new Runnable() { public void run() { performAllChangesToTheUI()}});), как изменения размера, так и прокрутки. 12.06.2013
  • @GuillaumePolet В этом проблема. Я не могу. Батик очень асинхронный. Мне нужно изменить размер холста, затем дождаться, пока батик сообщит мне о готовом рендеринге SVG. Затем я могу рассчитать новую позицию для прокрутки. Я попытался смоделировать этот эффект в моем примере выше. 12.06.2013
  • @radlan Не могли бы вы опубликовать очень простой пример (с SVG / Batik) кода, чтобы мы могли увидеть, как мы можем с этим разобраться? Я предполагаю, что вы должны изменить размер элементов батика, затем, когда батик будет готов, обновите размер/предпочтительный размер компонента батика, прокрутите до нужной позиции. 12.06.2013

Ответы:


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() возвращает порт просмотра, который может подавлять блики, например:

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);
    }
}

Это также потребует замораживания перед серией модификаций, а с помощью приведенного выше диспетчера перерисовки оттаивание до вызова метода оттаивания диспетчера перерисовки.

12.06.2013
Новые материалы

Введение в контекст React
В этом посте мы поговорим о Context API, который был представлен в React 16, и о том, как вы можете их использовать. Что такое контекст? Глядя на определение из react docs , оно..

Шлюз с лицензией OSS, совместимый с Apollo Federation v2, появится в WunderGraph
Сегодня мы рады сообщить, что мы сотрудничаем с поддерживаемой YC Tailor Technologies, Inc. для внедрения Apollo Federation v2. Реализация будет лицензирована MIT (Engine) и Apache 2.0..

Это оно
Ну, я официально уволился с работы! На этой неделе я буду лихорадочно выполнять последние требования Думающего , чтобы я мог сосредоточиться на поиске работы. Что именно это значит?..

7 полезных библиотек JavaScript, которые вы должны использовать в своем следующем проекте
Усильте свою разработку JavaScript Есть поговорка «Не нужно изобретать велосипед». Библиотеки — лучший тому пример. Это поможет вам написать сложные и трудоемкие функции простым способом...

Базовое руководство по переносу концепций обучения в глубокое обучение
Обзор По мере того, как машинное обучение становится все более мощным и продвинутым, модели, обеспечивающие эту расширенную возможность, становятся все больше и начинают требовать огромного..

C в C.R.U.D с использованием React-Redux
Если вы использовали React, возможно, вы знакомы с головной болью, связанной с обратным потоком данных. Передача состояния реквизитам от родительских компонентов к дочерним компонентам может..

5 обязательных элементов современного инструмента конвейера данных
В цифровом мире предприятия используют конвейеры данных для перемещения, преобразования и хранения огромных объемов данных. Эти конвейеры составляют основу бизнес-аналитики и играют..