2011-05-16 2 views
0

J'ai une classe qui étend JPanel (ci-dessous), ce panneau se trouve à l'intérieur d'un JScrollPane. Il écoute (à lui-même) les événements de la souris et essaie de se repositionner (en faisant glisser) et de se redimensionner (sur la roue) pour simuler le mouvement de la souris et le zoom. Le panneau est également responsable de la sortie visuelle principale de mon application. Il stocke un BufferedImage qui est rendu dans la zone visible du JScrollPane (mais sur les graphiques du panneau). La taille et la forme de l'image sont conservées pour correspondre à la zone visible.Java Swing painting & Mouse événement scintillement

Mes problèmes sont tels quels; 1) Sur les événements souris, je reçois une quantité massive de scintillement et des baisses de performance 2) Si je remplace les méthodes paint ou paintComponent avec ma propre méthode de peinture, ce qui est souhaitable pour se débarrasser de scintillement et d'autres problèmes de peinture, je obtiennent toujours le même effet de scintillement et les mêmes graphiques tirés d'images chargées qui ont une zone transparente puis colorent cette zone en noir. Lorsque j'appelle ma méthode de peinture manuellement sans surcharger les méthodes paint et paintComponent, je reçois toujours des scintillements mais les zones transparentes s'affichent correctement.

Je suis nouveau à la peinture Swing et évidemment faire quelque chose de mal, quelqu'un pourrait me pointer dans la bonne direction pour résoudre ce problème?

Merci

import jSim.simulation.Simulation; 
    import java.awt.Color; 
    import java.awt.Cursor; 
    import java.awt.Dimension; 
    import java.awt.Graphics; 
    import java.awt.Graphics2D; 
    import java.awt.Image; 
    import java.awt.Point; 
    import java.awt.Rectangle; 
    import java.awt.Toolkit; 
    import java.awt.event.MouseEvent; 
    import java.awt.event.MouseWheelEvent; 
    import java.awt.event.MouseWheelListener; 
    import java.awt.image.BufferStrategy; 
    import java.awt.image.BufferedImage; 
    import javax.swing.JPanel; 
    import javax.swing.JViewport; 
    import javax.swing.event.MouseInputListener; 

    public class SimPanel extends JPanel implements MouseWheelListener, MouseInputListener { 
     //Simulation 

     Simulation sim; 
     //Viewer 
     JViewport viewport; 
     Dimension viewSize; 
     BufferStrategy strat; 
     //Drawing 
     Image renderImage; 
     Graphics2D g2d; 
     boolean draw = true; 
     double scale = 1.0; 
     Object drawLock = new Object(); 
     //Mouse events 
     int m_XDifference, m_YDifference; 

     public SimPanel(JViewport viewport) { 
      this.viewport = viewport; 
      this.addMouseListener(this); 
      this.addMouseMotionListener(this); 
      this.addMouseWheelListener(this); 

      //this.setup(); 
     } 

     public SimPanel(Simulation sim, JViewport viewport) { 
      this.sim = sim; 
      this.viewport = viewport; 
      this.addMouseListener(this); 
      this.addMouseMotionListener(this); 
      this.addMouseWheelListener(this); 
      //this.setup(); 
     } 

     //Used to initialise the buffered image once drawing begins 
     private void setup() { 
      synchronized (drawLock) { 
       viewSize = viewport.getExtentSize(); 
       renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB); 
       g2d = (Graphics2D) renderImage.getGraphics(); 
      } 
     } 

    // @Override 
    // public void paint(Graphics g) 
    // { 
    //  synchronized(drawLock) { 
    //  //super.paintComponent(g); 
    //  paintSimulation(); 
    //  } 
    // } 
     //Paint the screen for a specific simulation 
     public void paintSimulation(Simulation sim) { 
      synchronized (drawLock) { 
       setSimulation(sim); 
       paintSimulation(); 
      } 
     } 

     //Paint the screen with the panels simulation 
     public void paintSimulation() { 
      synchronized (drawLock) { 
       //if no image, then init 
       if (renderImage == null) { 
        setup(); 
       } 
       //clear the screen 
       resetScreen(); 
       //draw the simulation if not null, to the image 
       if (sim != null) { 
        sim.draw(this); 
       } 
       //paint the screen with the image 
       paintScreen(); 
      } 
     } 

     private void resetScreen() { 
      Dimension newSize = viewport.getExtentSize(); 
      if (viewSize.height != newSize.height || viewSize.width != newSize.width || renderImage == null) { 
       //System.out.println("Screen Size Changed: " + viewSize + " " + newSize); 
       viewSize = newSize; 
       renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB); 
       g2d = (Graphics2D) renderImage.getGraphics(); 
      } else { 
       g2d.setBackground(Color.DARK_GRAY); 
       g2d.clearRect(0, 0, (int) (viewSize.width), (int) (viewSize.height)); 
      } 
     } 

     private void paintScreen() { 
      Graphics g; 
      Graphics2D g2; 
      try { 
       //g = viewport.getGraphics(); 
       g = this.getGraphics(); 
       g2 = (Graphics2D) g; 
       if ((g != null) && (renderImage != null)) { 
        g2.drawImage(renderImage, (int) viewport.getViewPosition().getX(), (int) viewport.getViewPosition().getY(), null); 
       } 
       Toolkit.getDefaultToolkit().sync(); // sync the display on some systems 
       g.dispose(); 
       g2.dispose(); 
       this.revalidate(); 
      } catch (Exception e) { 
       System.out.println("Graphics context error: " + e); 
      } 
     } 

     //Simulation makes calls to this method to draw items on the image 
     public void draw(BufferedImage image, int x, int y, Color colour) { 
      synchronized (drawLock) { 
       Rectangle r = viewport.getViewRect(); 
       if (g2d != null && draw) { 
        Point p = new Point((int) (x * scale), (int) (y * scale)); 
        if (r.contains(p)) { 
         if (scale < 1) { 
          Graphics2D g2 = (Graphics2D) image.getGraphics(); 
          Image test = image.getScaledInstance((int) (image.getWidth(null) * scale), (int) (image.getHeight(null) * scale), Image.SCALE_FAST); 
          g2d.drawImage(test, (int) ((x * scale - r.x)), (int) ((y * scale - r.y)), null); 
         } else { 
          g2d.drawImage(image, x - r.x, y - r.y, null); 
         } 
        } 
       } 
      } 
     } 

     public void setDraw(boolean draw) { 
      this.draw = draw; 
     } 

     public void setSimulation(Simulation sim) { 
      synchronized (drawLock) { 
       if (!(this.sim == sim)) { 
        this.sim = sim; 
       } 
      } 
     } 

     public void mouseWheelMoved(MouseWheelEvent e) { 
      synchronized (drawLock) { 
       updatePreferredSize(e.getWheelRotation(), e.getPoint()); 
      } 
     } 

     private void updatePreferredSize(int wheelRotation, Point stablePoint) { 
      double scaleFactor = findScaleFactor(wheelRotation); 
      if (scale * scaleFactor < 1 && scale * scaleFactor > 0.05) { 
       scaleBy(scaleFactor); 
       Point offset = findOffset(stablePoint, scaleFactor); 
       offsetBy(offset); 
       this.getParent().doLayout(); 
      } 
     } 

     private double findScaleFactor(int wheelRotation) { 
      double d = wheelRotation * 1.08; 
      return (d > 0) ? 1/d : -d; 
     } 

     private void scaleBy(double scaleFactor) { 
      int w = (int) (this.getWidth() * scaleFactor); 
      int h = (int) (this.getHeight() * scaleFactor); 
      this.setPreferredSize(new Dimension(w, h)); 
      this.scale = this.scale * scaleFactor; 
     } 

     private Point findOffset(Point stablePoint, double scaleFactor) { 
      int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x; 
      int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y; 
      return new Point(x, y); 
     } 

     private void offsetBy(Point offset) { 
      Point location = viewport.getViewPosition(); 
      //this.setLocation(location.x - offset.x, location.y - offset.y); 
      viewport.setViewPosition(new Point(location.x + offset.x, location.y + offset.y)); 
     } 

     public void mouseDragged(MouseEvent e) { 
      synchronized (drawLock) { 
       //Point p = this.getLocation(); 
       Point p = viewport.getViewPosition(); 
       int newX = p.x - (e.getX() - m_XDifference); 
       int newY = p.y - (e.getY() - m_YDifference); 
       //this.setLocation(newX, newY); 
       viewport.setViewPosition(new Point(newX, newY)); 
       //this.getParent().doLayout(); 
      } 
     } 

     public void mousePressed(MouseEvent e) { 
      synchronized (drawLock) { 
       setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 
       m_XDifference = e.getX(); 
       m_YDifference = e.getY(); 
      } 
     } 

     public void mouseReleased(MouseEvent e) { 
      synchronized (drawLock) { 
       setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 
      } 
     } 

     public void mouseClicked(MouseEvent e) { 
      //throw new UnsupportedOperationException("Not supported yet."); 
     } 

     public void mouseEntered(MouseEvent e) { 
      //throw new UnsupportedOperationException("Not supported yet."); 
     } 

     public void mouseExited(MouseEvent e) { 
      //throw new UnsupportedOperationException("Not supported yet."); 
     } 

     public void mouseMoved(MouseEvent e) { 
      //throw new UnsupportedOperationException("Not supported yet."); 
     } 
    } 
+0

Pourquoi synchroniser toutes vos méthodes? J'ai travaillé avec Swing pendant des années et je n'ai jamais eu besoin de faire ça. – Paul

+0

Désolé j'ai ajouté ceux juste pour voir si cela a fait une différence après avoir essayé beaucoup d'autres choses et j'ai oublié de les sortir. Je voulais juste vérifier qu'il ne se passait rien d'idiot, comme redimensionner le tableau au milieu du tableau ou similaire. – James

+0

La peinture Swing se déroule sur le fil EDT - Envoi d'événement. L'événement "redimensionner le panneau" ne sera pas traité tant que la peinture n'aura pas été terminée, car ils sont tous deux traités sur le même fil. C'est pourquoi, si un événement entraîne un long processus, vous devez exécuter ce processus dans un autre thread afin que votre interface utilisateur Swing reste sensible et se repeigne. – Paul

Répondre

1

En bref, recherchez le double tampon.

La réponse plus ...

Remplacer paintComponent. Créez un objet graphique hors écran. Faites votre peinture sur cet objet. Copiez cela sur les objets graphiques transmis dans la méthode paint.

Oh, et se débarrasser de toute la synchronisation. Vous n'en avez pas besoin.

+0

C'est ce que je fais déjà, n'est-ce pas? Je dessine sur une image, puis toute ma méthode de peinture dessine le contenu de cette image sur l'objet graphique JPanels. – James

+0

Je me suis trompé ci-dessus ... outrepassement paintComponent. Remplacer également la mise à jour comme suit: 'mise à jour publique du vide (Graphics g) {paint (g); Update ' La mise à jour efface le panneau par défaut - l'appel de peinture empêchera cela. – Paul

+0

Merci encore pour le commentaire mais j'ai déjà essayé le ci-dessus en vain. D'autres idées? Ça m'a rendu fou ça. J'ai besoin d'en savoir plus sur Swing et la peinture. – James

0

Si vous ajoutez un setOpaque (true) sur la fenêtre que vous informiez swing qui vous allez faire toute la peinture (en particulier l'arrière-plan) vous-même. Cela peut déjà aider un peu.

EDIT

Je l'ai regardé autour d'un peu plus, et pense que vous devriez remplacer paintComponent.

Vous pourriez avoir 2 images, et quatre références:

  • imageToPaint = null
  • imageToWriteTo = null
  • bufferImageOne initialisé à la taille appropriée BufferedImage
  • bufferImageTwo initialisé à taille appropriée BufferedImage

Vous devez remplacer paintComponent pour dessiner le backgro und et ensuite drawImage (imageToPaint) (si ce n'est pas null, ce qui ne devrait pas être)

Vous aurez un thread qui fait la peinture personnalisée de imageToWriteTo. À la fin, il échange imageToPaint et imageToWriteTo.

Ensuite, vous appelez repaint(). Cela demande un repeint, avec l'avantage supplémentaire que toutes les demandes de repeint sur la file d'attente Swing sont prises ensemble et aboutissent à une seule peinture. Pas de revalidation ou de synchronisation s'il vous plaît. Cette repeinte est automatiquement effectuée sur votre deuxième fil, le fil d'expédition d'événement.

De cette façon, la mise à jour de l'image de simulation est découplée de la peinture réelle, et les mises à jour de peinture ne doivent pas attendre que la simulation ait fini de dessiner. Il peut résulter d'images légèrement obsolètes (un peu implicite dans l'utilisation de la mise en mémoire tampon), mais il devrait donner de meilleurs résultats.

En bref, l'écriture coûteuse est faite à imageToWriteTo. La peinture est faite en utilisant imageToPaint.L'écriture chère se termine par l'échange d'imageToWriteTo et d'imageToPaint.

+0

Merci pour la réponse, j'ai déjà essayé mais ça n'a pas fait de différence. – James

+0

@James Mis à jour ma réponse un peu. – extraneon