2008-09-22 6 views
10

J'essaye d'implémenter le zoom sensible à la position à l'intérieur d'un JScrollPane. Le JScrollPane contient un composant avec un paint personnalisé qui va se dessiner à l'intérieur de l'espace qui lui est alloué - donc le zoom est aussi simple que d'utiliser un MouseWheelListener qui redimensionne le composant interne comme requis. Mais je veux aussi zoomer sur (ou sur) un point pour garder ce point aussi central que possible dans la vue agrandie (ou -out) qui en résulte (c'est ce que j'appelle "position-sensitive" zooming), similaire à la façon dont le zoom fonctionne dans google maps. Je suis sûr que cela a été fait à plusieurs reprises auparavant - quelqu'un sait-il la "bonne" façon de le faire sous Java Swing ?. Serait-il préférable de jouer avec les transformations de Graphic2D au lieu d'utiliser JScrollPanes?Comment implémenter un zoom sensible à la position dans un JScrollPane?

Exemple de code suivant:

package test; 

import java.awt.*; 
import java.awt.event.*; 
import java.awt.geom.*; 
import javax.swing.*; 

public class FPanel extends javax.swing.JPanel { 

private Dimension preferredSize = new Dimension(400, 400);  
private Rectangle2D[] rects = new Rectangle2D[50]; 

public static void main(String[] args) {   
    JFrame jf = new JFrame("test"); 
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    jf.setSize(400, 400); 
    jf.add(new JScrollPane(new FPanel())); 
    jf.setVisible(true); 
}  

public FPanel() { 
    // generate rectangles with pseudo-random coords 
    for (int i=0; i<rects.length; i++) { 
     rects[i] = new Rectangle2D.Double(
       Math.random()*.8, Math.random()*.8, 
       Math.random()*.2, Math.random()*.2); 
    } 
    // mouse listener to detect scrollwheel events 
    addMouseWheelListener(new MouseWheelListener() { 
     public void mouseWheelMoved(MouseWheelEvent e) { 
      updatePreferredSize(e.getWheelRotation(), e.getPoint()); 
     } 
    }); 
} 

private void updatePreferredSize(int n, Point p) { 
    double d = (double) n * 1.08; 
    d = (n > 0) ? 1/d : -d; 
    int w = (int) (getWidth() * d); 
    int h = (int) (getHeight() * d); 
    preferredSize.setSize(w, h); 
    getParent().doLayout(); 
    // Question: how do I keep 'p' centered in the resulting view? 
} 

public Dimension getPreferredSize() { 
    return preferredSize; 
} 

private Rectangle2D r = new Rectangle2D.Float(); 
public void paint(Graphics g) { 
    super.paint(g); 
    g.setColor(Color.red); 
    int w = getWidth(); 
    int h = getHeight(); 
    for (Rectangle2D rect : rects) { 
     r.setRect(rect.getX() * w, rect.getY() * h, 
       rect.getWidth() * w, rect.getHeight() * h); 
     ((Graphics2D)g).draw(r); 
    }  
    } 
} 
+0

prime Ajouté à voir si je peux obtenir une réponse complète (idéalement: l'extrait de code qui, lorsqu'il est ajouté ci-dessus, répond à la question). – tucuxi

Répondre

8

testé, semble fonctionner ...

private void updatePreferredSize(int n, Point p) { 
    double d = (double) n * 1.08; 
    d = (n > 0) ? 1/d : -d; 

    int w = (int) (getWidth() * d); 
    int h = (int) (getHeight() * d); 
    preferredSize.setSize(w, h); 

    int offX = (int)(p.x * d) - p.x; 
    int offY = (int)(p.y * d) - p.y; 
    setLocation(getLocation().x-offX,getLocation().y-offY); 

    getParent().doLayout(); 
} 

Mise à jour

Voici une explication: le point p est l'emplacement de la souris par rapport au FPanel. Puisque vous mettez à l'échelle la taille du panneau, l'emplacement de p (par rapport à la taille du panneau) sera mis à l'échelle du même facteur. En soustrayant l'emplacement actuel de l'emplacement mis à l'échelle, vous obtenez combien le point «décale» lorsque le panneau est redimensionné. Ensuite, il suffit de déplacer l'emplacement du panneau dans le volet de défilement de la même valeur dans la direction opposée pour remettre p sous le curseur de la souris.

+0

Exactement ce que je cherchais. Merci! – tucuxi

1

Votre MouseWheelListener a également pour localiser le curseur, le déplacer vers le centre de la JScrollPane et régler le xmin/ymin et Xmax/ymax du contenu à afficher.

+0

Oui, c'est vrai - c'est aussi ce que j'essayais de faire en premier lieu (le curseur est situé à 'p', et ajuster ces limites correctement est ce que je ne sais pas comment faire). – tucuxi

1

Je pense smt comme cela devrait fonctionner ...


private void updatePreferredSize(int n, Point p) { 
    double d = (double) n * 1.08; 
    d = (n > 0) ? 1/d : -d; 
    int w = (int) (getWidth() * d); 
    int h = (int) (getHeight() * d); 
    preferredSize.setSize(w, h); 

    // Question: how do I keep 'p' centered in the resulting view? 

    int parentWdt = this.getParent().getWidth() ; 
    int parentHgt = this.getParent().getHeight() ; 

    int newLeft = p.getLocation().x - (p.x - (parentWdt/2)) ; 
    int newTop = p.getLocation().y - (p.y - (parentHgt/2)) ; 
    this.setLocation(newLeft, newTop) ; 

    getParent().doLayout(); 
} 

EDIT: changé deux choses.

+0

Votre code ne conserve pas le point 'p' centré dans la vue résultante (par exemple, essayez de zoomer près du coin inférieur droit), même après avoir changé 'int newTop = py - w/2;' à 'int newTop = py - h/2; Veuillez tester le code avant de le proposer comme réponse. – tucuxi

+0

Le code mis à jour semble zoomer dans le coin en haut à gauche (java 6 sur XP), et non pas là où pointe le pointeur de la souris. J'utilise le même programme de test que celui mentionné dans la question. – tucuxi

2

est ici un refactoring mineur de la solution de @ Kevin K:

private void updatePreferredSize(int wheelRotation, Point stablePoint) { 
    double scaleFactor = findScaleFactor(wheelRotation); 
    scaleBy(scaleFactor); 
    Point offset = findOffset(stablePoint, scaleFactor); 
    offsetBy(offset); 
    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) (getWidth() * scaleFactor); 
    int h = (int) (getHeight() * scaleFactor); 
    preferredSize.setSize(w, h); 
} 

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 = getLocation(); 
    setLocation(location.x - offset.x, location.y - offset.y); 
} 
Questions connexes