Dynamically change the color of custom graphics

Problem: graphs are not redrawn until methods are run.

When a button is pressed, two methods are called. Inside each method there is code that should change the color of the graph associated with this method (in the user interface); when the method starts, the image changes from black to green; when the method ends, the color changes from green to red. Then the next method is called, and its graphical object should turn green (the method is running), and when the method finishes its graphics, it should be filled in red (the method is finished).

I created a simple pie status diagram (30 px circle with fill color) with three color states: black for ready; green for running; red for the ready.

I believe the problem is that repaint() is in a separate thread and is scheduled to run when possible? I tried to put code that updates the graphics inside my own thread, and then use thread .join() to make sure the code is already running, but that didn't work.

EDIT

Edit: removing the code that I used for demonstration, and replacing it with one sample of executable code according to the comments. What you will see, if you run the code, after you click the button, the schedule is not updated, when each method is started and stopped, it waits for both methods to start, and then repaint the graphic.

  package graphicsUpdateDemo; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Ellipse2D; import java.beans.Transient; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; /** * Application entry */ public class App{ public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new MainFrame(); } }); } } /** * Main frame */ class MainFrame extends JFrame implements SomeListener{ private AddedPanel addedPanel; // Constructor public MainFrame(){ // Set frame properties setSize(500, 500); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); setLayout(new FlowLayout(FlowLayout.CENTER, 5, 20)); // Create AddedPanel. addedPanel = new AddedPanel(); add(addedPanel); // Set AddedPanel listener to this JFrame. addedPanel.setSomeListener(this); } // AddedPanel listener method @Override public void doStuff() { // run simulated sort methods sort1(); sort2(); } // Simulated sort // .......graphic should turn green as soon as method starts // .......graphic should turn red as soon as method finishes. private void sort1() { // repaint graphic to show method is starting addedPanel.statusOne.setStatus(SortStatus.running); // EDIT: Make panel repaint itself. addedPanel.paintImmediately(0, 0, getWidth(), getHeight()); // Simulate work being done. try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // repaint graphic to show methid is finished addedPanel.statusOne.setStatus(SortStatus.finished); // EDIT: Make panel repaint itself. addedPanel.paintImmediately(0, 0, getWidth(), getHeight()); } // Simulated sort // .......graphic should turn green as soon as method starts // .......graphic should turn red as soon as method finishes. private void sort2() { // repaint graphic to show method is starting (green) addedPanel.statusTwo.setStatus(SortStatus.running); // EDIT: Make panel repaint itself. addedPanel.paintImmediately(0, 0, getWidth(), getHeight()); // Simulate work being done. try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // repaint graphic to show method is finished. addedPanel.statusTwo.setStatus(SortStatus.finished); // EDIT: Make panel repaint itself. addedPanel.paintImmediately(0, 0, getWidth(), getHeight()); } } /** * Panel to add to MainFrame */ class AddedPanel extends JPanel{ // Button listener SomeListener listener; // Button private JButton aButton = new JButton("Click Me"); // Create Status Circles for showing method state. public StatusCircles statusOne = new StatusCircles(); public StatusCircles statusTwo = new StatusCircles(); // Constructor. public AddedPanel(){ setLayout(new BorderLayout(0, 15)); // Add button to panel. add(aButton, BorderLayout.NORTH); // Make panel for holding graphics and labels. JPanel resultsPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(5, 5, 5, 5); resultsPanel.add(statusOne, c); c.gridx = 1; resultsPanel.add(new JLabel("Method A"), c); c.gridx = 0; c.gridy = 1; resultsPanel.add(statusTwo, c); c.gridx = 1; resultsPanel.add(new JLabel("Method B"), c); add(resultsPanel, BorderLayout.CENTER); aButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { if(listener != null){ listener.doStuff(); } } }); } public void setSomeListener(SomeListener listener){ this.listener = listener; } } /** * Graphic for showing user state of method: * black for ready * green for running * red for finished */ class StatusCircles extends JPanel{ private SortStatus sortStatus; private Ellipse2D.Double statusCircle = new Ellipse2D.Double(2, 2, 25, 25); // Constructor public StatusCircles(){ sortStatus = SortStatus.ready; } @Override protected void paintComponent(Graphics g) { // Cast Graphics to Graphics2D Graphics2D g2 = (Graphics2D)g; // Turn on anti aliasing g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Set background g2.setColor(Color.BLACK); g2.fillRect(0, 0, getWidth(), getHeight()); // Fill status circle with color based on status field switch(sortStatus){ case ready: g2.setColor(Color.BLACK); g2.fill(statusCircle); break; case running: g2.setColor(Color.GREEN); g2.fill(statusCircle); break; case finished: g2.setColor(Color.RED); g2.fill(statusCircle); break; } } @Override @Transient public Dimension getPreferredSize() { return new Dimension(30, 30); } // Set state method is in. public void setStatus(SortStatus status) { this.sortStatus = status; repaint(); } } /** * Interface */ interface SomeListener{ public void doStuff(); } /** * Enum for depicting status of graphic. */ enum SortStatus { ready, running, finished } 

EDIT

"The repaint method submits a request to update the viewport and returns immediately. Its effect is asynchronous, which means that the JVM must execute the paintComponent method in a separate thread." - Introduction to Java Liang programming.

I think the problem is either A) in my ignorance, my program design does something that a sensible programmer will not do, and / or B) I don’t know how to make the program change the graphic colors, and then it happens, then keep doing work on any thread you're working on (EDT, main thread?).

I came across an answer that suggested never slowing down the "main thread" in order to wait for something to be drawn; and instead create icons for each status circle and then change the icons - which, I think, will immediately redraw everything that the icon holds? Does this not mean that there is a way to make it repaint right away?

Thought experiment: you have a loop that runs 100 times, each iteration takes a second. You want to show the user each iteration by changing the color of the circle to one of a hundred different colors. Could you make 100 different icons for this? Or, and what I want to do is change the fill color at each iteration. ... but how to get a circle to be redrawn with each iteration?

EDIT

I do not know if this is the “right” solution, but the program now functions as I want it. I put these addedPanel.paintImmediately(0, 0, getWidth(), getHeight()); immediately after the method call, requiring a change in the graphic color. I updated the working example code above, editing is depicted on "// EDIT: Make the panel repaint itself."

EDIT

Now I have more confidence that I'm on the right track. I believe that I have implemented what I recommended. Understanding SwingWorker turned out to be very quick, as soon as I realized that it is mostly similar to Android asynTask() (that is where I first found out why I say that). And the simulated work through sleep happens in its own branch, with EDT, so now (?) ((Not that I need my program to take a nap). Here, now, this is the full working code:

 package graphicsUpdateDemo; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Ellipse2D; import java.beans.Transient; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; /** * Application entry */ public class App{ public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new MainFrame(); } }); } } /** * Main frame */ class MainFrame extends JFrame implements SomeListener{ private AddedPanel addedPanel; // Constructor public MainFrame(){ // Set frame properties setSize(500, 500); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new FlowLayout(FlowLayout.CENTER, 5, 20)); // Create AddedPanel. addedPanel = new AddedPanel(); add(addedPanel); // Set AddedPanel listener to this JFrame. addedPanel.setSomeListener(this); // Call setVisible last setVisible(true); } // AddedPanel listener method @Override public void doStuff() { // Call sort1(), when that finishes have it call sort2(). sort1(); } // Simulated sort // .......graphic should turn green as soon as method starts // .......graphic should turn red as soon as method finishes. private void sort1() { // repaint graphic to show method is starting addedPanel.statusOne.setStatus(SortStatus.running); // Run sort in its own thread. SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){ @Override protected Void doInBackground() throws Exception { // Simulate work being done. try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void done() { // repaint graphic to show methid is finished addedPanel.statusOne.setStatus(SortStatus.finished); // Call sort2 sort2(); } }; worker.execute(); } // Simulated sort // .......graphic should turn green as soon as method starts // .......graphic should turn red as soon as method finishes. private void sort2() { // repaint graphic to show method is starting (green) addedPanel.statusTwo.setStatus(SortStatus.running); // Run sort in its own thread SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){ @Override protected Void doInBackground() throws Exception { // Simulate work being done. try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void done() { // repaint graphic to show method is finished. addedPanel.statusTwo.setStatus(SortStatus.finished); } }; worker.execute(); } } /** * Panel to add to MainFrame */ class AddedPanel extends JPanel{ // Button listener SomeListener listener; // Button private JButton aButton = new JButton("Click Me"); // Create Status Circles for showing method state. public StatusCircles statusOne = new StatusCircles(); public StatusCircles statusTwo = new StatusCircles(); // Constructor. public AddedPanel(){ setLayout(new BorderLayout(0, 15)); // Add button to panel. add(aButton, BorderLayout.NORTH); // Make panel for holding graphics and labels. JPanel resultsPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(5, 5, 5, 5); resultsPanel.add(statusOne, c); c.gridx = 1; resultsPanel.add(new JLabel("Method A"), c); c.gridx = 0; c.gridy = 1; resultsPanel.add(statusTwo, c); c.gridx = 1; resultsPanel.add(new JLabel("Method B"), c); add(resultsPanel, BorderLayout.CENTER); aButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { if(listener != null){ listener.doStuff(); } } }); } public void setSomeListener(SomeListener listener){ this.listener = listener; } } /** * Graphic for showing user state of method: * black for ready * green for running * red for finished */ class StatusCircles extends JPanel{ private SortStatus sortStatus; private Ellipse2D.Double statusCircle = new Ellipse2D.Double(2, 2, 25, 25); // Constructor public StatusCircles(){ sortStatus = SortStatus.ready; } @Override protected void paintComponent(Graphics g) { // Cast Graphics to Graphics2D Graphics2D g2 = (Graphics2D)g; // Turn on anti aliasing g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Set background g2.setColor(Color.BLACK); g2.fillRect(0, 0, getWidth(), getHeight()); // Fill status circle with color based on status field switch(sortStatus){ case ready: g2.setColor(Color.BLACK); g2.fill(statusCircle); break; case running: g2.setColor(Color.GREEN); g2.fill(statusCircle); break; case finished: g2.setColor(Color.RED); g2.fill(statusCircle); break; } } @Override @Transient public Dimension getPreferredSize() { return new Dimension(30, 30); } // Set state method is in. public void setStatus(SortStatus status) { this.sortStatus = status; repaint(); } } /** * Interface */ interface SomeListener{ public void doStuff(); } /** * Enum for depicting status of graphic. */ enum SortStatus { ready, running, finished } 
+4
source share
1 answer

Using the approach presented here , let each view update its display from a separate SwingWorker , and the working Supervisor controls the CountDownLatch to determine when all views are executed.

image

Addendum: I have never seen an Applet before, and not SwingWorker ... I don’t understand why I will need to determine when all kinds will be completed ... I edited the question.

  • An example is also a hybrid .

  • SwingWorker helps avoid EDT blocking.

  • The definition done indicates when to (re) enable the start button.

  • Try adding the PropertyChangeListener shown here to the ColorIcon seen here in your shortcut. Each time you setProgress() at the worker, you will see the corresponding PropertyChangeEvent .

+5
source

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


All Articles