2016-02-06 7 views
0

Я пробовал этот пример, приведенный в another post, чтобы узнать о масштабировании и панорамировании относительно указателя мыши. Когда все находится на сетке, масштабирование работает так, как ожидалось:JavaFx масштабирование на мышь как точка поворота

При масштабировании в положение указателя мыши в верхнем левом изображении оно масштабируется в точном месте, как показано в верхнем правом изображении.

Если что-то вытащили из сетки, например. ось начинает «неправильно»:

При приближении к указателю мыши на нижнем левом изображении оно масштабируется в другое место, отличное от того, которое предназначено, как показано в нижнем правом изображении.

Границы холста внутри родителя изменяются от 600x600 (без масштаба) до примерно 600x700 ... Что влияет на результаты dx, dy следующей функции.

double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX())); 
double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY())); 

При редактировании этой функции путем изменения .getWidth() к .getHeight(), а затем снова переместить прямоугольник из правой ... трансфокатор работает правильно. Однако, если прямоугольник перемещается вертикально (снизу или сверху), а влево проблема снова воспроизводится снова.

Является ли вышеуказанная функция правильной, что она пытается сделать? Почему зум не работает так же, как когда все было на сетке?

enter image description here

import javafx.application.Application; 
import javafx.beans.property.DoubleProperty; 
import javafx.beans.property.SimpleDoubleProperty; 
import javafx.event.EventHandler; 
import javafx.scene.Group; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.canvas.Canvas; 
import javafx.scene.canvas.GraphicsContext; 
import javafx.scene.control.Label; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.input.ScrollEvent; 
import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.scene.shape.Rectangle; 
import javafx.stage.Stage; 

class PannableCanvas extends Pane { 

    DoubleProperty myScale = new SimpleDoubleProperty(1.0); 

    public PannableCanvas() { 
     setPrefSize(600, 600); 
     setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;"); 

     // add scale transform 
     scaleXProperty().bind(myScale); 
     scaleYProperty().bind(myScale); 
    } 

    /** 
    * Add a grid to the canvas, send it to back 
    */ 
    public void addGrid() { 

     double w = getBoundsInLocal().getWidth(); 
     double h = getBoundsInLocal().getHeight(); 

     // add grid 
     Canvas grid = new Canvas(w, h); 

     // don't catch mouse events 
     grid.setMouseTransparent(true); 

     GraphicsContext gc = grid.getGraphicsContext2D(); 

     gc.setStroke(Color.GRAY); 
     gc.setLineWidth(1); 

     // draw grid lines 
     double offset = 50; 
     for(double i=offset; i < w; i+=offset) { 
      gc.strokeLine(i, 0, i, h); 
      gc.strokeLine(0, i, w, i); 
     } 

     getChildren().add(grid); 

     grid.toBack(); 
    } 

    public double getScale() { 
     return myScale.get(); 
    } 

    public void setScale(double scale) { 
     myScale.set(scale); 
    } 

    public void setPivot(double x, double y) { 
     setTranslateX(getTranslateX()-x); 
     setTranslateY(getTranslateY()-y); 
    } 
} 


/** 
* Mouse drag context used for scene and nodes. 
*/ 
class DragContext { 

    double mouseAnchorX; 
    double mouseAnchorY; 

    double translateAnchorX; 
    double translateAnchorY; 

} 

/** 
* Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed. 
*/ 
class NodeGestures { 

    private DragContext nodeDragContext = new DragContext(); 

    PannableCanvas canvas; 

    public NodeGestures(PannableCanvas canvas) { 
     this.canvas = canvas; 

    } 

    public EventHandler<MouseEvent> getOnMousePressedEventHandler() { 
     return onMousePressedEventHandler; 
    } 

    public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() { 
     return onMouseDraggedEventHandler; 
    } 

    private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() { 

     public void handle(MouseEvent event) { 

      // left mouse button => dragging 
      if(!event.isPrimaryButtonDown()) 
       return; 

      nodeDragContext.mouseAnchorX = event.getSceneX(); 
      nodeDragContext.mouseAnchorY = event.getSceneY(); 

      Node node = (Node) event.getSource(); 

      nodeDragContext.translateAnchorX = node.getTranslateX(); 
      nodeDragContext.translateAnchorY = node.getTranslateY(); 

     } 

    }; 

    private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() { 
     public void handle(MouseEvent event) { 

      // left mouse button => dragging 
      if(!event.isPrimaryButtonDown()) 
       return; 

      double scale = canvas.getScale(); 

      Node node = (Node) event.getSource(); 

      node.setTranslateX(nodeDragContext.translateAnchorX + ((event.getSceneX() - nodeDragContext.mouseAnchorX)/scale)); 
      node.setTranslateY(nodeDragContext.translateAnchorY + ((event.getSceneY() - nodeDragContext.mouseAnchorY)/scale)); 

      event.consume(); 

     } 
    }; 
} 

/** 
* Listeners for making the scene's canvas draggable and zoomable 
*/ 
class SceneGestures { 

    private static final double MAX_SCALE = 10.0d; 
    private static final double MIN_SCALE = .1d; 

    private DragContext sceneDragContext = new DragContext(); 

    PannableCanvas canvas; 

    public SceneGestures(PannableCanvas canvas) { 
     this.canvas = canvas; 
    } 

    public EventHandler<MouseEvent> getOnMousePressedEventHandler() { 
     return onMousePressedEventHandler; 
    } 

    public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() { 
     return onMouseDraggedEventHandler; 
    } 

    public EventHandler<ScrollEvent> getOnScrollEventHandler() { 
     return onScrollEventHandler; 
    } 

    private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() { 

     public void handle(MouseEvent event) { 

      // right mouse button => panning 
      if(!event.isSecondaryButtonDown()) 
       return; 

      sceneDragContext.mouseAnchorX = event.getSceneX(); 
      sceneDragContext.mouseAnchorY = event.getSceneY(); 

      sceneDragContext.translateAnchorX = canvas.getTranslateX(); 
      sceneDragContext.translateAnchorY = canvas.getTranslateY(); 

     } 

    }; 

    private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() { 
     public void handle(MouseEvent event) { 

      // right mouse button => panning 
      if(!event.isSecondaryButtonDown()) 
       return; 

      canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX); 
      canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY); 

      event.consume(); 
     } 
    }; 

    /** 
    * Mouse wheel handler: zoom to pivot point 
    */ 
    private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() { 

     @Override 
     public void handle(ScrollEvent event) { 

      double delta = 1.2; 

      double scale = canvas.getScale(); // currently we only use Y, same value is used for X 
      double oldScale = scale; 

      if (event.getDeltaY() < 0) 
       scale /= delta; 
      else 
       scale *= delta; 

      scale = clamp(scale, MIN_SCALE, MAX_SCALE); 

      double f = (scale/oldScale)-1; 

      double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX())); 
      double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY())); 

      canvas.setScale(scale); 

      // note: pivot value must be untransformed, i. e. without scaling 
      canvas.setPivot(f*dx, f*dy); 

      event.consume(); 

     } 

    }; 


    public static double clamp(double value, double min, double max) { 

     if(Double.compare(value, min) < 0) 
      return min; 

     if(Double.compare(value, max) > 0) 
      return max; 

     return value; 
    } 
} 



/** 
* An application with a zoomable and pannable canvas. 
*/ 
public class ZoomAndScrollApplication extends Application { 
    public static void main(String[] args) { 
     launch(args); 
    } 

    @Override 
    public void start(Stage stage) { 

     Group group = new Group(); 

     // create canvas 
     PannableCanvas canvas = new PannableCanvas(); 

     // we don't want the canvas on the top/left in this example => just 
     // translate it a bit 
     canvas.setTranslateX(100); 
     canvas.setTranslateY(100); 

     // create sample nodes which can be dragged 
     NodeGestures nodeGestures = new NodeGestures(canvas); 

     Label label1 = new Label("Draggable node 1"); 
     label1.setTranslateX(10); 
     label1.setTranslateY(10); 
     label1.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); 
     label1.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); 

     Label label2 = new Label("Draggable node 2"); 
     label2.setTranslateX(100); 
     label2.setTranslateY(100); 
     label2.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); 
     label2.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); 

     Label label3 = new Label("Draggable node 3"); 
     label3.setTranslateX(200); 
     label3.setTranslateY(200); 
     label3.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); 
     label3.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); 

     Circle circle1 = new Circle(300, 300, 50); 
     circle1.setStroke(Color.ORANGE); 
     circle1.setFill(Color.ORANGE.deriveColor(1, 1, 1, 0.5)); 
     circle1.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); 
     circle1.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); 

     Rectangle rect1 = new Rectangle(100,100); 
     rect1.setTranslateX(450); 
     rect1.setTranslateY(450); 
     rect1.setStroke(Color.BLUE); 
     rect1.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.5)); 
     rect1.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); 
     rect1.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); 

     canvas.getChildren().addAll(label1, label2, label3, circle1, rect1); 

     group.getChildren().add(canvas); 

     // create scene which can be dragged and zoomed 
     Scene scene = new Scene(group, 1024, 768); 

     SceneGestures sceneGestures = new SceneGestures(canvas); 
     scene.addEventFilter(MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler()); 
     scene.addEventFilter(MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler()); 
     scene.addEventFilter(ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler()); 

     stage.setScene(scene); 
     stage.show(); 

     canvas.addGrid(); 

    } 
} 
+0

Проблема в том, что [масштабирование происходит в центре узла] (https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#scaleXProperty). Когда вы перетаскиваете прямоугольник снаружи, изменяется ширина родительского элемента, а также масштаб. – Roland

+0

Немного взлома, но я создал новую панель с тем же размером, что и Canvas, и добавил ее к конструктору Canvas. Затем я заменил границы Canvas в функции на функцию панели ... и затем она работает, потому что она не изменяет размер. Спасибо за информацию. – Josephus87

ответ

0

Поскольку никто не ответил на вопрос, до сих пор, и я наткнулся на такую ​​же проблему, я вывешу мое решение, которое добавляет простой расчет влево/вверх/вниз и вправо свеса узлы.

Если вы замените часть кода масштабирования на часть, приведенную ниже, вы должны быть добры.

//maxX = right overhang, maxY = lower overhang 
double maxX = canvas.getBoundsInParent().getMaxX() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getX(); 
double maxY = canvas.getBoundsInParent().getMaxY() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getY(); 

// minX = left overhang, minY = upper overhang 
double minX = canvas.localToParent(0,0).getX() - canvas.getBoundsInParent().getMinX(); 
double minY = canvas.localToParent(0,0).getY() - canvas.getBoundsInParent().getMinY(); 

// adding the overhangs together, as we only consider the width of canvas itself 
double subX = maxX + minX; 
double subY = maxY + minY; 

// subtracting the overall overhang from the width and only the left and upper overhang from the upper left point 
double dx = (event.getSceneX() - ((canvas.getBoundsInParent().getWidth()-subX)/2 + (canvas.getBoundsInParent().getMinX()+minX))); 
double dy = (event.getSceneY() - ((canvas.getBoundsInParent().getHeight()-subY)/2 + (canvas.getBoundsInParent().getMinY()+minY))); 

ВНИМАНИЕ: Левая и до свеса всегда будет вычислена правильно, но я не нашел рабочий путь, чтобы вычислить право и нижний свес узлов без использования предпочтительной высоты и ширины атрибутов. Поэтому имейте в виду, что вам это нужно.

Кроме того, вы можете улучшить производительность, только вычислив вещь canvas.getBoundsInParent() только один раз, а также другие вычисления, которые вычисляются несколько раз.

Надеюсь, это поможет кому-то.

Смежные вопросы