Atomic rendering in Swing

I have a swing application containing an SVG canvas inside a JScrollPane. The application changes the displayed SVG document, which also leads to a change in the size of the document. This resizing should be reflected in the application. The size of the SVG canvas changes, and the JScrollPane viewport scrolls so that it displays the correct section of the canvas.

However, this leads to something like a β€œvisual jump,” because the user first sees the canvas resize and then sees the scroll operation.

Is there any way to tell java to stop processing rendering events for a given component (and its subcomponents) and resume only after I have finished my changes to only display the result of all the changes?

Here is my idea in pseudo code:

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

I tried using myScrollPane.setIgnoreRepaint(true) , but it seems to have no effect (even if I never set ignoreRepaint to false again.

And here is SSCCE, which is trying to simulate the effect:

 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 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
source share
1 answer

Typically, one of the possibilities is to use a custom RepaintManager. The following is a simplified and very non-optimized example:

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

Install a redraw manager somewhere at the beginning of your code using RepaintManager.setCurrentManager() , and then use freeze(componentTree) before looking at a series of operations and follow thaw(componentTree) after completion.

This works for most components, but unfortunately, JScrollPane is one for which it is not enough, since it makes a more complex picture than most. Therefore, you may need a JScrollPane whose createViewport () returns a view port that can suppress glare, for example:

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

It will also require freezing before a series of modifications and with the aforementioned redraw manager, thawing before the repaint dispatch method is called.

+1
source

Source: https://habr.com/ru/post/1485755/


All Articles