Qualcuno ha mai provato a utilizzare Swing per costruire un ambiente di rendering multi-buffer corretto in cima al quale è possibile aggiungere gli elementi dell'interfaccia utente di Swing?JTextFields sopra il disegno attivo su JPanel, problemi di threading
In questo caso ho un rettangolo rosso animato disegnato su uno sfondo. Lo sfondo non ha bisogno di essere aggiornato ogni fotogramma, quindi lo renderò su una BufferedImage e ridisegnerò solo la parte necessaria per cancellare la posizione precedente del rettangolo. Vedi il codice completo qui sotto, questo estende l'esempio dato da @trashgod in una discussione precedente, here.
Fin qui tutto bene; animazione fluida, basso utilizzo della CPU, assenza di sfarfallio.
Quindi aggiungo un JTextField al Jpanel (facendo clic su qualsiasi posizione sullo schermo) e ci concentriamo su di esso facendo clic all'interno della casella di testo. Cancellare la posizione precedente del rettangolo ora non riesce su ogni lampeggiamento del cursore, vedere l'immagine qui sotto.
Sono curioso di sapere se qualcuno ha un'idea del perché questo potrebbe accadere (Swing non è thread-safe? L'immagine viene dipinta in modo asincrono?) E in che direzione cercare le possibili soluzioni.
Questo è in 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
}
}
Grazie per la risposta. Se aggiungi super.paintComponent (g) ed esegui l'applicazione di prova che ho fornito, vedrai che sfortunatamente questa non è una soluzione. Il metodo paintComponent predefinito di JPanel cancella il suo sfondo, il che rende necessario ridisegnare l'intera immagine di sfondo, principalmente annullando lo scopo di pre-rendering dell'immagine di sfondo. Ho la sensazione che sia possibile ridisegnare solo l'area necessaria, ma a quanto pare, c'è qualche problema nel ridisegnare il campo di testo in un thread diverso rispetto al ridisegnare l'immagine di sfondo che causa questi artefatti. – Mattijs
Riguardo all'addendum: molte grazie ancora per aver dedicato del tempo a tuffarsi in questo. Mi rendo conto che il tuo punto, essendo che non c'è modo che i JTextFields che si ridisegnano automaticamente funzionino correttamente insieme al ridisegno attivo * senza * usando super.paintComponent (g), è corretto. Tuttavia, l'uso di super.paintComponent (g) implica che l'intera finestra debba essere ridisegnata su ogni frame di animazione, rendendo l'utilizzo della CPU dipendente dalla dimensione della finestra, che è ciò che vorrei impedire. Quindi la mia domanda ha una risposta, ma il mio problema è ancora in piedi. Farò un nuovo post su questo. – Mattijs
@Mattijs: l'utilizzo di setOpaque (false) eviterà il riempimento. Ho aggiornato l'esempio per mostrare il tempo di pittura. La differenza è notevole, ma deve essere valutata rispetto allo sforzo di gestire gli aggiornamenti. – trashgod