2010-07-15 2 views
9

Hat jemand jemals versucht, Swing zu verwenden, um eine richtige Multi-gepufferte Rendering-Umgebung zu erstellen, auf der Swing-Benutzeroberflächenelemente hinzugefügt werden können?JTextFields über aktive Zeichnung auf JPanel, Threading-Probleme

In diesem Fall habe ich ein animiertes rotes Rechteck auf einen Hintergrund gezeichnet. Der Hintergrund muss nicht für jeden Frame aktualisiert werden, daher rende ich ihn auf ein BufferedImage und zeichne nur den Teil neu, der zum Löschen der vorherigen Position des Rechtecks ​​erforderlich ist. Sehen Sie sich den vollständigen Code unten an. Dies erweitert das Beispiel von @trashgod in einem früheren Thread, here.

So weit so gut; glatte Animation, geringe CPU-Auslastung, kein Flackern.

Dann füge ich ein JTextField zum Jpanel hinzu (indem ich auf irgendeine Position auf dem Bildschirm klicke), und konzentriere mich darauf, indem ich in das Textfeld klicke. Das Löschen der vorherigen Position des Rechtecks ​​schlägt bei jedem blinkenden Cursor fehl, siehe Abbildung unten.

Ich bin neugierig, wenn jemand eine Idee davon hat, warum dies passieren könnte (Swing nicht thread-safe? Das Bild wird asynchron gemalt?) Und in welche Richtung nach möglichen Lösungen zu suchen.

Dies ist auf Mac OS 10.5, Java 1,6

JPanel redraw fail http://www.arttech.nl/javaredrawerror.png

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.GraphicsConfiguration; 
import java.awt.GraphicsDevice; 
import java.awt.GraphicsEnvironment; 
import java.awt.Insets; 
import java.awt.Rectangle; 
import java.awt.Transparency; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.ComponentEvent; 
import java.awt.event.ComponentListener; 
import java.awt.event.MouseEvent; 
import java.awt.event.MouseListener; 
import java.awt.image.BufferedImage; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.JTextField; 
import javax.swing.Timer; 

public class NewTest extends JPanel implements 
    MouseListener, 
    ActionListener, 
    ComponentListener, 
    Runnable 
{ 

JFrame f; 
Insets insets; 
private Timer t = new Timer(20, this); 
BufferedImage buffer1; 
boolean repaintBuffer1 = true; 
int initWidth = 640; 
int initHeight = 480; 
Rectangle rect; 

public static void main(String[] args) { 
    EventQueue.invokeLater(new NewTest()); 
} 

@Override 
public void run() { 
    f = new JFrame("NewTest"); 
    f.addComponentListener(this); 
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    f.add(this); 
    f.pack(); 
    f.setLocationRelativeTo(null); 
    f.setVisible(true); 
    createBuffers(); 
    insets = f.getInsets(); 
    t.start(); 
} 

public NewTest() { 
    super(true); 
    this.setPreferredSize(new Dimension(initWidth, initHeight)); 
    this.setLayout(null); 
    this.addMouseListener(this); 
} 

void createBuffers() { 
    int width = this.getWidth(); 
    int height = this.getHeight(); 

    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 
    GraphicsDevice gs = ge.getDefaultScreenDevice(); 
    GraphicsConfiguration gc = gs.getDefaultConfiguration(); 

    buffer1 = gc.createCompatibleImage(width, height, Transparency.OPAQUE);   

    repaintBuffer1 = true; 
} 

@Override 
protected void paintComponent(Graphics g) { 
    int width = this.getWidth(); 
    int height = this.getHeight(); 

    if (repaintBuffer1) { 
     Graphics g1 = buffer1.getGraphics(); 
     g1.clearRect(0, 0, width, height); 
     g1.setColor(Color.green); 
     g1.drawRect(0, 0, width - 1, height - 1); 
     g.drawImage(buffer1, 0, 0, null); 
     repaintBuffer1 = false; 
    } 

    double time = 2* Math.PI * (System.currentTimeMillis() % 5000)/5000.; 
    g.setColor(Color.RED); 
    if (rect != null) { 
     g.drawImage(buffer1, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, this); 
    } 
    rect = new Rectangle((int)(Math.sin(time) * width/3 + width/2 - 20), (int)(Math.cos(time) * height/3 + height/2) - 20, 40, 40); 
    g.fillRect(rect.x, rect.y, rect.width, rect.height); 
} 

@Override 
public void actionPerformed(ActionEvent e) { 
    this.repaint(); 
} 

@Override 
public void componentHidden(ComponentEvent arg0) { 
    // TODO Auto-generated method stub 

} 

@Override 
public void componentMoved(ComponentEvent arg0) { 
    // TODO Auto-generated method stub 

} 

@Override 
public void componentResized(ComponentEvent e) { 
    int width = e.getComponent().getWidth() - (insets.left + insets.right); 
    int height = e.getComponent().getHeight() - (insets.top + insets.bottom); 
    this.setSize(width, height); 
    createBuffers(); 
} 

@Override 
public void componentShown(ComponentEvent arg0) { 
    // TODO Auto-generated method stub 

} 

@Override 
public void mouseClicked(MouseEvent e) { 
    JTextField field = new JTextField("test"); 
    field.setBounds(new Rectangle(e.getX(), e.getY(), 100, 20)); 
    this.add(field); 
    repaintBuffer1 = true; 
} 

@Override 
public void mouseEntered(MouseEvent arg0) { 
    // TODO Auto-generated method stub 

} 

@Override 
public void mouseExited(MouseEvent arg0) { 
    // TODO Auto-generated method stub 

} 

@Override 
public void mousePressed(MouseEvent arg0) { 
    // TODO Auto-generated method stub 

} 

@Override 
public void mouseReleased(MouseEvent arg0) { 
    // TODO Auto-generated method stub 

} 
} 

Antwort

18

NewTest erstreckt JPanel; sondern weil Sie nicht jedes Pixel bei jedem Aufruf von paintComponent() Malerei, benötigen Sie die Super-Klasse-Methode aufrufen und das alte Zeichnung löschen:

@Override 
protected void paintComponent(Graphics g) { 
    super.paintComponent(g); 
    int width = this.getWidth(); 
    int height = this.getHeight(); 
    g.setColor(Color.black); 
    g.fillRect(0, 0, width, height); 
    ... 
} 

Nachtrag: Wie Sie beachten, die Einstellung der Hintergrundfarbe im Konstruktor schließt die Notwendigkeit aus, das Panel in paintComponent() zu füllen, während super.paintComponent() das Textfeld erlaubt, richtig zu funktionieren. Wie Sie sehen, ist die vorgeschlagene Problemumgehung fragil. Stattdessen vereinfachen Sie den Code und optimieren Sie wie gewünscht. Zum Beispiel benötigen Sie möglicherweise nicht die Komplikation von Einfügungen, zusätzlichen Puffern und einem Komponenten-Listener.

Zusatz 2: Beachten Sie, dass super.paintComponent() die update() -Methode des UI-Delegaten aufruft, "die die angegebene Komponente mit ihrer Hintergrundfarbe füllt (wenn ihre undurchsichtige Eigenschaft true ist)". Sie können setOpaque(false) verwenden, um dies auszuschließen.

Animation Test

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.GraphicsConfiguration; 
import java.awt.GraphicsEnvironment; 
import java.awt.Rectangle; 
import java.awt.Transparency; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.ComponentAdapter; 
import java.awt.event.ComponentEvent; 
import java.awt.event.MouseAdapter; 
import java.awt.event.MouseEvent; 
import java.awt.image.BufferedImage; 
import java.util.Random; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.JTextField; 
import javax.swing.Timer; 

/** @see http://stackoverflow.com/questions/3256941 */ 
public class AnimationTest extends JPanel implements ActionListener { 

    private static final int WIDE = 640; 
    private static final int HIGH = 480; 
    private static final int RADIUS = 25; 
    private static final int FRAMES = 24; 
    private final Timer timer = new Timer(20, this); 
    private final Rectangle rect = new Rectangle(); 
    private BufferedImage background; 
    private int index; 
    private long totalTime; 
    private long averageTime; 
    private int frameCount; 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       new AnimationTest().create(); 
      } 
     }); 
    } 

    private void create() { 
     JFrame f = new JFrame("AnimationTest"); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.add(this); 
     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setVisible(true); 
     timer.start(); 
    } 

    public AnimationTest() { 
     super(true); 
     this.setOpaque(false); 
     this.setPreferredSize(new Dimension(WIDE, HIGH)); 
     this.addMouseListener(new MouseHandler()); 
     this.addComponentListener(new ComponentHandler()); 
    } 

    @Override 
    protected void paintComponent(Graphics g) { 
     long start = System.nanoTime(); 
     super.paintComponent(g); 
     int w = this.getWidth(); 
     int h = this.getHeight(); 
     g.drawImage(background, 0, 0, this); 
     double theta = 2 * Math.PI * index++/64; 
     g.setColor(Color.blue); 
     rect.setRect(
      (int) (Math.sin(theta) * w/3 + w/2 - RADIUS), 
      (int) (Math.cos(theta) * h/3 + h/2 - RADIUS), 
      2 * RADIUS, 2 * RADIUS); 
     g.fillOval(rect.x, rect.y, rect.width, rect.height); 
     g.setColor(Color.white); 
     if (frameCount == FRAMES) { 
      averageTime = totalTime/FRAMES; 
      totalTime = 0; frameCount = 0; 
     } else { 
      totalTime += System.nanoTime() - start; 
      frameCount++; 
     } 
     String s = String.format("%1$5.3f", averageTime/1000000d); 
     g.drawString(s, 5, 16); 
    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     this.repaint(); 
    } 

    private class MouseHandler extends MouseAdapter { 

     @Override 
     public void mousePressed(MouseEvent e) { 
      super.mousePressed(e); 
      JTextField field = new JTextField("test"); 
      Dimension d = field.getPreferredSize(); 
      field.setBounds(e.getX(), e.getY(), d.width, d.height); 
      add(field); 
     } 
    } 

    private class ComponentHandler extends ComponentAdapter { 

     private final GraphicsEnvironment ge = 
      GraphicsEnvironment.getLocalGraphicsEnvironment(); 
     private final GraphicsConfiguration gc = 
      ge.getDefaultScreenDevice().getDefaultConfiguration(); 
     private final Random r = new Random(); 

     @Override 
     public void componentResized(ComponentEvent e) { 
      super.componentResized(e); 
      int w = getWidth(); 
      int h = getHeight(); 
      background = gc.createCompatibleImage(w, h, Transparency.OPAQUE); 
      Graphics2D g = background.createGraphics(); 
      g.clearRect(0, 0, w, h); 
      g.setColor(Color.green.darker()); 
      for (int i = 0; i < 128; i++) { 
       g.drawLine(w/2, h/2, r.nextInt(w), r.nextInt(h)); 
      } 
      g.dispose(); 
      System.out.println("Resized to " + w + " x " + h); 
     } 
    } 
} 
+0

Vielen Dank für Ihre Antwort. Wenn Sie super.paintComponent (g) hinzufügen und die von mir bereitgestellte Testanwendung ausführen, werden Sie feststellen, dass dies leider keine Lösung ist. Die standardmäßige paintComponent-Methode von JPanel löscht ihren Hintergrund, was es notwendig macht, das gesamte Hintergrundbild neu zu zeichnen, wobei der Zweck des Vorrenderns des Hintergrundbilds größtenteils zunichte gemacht wird. Ich habe eine Ahnung, dass das Neuzeichnen nur der notwendigen Bereich möglich sein sollte, aber wie es scheint, gibt es ein Problem mit dem Neuzeichnen des Textfelds in einem anderen Thread als Neuzeichnen des Hintergrundbildes, das diese Zeichnungsartefakte verursacht .. – Mattijs

+0

In Bezug auf das Addendum: vielen dank nochmals, dass du dir die zeit genommen hast, in diese einzutauchen. Ich verstehe, dass Ihr Punkt, dass es keine Möglichkeit gibt, dass die automatisch neu gezeichneten JTextFields korrekt zusammen mit dem aktiven Neuzeichnen * ohne * mit super.paintComponent (g) funktionieren, korrekt ist. Nichtsdestotrotz impliziert die Verwendung von super.paintComponent (g), dass das gesamte Fenster in jedem Animationsframe neu gezeichnet werden muss, was die CPU-Nutzung von der Fenstergröße abhängig macht, was ich gerne verhindern möchte. So wird meine Frage beantwortet, aber mein Problem steht immer noch. Ich werde einen neuen Beitrag dazu machen. – Mattijs

+0

@ Mattijs: Verwenden von SetOpaque (false) wird die Füllung vermeiden. Ich habe das Beispiel aktualisiert, um die Malzeit anzuzeigen. Der Unterschied ist beträchtlich, aber es muss gegen den Aufwand abgewogen werden, Updates zu verwalten. – trashgod

2

fand ich eine Abhilfe.

Was ich denke, geschah: wann immer ein JTextfield aktualisiert werden muss (d. H. Bei jedem Cursor blinkend), wird JPanels überschriebene paintComponent() aufgerufen, aber mit einem anderen Graphics-Argument als beim Aufruf von repaint(). So blinkte auf jedem Cursor, mein Rechteck wurde gelöscht und auf einer falschen Graphics-Instanz neu gezeichnet, wodurch die auf dem Bildschirm angezeigten Grafiken ungültig blieben.

Macht das Sinn? Wenn das der Fall ist, ist das nicht eine seltsame Unannehmlichkeit in Swing?

Wie auch immer, wenn ich eine boolean (activeRedraw) davon halte, woher der Aufruf stammt, sieht es so aus, als habe ich es geschafft, dieses Problem zu umgehen.Es scheint also, dass ich endlich einen Weg gefunden habe, aktiv zu zeichnen, ohne den gesamten Bildschirmbereich auf jedem Frame neu zu streichen, was eine geringe CPU-Nutzung unabhängig von der Fenstergröße bedeutet!

komplette Code hier:

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.GraphicsConfiguration; 
import java.awt.GraphicsDevice; 
import java.awt.GraphicsEnvironment; 
import java.awt.Insets; 
import java.awt.Rectangle; 
import java.awt.Transparency; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.ComponentEvent; 
import java.awt.event.ComponentListener; 
import java.awt.event.MouseEvent; 
import java.awt.event.MouseListener; 
import java.awt.image.BufferedImage; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.JTextField; 
import javax.swing.Timer; 

public class NewTest extends JPanel implements 
    MouseListener, 
    ActionListener, 
    ComponentListener, 
    Runnable 
{ 

    JFrame f; 
    Insets insets; 
    private Timer t = new Timer(20, this); 
    BufferedImage buffer1; 
    boolean repaintBuffer1 = true; 
    int initWidth = 640; 
    int initHeight = 480; 
    Rectangle rect; 
    boolean activeRedraw = true; 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new NewTest()); 
    } 

    @Override 
    public void run() { 
     f = new JFrame("NewTest"); 
     f.addComponentListener(this); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.add(this); 
     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setVisible(true); 
     createBuffers(); 
     insets = f.getInsets(); 
     t.start(); 
    } 

    public NewTest() { 
     super(true); 
     this.setPreferredSize(new Dimension(initWidth, initHeight)); 
     this.setLayout(null); 
     this.addMouseListener(this); 
    } 

    void createBuffers() { 
     int width = this.getWidth(); 
     int height = this.getHeight(); 

     GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 
     GraphicsDevice gs = ge.getDefaultScreenDevice(); 
     GraphicsConfiguration gc = gs.getDefaultConfiguration(); 

     buffer1 = gc.createCompatibleImage(width, height, Transparency.OPAQUE);   

     repaintBuffer1 = true; 
    } 

    @Override 
    protected void paintComponent(Graphics g) { 
     //super.paintComponent(g); 
     int width = this.getWidth(); 
     int height = this.getHeight(); 

     if (activeRedraw) { 
      if (repaintBuffer1) { 
       Graphics g1 = buffer1.getGraphics(); 
       g1.clearRect(0, 0, width, height); 
       g1.setColor(Color.green); 
       g1.drawRect(0, 0, width - 1, height - 1); 
       g.drawImage(buffer1, 0, 0, null); 
       repaintBuffer1 = false; 
      } 

      double time = 2* Math.PI * (System.currentTimeMillis() % 5000)/5000.; 
      g.setColor(Color.RED); 
      if (rect != null) { 
       g.drawImage(buffer1, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, this); 
      } 
      rect = new Rectangle((int)(Math.sin(time) * width/3 + width/2 - 20), (int)(Math.cos(time) * height/3 + height/2) - 20, 40, 40); 
      g.fillRect(rect.x, rect.y, rect.width, rect.height); 

      activeRedraw = false; 
     } 
    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     activeRedraw = true; 
     this.repaint(); 
    } 

    @Override 
    public void componentHidden(ComponentEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void componentMoved(ComponentEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void componentResized(ComponentEvent e) { 
     int width = e.getComponent().getWidth() - (insets.left + insets.right); 
     int height = e.getComponent().getHeight() - (insets.top + insets.bottom); 
     this.setSize(width, height); 
     createBuffers(); 
    } 

    @Override 
    public void componentShown(ComponentEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void mouseClicked(MouseEvent e) { 
     JTextField field = new JTextField("test"); 
     field.setBounds(new Rectangle(e.getX(), e.getY(), 100, 20)); 
     this.add(field); 
     repaintBuffer1 = true; 
    } 

    @Override 
    public void mouseEntered(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void mouseExited(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void mousePressed(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void mouseReleased(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 
} 
+0

Hinweis: Unter Windows ist das Verhalten erneut anders und die Problemumgehung ist nicht ausreichend. – Mattijs

+0

Ich habe in meiner Antwort ausgearbeitet. – trashgod