2017-01-09 23 views
0

我有一個在至少三個繪製樣條曲線的節點之間插入的GUI。在窗格上單擊鼠標左鍵即可在單擊位置設置新的(可拖動)節點。如果至少存在三個節點,則按下按鈕「繪製樣條線」將在節點之間繪製插值樣條曲線。除此之外,一切正常,當其中一個節點被向上或向下拖動時,Spline不會動態地重新繪製。相反,在我移動一個節點後,我需要按下「繪製樣條曲線」按鈕,並出現新的樣條曲線。但是我想看到一個節點立即轉移到其位置的效果,並不總是按下按鈕。我在NetBeans 8.1中開發了這個JavaFX項目。JavaFX:在窗格上拖動節點時動態更新(重新計算+重繪)折線

這裏是我的代碼:

Main.java

package InterpolationMinimal; 

import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Scene; 
import javafx.scene.layout.AnchorPane; 
import javafx.stage.Stage; 

public class Main extends Application { 

    public static void main(String[] args) { 
     launch(args); 
    } 

    @Override 
    public void start(Stage stage) throws Exception { 
     AnchorPane root = FXMLLoader.load(getClass().getResource("gui.fxml")); 
     Scene scene = new Scene(root); 

     stage.setTitle("Interpolation using cubic splines"); 
     stage.setScene(scene); 
     stage.show(); 

     // this is needed to resize object if window/scene is resized 
     root.prefWidthProperty().bind(scene.widthProperty()); 
     root.prefHeightProperty().bind(scene.heightProperty()); 
    } 
} 

Controller.java

package InterpolationMinimal; 

//import DraggableNodesGraph.MyAlerts; 
import java.net.URL; 
import java.util.ResourceBundle; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.event.ActionEvent; 
import javafx.fxml.FXML; 
import javafx.fxml.Initializable; 
import javafx.scene.control.ChoiceBox; 
import javafx.scene.control.Separator; 
import javafx.scene.layout.AnchorPane; 
import javafx.scene.layout.Pane; 

public class Controller implements Initializable { 

    @FXML private AnchorPane anchorPane; 
    @FXML private Pane pane; 
    @FXML private Separator separator; 
    @FXML private ChoiceBox choseBoundaryConditions; 
    @FXML private ChoiceBox choseSolverTechnique; 

    private ObservableList boundaryConditionsToChose; //!< List of boundary condtions type 
    private ObservableList solvingTechniqueToChose; //!< List of techniques for solving a linear system of equations 
    public static Spline mySpline = new Spline(); 

    @Override 
    public void initialize(URL url, ResourceBundle rb) { 
     // Set choices, default and event handling for the boundary conditions ChoiceBox 
     boundaryConditionsToChose = FXCollections.observableArrayList(); 
     boundaryConditionsToChose.add("Natural"); 
     boundaryConditionsToChose.add("Periodic"); 
     choseBoundaryConditions.setItems(boundaryConditionsToChose); 
     choseBoundaryConditions.setValue(boundaryConditionsToChose.get(0)); 

     // Set choices, default and event handling for the type of solving technique ChoiceBox 
     solvingTechniqueToChose = FXCollections.observableArrayList(); 
     solvingTechniqueToChose.add("Jacobi"); 
     solvingTechniqueToChose.add("Gauss-Seidel"); 
     choseSolverTechnique.setItems(solvingTechniqueToChose); 
     choseSolverTechnique.setValue(solvingTechniqueToChose.get(0)); 

     // Add listeners to the window size and redraw in case the window's size is changed. 
     // Subtract canvas.layout* to make sure the center of drawing is in the center 
     // of the canvas and not in the center of the whole window. 
     anchorPane.prefWidthProperty().addListener((ov, oldValue, newValue) -> { 
      pane.setPrefWidth(newValue.doubleValue() - pane.layoutXProperty().doubleValue()); 
      rescaleObjects('x', oldValue.doubleValue(), newValue.doubleValue()); 
     }); 
     anchorPane.prefHeightProperty().addListener((ov, oldValue, newValue) -> { 
      pane.setPrefHeight(newValue.doubleValue() - pane.layoutYProperty().doubleValue()); 
      separator.setPrefHeight(pane.getPrefHeight()); 
      rescaleObjects('y', oldValue.doubleValue(), newValue.doubleValue()); 
     }); 

     MyMouseEvents.paneMouseEvents(pane); 
    } 

    @FXML public void handleButtonDrawSpline() { 
     if (mySpline.getNodeList().size() >= 3) { 
      mySpline.calculate(String.valueOf(choseBoundaryConditions.getValue()), 
       String.valueOf(choseSolverTechnique.getValue())); 
      mySpline.draw(pane); 
     } /*else { 
      MyAlerts.displayAlert("You need at least 3 points for the interpolation!"); 
     }*/ 
    } 

    @FXML private void handleButtonClearCanvas(ActionEvent event) { 
     pane.getChildren().clear(); 
     mySpline.getNodeList().clear(); 
    } 

    private void rescaleObjects (char xOrY, double oldVal, double newVal) { 
     double scaleFactor = newVal/oldVal; 
     for (Node node: mySpline.getNodeList()) { 
      if (xOrY == 'x') { 
       node.setX(node.getX()*scaleFactor); 
       node.relocate(node.getX(), node.getY()); 
      } else if (xOrY == 'y') { 
       node.setY(node.getY()*scaleFactor); 
       node.relocate(node.getX(), node.getY()); 
      } 
     } 
    } 
} 

gui.fxml

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.scene.canvas.*?> 
<?import javafx.geometry.*?> 
<?import javafx.scene.text.*?> 
<?import java.lang.*?> 
<?import java.util.*?> 
<?import javafx.scene.*?> 
<?import javafx.scene.control.*?> 
<?import javafx.scene.layout.*?> 

<AnchorPane fx:id="anchorPane" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="InterpolationMinimal.Controller"> 
    <children> 
     <HBox alignment="CENTER_LEFT" layoutX="0.0" prefHeight="40.0" prefWidth="250.0"> 
     <children> 
      <ChoiceBox fx:id="choseBoundaryConditions" prefWidth="210.0"> 
       <HBox.margin> 
        <Insets left="10.0" /> 
       </HBox.margin> 
      </ChoiceBox> 
     </children> 
     </HBox> 
     <HBox alignment="CENTER_LEFT" layoutX="0.0" layoutY="40.0" prefHeight="40.0" prefWidth="250.0"> 
     <children> 
      <ChoiceBox fx:id="choseSolverTechnique" prefWidth="210.0"> 
       <HBox.margin> 
        <Insets left="10.0" /> 
       </HBox.margin> 
      </ChoiceBox> 
     </children> 
     </HBox> 
     <HBox alignment="CENTER_LEFT" layoutY="80.0" prefHeight="40.0" prefWidth="250.0"> 
     <children> 
        <HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="250.0"> 
        <children> 
         <Button fx:id="drawSpline" mnemonicParsing="false" onAction="#handleButtonDrawSpline" text="Draw Spline"> 
          <HBox.margin> 
           <Insets left="10.0" /> 
          </HBox.margin> 
          <font> 
           <Font size="15.0" /> 
          </font> 
         </Button> 
        </children> 
        </HBox> 
     </children> 
     </HBox> 
     <HBox alignment="CENTER_LEFT" layoutY="120.0" prefHeight="40.0" prefWidth="250.0"> 
     <children> 
        <HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="250.0"> 
        <children> 
         <Button fx:id="clearCanvas" mnemonicParsing="false" onAction="#handleButtonClearCanvas" text="Clear Canvas"> 
          <font> 
           <Font size="15.0" /> 
          </font> 
          <HBox.margin> 
           <Insets left="10.0" /> 
          </HBox.margin> 
         </Button> 
        </children> 
        </HBox> 
     </children> 
     </HBox> 
     <Pane fx:id="pane" layoutX="250.0" prefHeight="700.0" prefWidth="750.0"/> 
     <Separator fx:id="separator" layoutX="250.0" layoutY="0.0" orientation="VERTICAL" prefHeight="700.0" /> 
    </children> 
</AnchorPane> 

Node.java

package InterpolationMinimal; 

import javafx.scene.shape.Circle; 

public class Node extends Circle implements Comparable<Node> { 

    private double x; 
    private double y; 

    public Node() { 

    } 

    public Node (double x, double y) 
    { 
     this.x = x; 
     this.y = y; 
     // also initialize the Circle properties: 
     this.setCenterX(x); 
     this.setCenterY(y); 
    } 

    @Override 
    public int compareTo(Node n) { 
     return this.x<n.getX()?-1:this.x>n.getX()?1:0; 
    } 

    public double getX() 
    { 
     return this.x; 
    } 

    public double getY() 
    { 
     return this.y; 
    } 

    public void setX (double x) 
    { 
     this.x = x; 
    } 

    public void setY (double y) 
    { 
     this.y = y; 
    } 
} 

Spline.java

package InterpolationMinimal; 

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.Objects; 
import javafx.scene.shape.Polyline; 
import javafx.scene.layout.Pane; 

public class Spline { 

    private ArrayList<Node> nodeList = new ArrayList<>(); 
    private Polyline polyline = new Polyline(); 
    private double[][] coefficients; 

    public ArrayList<Node> getNodeList() 
    { 
     return this.nodeList; 
    } 

    public void calculate (String boundaryConditions, String solverTechnique) 
    { 
     int numberOfNodes = this.nodeList.size(); 
     int n = numberOfNodes - 1; // = number of splines 
     this.coefficients = new double[n][4]; 

     Collections.sort(this.nodeList); 

     // System of equations to solve: Mc = m 

     // Fill the matrix M: 
     double[][] M = new double[n-1][n-1]; 
     int i, j; 
     double h, hPlus1; 

     for (i=0; i<n-1; i++) { 
      j = i; 

      h = this.nodeList.get(i+1).getX() - this.nodeList.get(i).getX(); 
      hPlus1 = this.nodeList.get(i+2).getX() - this.nodeList.get(i+1).getX(); 

      M[i][j] = 2 * (h + hPlus1); // Diagonale der Matrix mit i=j 
      if (j > 0 ) M[i][j-1] = h; 
      if (j < n-2) M[i][j+1] = hPlus1; 
     } 

     // Fill m 
     double[] m = new double[n-1]; 
     for (i=0; i<n-1; i++) { 

      h  = this.nodeList.get(i+1).getX() - this.nodeList.get(i ).getX(); 
      hPlus1 = this.nodeList.get(i+2).getX() - this.nodeList.get(i+1).getX(); 

      m[i] = 3 * (this.nodeList.get(i+2).getY() - this.nodeList.get(i+1).getY())/hPlus1 - 
        3 * (this.nodeList.get(i+1).getY() - this.nodeList.get(i ).getY())/h; 

      // Use the boundary conditions 
      if (i==0) { 
       m[i] -= h * 0.0; 
      } else if (i==n-2) { 
       m[i] -= h * 0.0; 
      } 
     } 

     // Iterative Lösung für c 
     double[] cTemp = new double[n-1]; 
     double tolerance = 0.0001; 
     if (Objects.equals(solverTechnique,"Jacobi")) { 
      cTemp = jacobiVerfahren(M, m, tolerance); 
     } /*else if (Objects.equals(solverTechnique,"Gauss-Seidel")) { 
      cTemp = Matrix.gaussSeidelVerfahren(M, m, tolerance); 
     }*/ 

     double[] c = new double[n]; 
     c[0] = 0.0; // As in the left boundary condition 
     for (i=1; i<n; i++) { 
      c[i] = cTemp[i-1]; 
     } 

     // Determine the other three coefficients 
     double[] a = new double[n]; 
     double[] b = new double[n]; 
     double[] d = new double[n]; 
     for (i=0; i<n; i++) { 
      a[i] = this.nodeList.get(i).getY(); 
      h = this.nodeList.get(i+1).getX() - this.nodeList.get(i).getX(); 
      if (i < n-1) { 
       b[i] = ((this.nodeList.get(i+1).getY()-this.nodeList.get(i).getY())/h) - 
        h * (2*c[i] + c[i+1])/3.0; 
       d[i] = (c[i+1] - c[i])/(3.0 * h); 
      } else { 
       // c[i+1] = 0.0 wegen der natürlichen Randbedingungen 
       d[i] = (0.0 - c[i])/(3.0 * h); 
       b[i] = ((this.nodeList.get(i+1).getY()-this.nodeList.get(i).getY())/h) - 
        h * (2*c[i] + 0.0)/3.0; 
      } 
     } 

     for (i=0; i<n; i++) { 
      this.coefficients[i][0] = a[i]; 
      this.coefficients[i][1] = b[i]; 
      this.coefficients[i][2] = c[i]; 
      this.coefficients[i][3] = d[i]; 
     } 
    } 

    /** 
    * Solve a linear system of equations (LSE) Ax=b 
    * @param A Matrix giving the coefficients of the LSE 
    * @param b Right-hand side of the equations 
    * @return Solution vector x of the LSE 
    */ 
    public static double[] jacobiVerfahren(double[][] A, double[] b, double tolerance) 
    { 
     int i, j; 
     int n = A.length; // A.lenght = A[0].length, da quadratisch 
     double[] x = new double[n]; 
     double startwert = 0.0; // willkürlicher Startwert 
     double summe; 
     double[] xOld = new double[n]; 
     for (i=0; i<n; i++) { 
      xOld[i] = startwert; 
     } 
     double[] genauigkeit = new double[n]; 
     int howManyTimesUntilSmallerEpsilon = 0; 
     int maxIterations = 100; 
     boolean genauigkeitErreicht = false; 

     while (! genauigkeitErreicht) { 
      howManyTimesUntilSmallerEpsilon++; 
      if (howManyTimesUntilSmallerEpsilon > maxIterations) break; 

      for (i=0; i<n; i++) { 
       summe = 0.0; 
       for (j=0; j<n; j++) { 
        if (j != i) { 
         summe += A[i][j] * xOld[j]; 
        } 
       } 
       x[i] = (b[i] - summe)/A[i][i]; 
      } 
      for (i=0; i<n; i++) { 
       genauigkeit[i] = Math.abs(x[i] - xOld[i]); 
      } 
      // Die Genauigkeit von epsilon muss für jedes Element erreicht sein, so dass 
      // bereits ein Element, auf das das nicht zutrifft, ausreicht, um die Iteration 
      // weiter zu führen. 
      for (i=0; i<n; i++) { 
       if (genauigkeit[i] > tolerance) { 
        genauigkeitErreicht = false; 
        break; 
       } else { 
        genauigkeitErreicht = true; 
       } 
      } 
      for (i=0; i<n; i++) { 
       xOld[i] = x[i]; 
      } 
     } 
     return x; 
    } 

    public void draw (Pane pane) 
    { 
     int i, j, s; 
     int n = this.nodeList.size(); 
     double oneStep; 
     double nSteps = 12.0; 
     double x, y, a, b, c, d; 
     double xDifference; 

     polyline.getPoints().clear(); 
     for (i=0; i<n-1; i++) { 
      // Calculate the increment by means of the distance between two neighboring points 
      oneStep = Math.abs(this.nodeList.get(i).getX() - this.nodeList.get(i+1).getX())/nSteps; 

      a = this.coefficients[i][0]; 
      b = this.coefficients[i][1]; 
      c = this.coefficients[i][2]; 
      d = this.coefficients[i][3]; 

      for (s=0; s<nSteps; s++) { 
       x = this.nodeList.get(i).getX() + s * oneStep; 
       // to calculate y we need the formula of a third-order cubic spline of the form 
       // y(x) = a(i) + b(i)(x-x(i))^1 + c(i)(x-x(i))^2 + d(i)(x-x(i))^3 
       xDifference = x - this.nodeList.get(i).getX(); 
       y = a + (b*xDifference) + (c*Math.pow(xDifference,2.0)) + (d*Math.pow(xDifference,3.0)); 
       polyline.getPoints().addAll(x, y); 
      } 
     } 
     polyline.getPoints().addAll(this.nodeList.get(n-1).getX(), this.nodeList.get(n-1).getY()); 
     polyline.toBack(); 
     pane.getChildren().add(polyline); 
    } 

} 

MyMouseEvents.java

package InterpolationMinimal; 

import javafx.event.EventHandler; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.layout.Pane; 

public class MyMouseEvents { 

    public static void paneMouseEvents(Pane pane) { 

     MouseGestures mg = new MouseGestures(); 

     pane.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 
      @Override 
      public void handle(MouseEvent mouseEvent) { 

       if (mouseEvent.getTarget() == pane) { 
        Node node = new Node(mouseEvent.getSceneX()-pane.getLayoutX(), mouseEvent.getSceneY()); 
        node.setRadius(10.0); 
        mg.makeDraggable(node); 
        Controller.mySpline.getNodeList().add(node); 
        pane.getChildren().add(node); 
       } 
       mouseEvent.consume(); 
      } 
     }); 
    } 
} 

MouseGestures.java

package InterpolationMinimal; 

import javafx.event.EventHandler; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.input.MouseEvent; 

public class MouseGestures { 

    double orgSceneX, orgSceneY; 
    double orgTranslateX, orgTranslateY; 

    public void makeDraggable(Node node) { 
     node.setOnMousePressed(circleOnMousePressedEventHandler); 
     node.setOnMouseDragged(circleOnMouseDraggedEventHandler); 
    } 

    EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() { 
     @Override 
     public void handle(MouseEvent me) { 
      //orgSceneX = me.getSceneX(); 
      orgSceneY = me.getSceneY(); 
      Node p = (Node) me.getSource(); 
      //orgTranslateX = p.getCenterX(); 
      orgTranslateY = p.getCenterY(); 
     } 
    }; 

    EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() { 
     @Override 
     public void handle(MouseEvent me) { 
      //double offsetX = me.getSceneX() - orgSceneX; 
      double offsetY = me.getSceneY() - orgSceneY; 
      //double newTranslateX = orgTranslateX + offsetX; 
      double newTranslateY = orgTranslateY + offsetY; 

      Node p = (Node) me.getSource(); 
      System.out.println(); 
      //p.setCenterX(newTranslateX); 
      p.setCenterY(newTranslateY); 
      //p.setX(newTranslateX); 
      p.setY(newTranslateY); 

      try { 
       FXMLLoader loader = new FXMLLoader(getClass().getResource("gui.fxml")); 
       loader.load(); 
       Controller controller = loader.getController(); 
       controller.handleButtonDrawSpline(); 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 
     } 
    }; 
} 

重新計算和重新繪製動作的方式,經由所述MouseGestures.circleOnMouseDraggedEventHandler方法Controller.handleButtonDrawSpline訪問實現。但是這樣做pane.getChildren()是空的(它應該至少包含已添加到panenode對象),而當我點擊「繪製樣條線」按鈕時它不爲空。 handleButtonDrawSpline方法都執行,但pane對象不同。我有「感覺」問題出現在那裏。任何幫助是極大的讚賞。

回答

2

每次調用circleOnMouseDraggedEventHandler方法時,都會創建一個新的gui控件,並帶有新的pane。這意味着電話號碼controller.handleButtonDrawSpline()每次都會繪製一個新的窗格實例。

避免這種情況的最簡單方法是將控制器作爲參數傳遞。

在控制器:

@Override 
public void initialize(URL url, ResourceBundle rb) { 
    [...] 
    MyMouseEvents.paneMouseEvents(pane, this); 
} 

在MyMouseEvents:

public static void paneMouseEvents(Pane pane, Controller controller) { 
    MouseGestures mg = new MouseGestures(controller); 
    [...] 
} 

在MouseGestures:

private final Controller controller; 

public MouseGestures(Controller controller) { 
    this.controller = controller; 
} 

[...] 

EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() { 
    @Override 
    public void handle(MouseEvent me) { 
     // double offsetX = me.getSceneX() - orgSceneX; 
     double offsetY = me.getSceneY() - orgSceneY; 
     // double newTranslateX = orgTranslateX + offsetX; 
     double newTranslateY = orgTranslateY + offsetY; 

     Node p = (Node) me.getSource(); 
     System.out.println(); 
     // p.setCenterX(newTranslateX); 
     p.setCenterY(newTranslateY); 
     // p.setX(newTranslateX); 
     p.setY(newTranslateY); 

     controller.handleButtonDrawSpline(); 

    } 
}; 

因爲抽籤方法上的pane同一個實例現在畫畫,你有以避免多次將折線添加到中的窗格:

polyline.getPoints().addAll(this.nodeList.get(n - 1).getX(), this.nodeList.get(n - 1).getY()); 
polyline.toBack(); 
if (!pane.getChildren().contains(polyline)) { 
    pane.getChildren().add(polyline); 
} 

結果:

animated gif

+0

謝謝你這麼多@Modus託倫斯! – 732E772E