JavaFx缩放到鼠标作为枢轴 [英] JavaFx zooming to mouse as pivot

查看:221
本文介绍了JavaFx缩放到鼠标作为枢轴的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在

  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;

类PannableCanvas扩展窗格{

DoubleProperty myScale = new SimpleDoubleProperty(1.0);

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

//添加比例变换
scaleXProperty()。bind(myScale);
scaleYProperty()。bind(myScale);
}

/ **
*在画布上添加网格,将其发送回
* /
public void addGrid(){

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

//添加网格
Canvas grid = new Canvas(w,h);

//不捕捉鼠标事件
grid.setMouseTransparent(true);

GraphicsContext gc = grid.getGraphicsContext2D();

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

//绘制网格线
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);
}
}


/ **
*用于场景和节点的鼠标拖动上下文。
* /
class DragContext {

double mouseAnchorX;
double mouseAnchorY;

double translateAnchorX;
double translateAnchorY;

}

/ **
*通过鼠标左键使节点可拖动的监听器。考虑父母是否被缩放。
* /
类NodeGestures {

private DragContext nodeDragContext = new DragContext();

PannableCanvas画布;

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

}

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

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

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

public void handle(MouseEvent event){

//鼠标左键=>拖动
if(!event.isPrimaryButtonDown())
return;

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

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

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

}

};

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

//鼠标左键=>拖动
if(!event.isPrimaryButtonDown())
return;

double scale = canvas.getScale();

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

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

event.consume();

}
};
}

/ **
*使场景的画布可拖动和可缩放的听众
* /
class SceneGestures {

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

private DragContext sceneDragContext = new DragContext();

PannableCanvas画布;

public Sc​​eneGestures(PannableCanvas canvas){
this.canvas = canvas;
}

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

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

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

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

public void handle(MouseEvent event){

//鼠标右键=>平移
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){

//鼠标右键=>平移
if(!event.isSecondaryButtonDown())
return;

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

event.consume();
}
};

/ **
*鼠标滚轮处理程序:缩放到数据点
* /
private EventHandler< ScrollEvent> onScrollEventHandler = new EventHandler< ScrollEvent>(){

@Override
public void handle(ScrollEvent event){

double delta = 1.2;

double scale = canvas.getScale(); //目前我们只使用Y,相同的值用于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);

//注意:枢轴值必须是未转换的,i。即不缩放
canvas.setPivot(f * dx,f * dy);

event.consume();

}

};


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

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

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

返回值;
}
}



/ **
*具有可缩放和可平移画布的应用程序。
* /
公共类ZoomAndScrollApplication扩展Application {
public static void main(String [] args){
launch(args);
}

@Override
public void start(阶段阶段){

Group group = new Group();

//创建画布
PannableCanvas canvas = new PannableCanvas();

//我们不希望在此示例中顶部/左侧的画布=>只需
//翻译一下
canvas.setTranslateX(100);
canvas.setTranslateY(100);

//创建可以拖动的示例节点
NodeGestures nodeGestures = new NodeGestures(canvas);

标签label1 =新标签(可拖动节点1);
label1.setTranslateX(10);
label1.setTranslateY(10);
label1.addEventFilter(MouseEvent.MOUSE_PRESSED,nodeGestures.getOnMousePressedEventHandler());
label1.addEventFilter(MouseEvent.MOUSE_DRAGGED,nodeGestures.getOnMouseDraggedEventHandler());

标签label2 =新标签(可拖动节点2);
label2.setTranslateX(100);
label2.setTranslateY(100);
label2.addEventFilter(MouseEvent.MOUSE_PRESSED,nodeGestures.getOnMousePressedEventHandler());
label2.addEventFilter(MouseEvent.MOUSE_DRAGGED,nodeGestures.getOnMouseDraggedEventHandler());

标签label3 =新标签(可拖动节点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());

矩形rect1 =新矩形(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);

//创建可以拖放的场景
场景场景=新场景(组,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(场景);
stage.show();

canvas.addGrid();

}
}


解决方案

由于直到现在还没有人回答这个问题,我偶然发现了同样的问题,我将发布我的解决方案,它会添加一个简单的计算左/上/下和右悬的节点。



如果用下面附带的部分替换缩放代码的部分,你应该很高兴。

  // maxX =右突出,maxY =低突出
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 =左悬,minY =上悬'
double minX = canvas.localToParent(0,0).getX() - canvas.getBoundsInParent()。getMinX();
double minY = canvas.localToParent(0,0).getY() - canvas.getBoundsInParent()。getMinY();

//将悬垂加在一起,因为我们只考虑画布本身的宽度
double subX = maxX + minX;
double subY = maxY + minY;

//从宽度减去整体悬垂,只从左上角开始减去左上角和上悬点
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()事物来提高性能。多次计算的其他计算。



希望它有所帮助。


I have tried the this example given in another post to learn about zooming and panning relative to the mouse pointer. When everything is on the grid, zooming works as expected:

When zooming into the mouse pointer location on the top left image, it is zoomed into the exact location as seen in the top right image.

If something is dragged off the grid, e.g. the pivot starts to 'misbehave':

When zooming into the mouse pointer location on the bottom left image, it is zoomed into a location other than the one intended, seen in the bottom right image.

The bounds of the canvas inside the parent changes from 600x600 (without scale) to something like 600x700… Which affects the outcomes dx, dy of the following function.

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

When editing this function by changing .getWidth() to .getHeight() and then again move the rectangle out right… the zoom works correctly. However, if the rectangle is moved out vertically (to the bottom or top) and to the left the problem again is reproduced again.

Is the above function correct, what is it trying to do? Why does the zoom not work the same, as when everything was on the grid?

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();

    }
}

解决方案

As nobody answered the question up until now and I stumbled over the same problem, I will post my solution, which adds a simple calculation of the left/up/lower and right overhang of nodes.

If you replace the part of the zooming-code with the part attached below, you should be good to got.

//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)));

WARNING: The left and up overhang will always be computed correctly, but I did not find any working way, to compute the right and lower overhang of nodes without the use of the preferred height and width attributes. So keep in mind, that you need these.

Also, you can improve the performance by only computing the canvas.getBoundsInParent() thing only once before as well as the the other calculations that are computed multiple times.

Hope it helps someone.

这篇关于JavaFx缩放到鼠标作为枢轴的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆