Reputation: 17
I am trying to draw an oval in a canvas but fillOval
does nothing.
There are no exceptions or errors. While debugging it seems to get to the code and run it but nothing appears on the screen.
code (only the relevant package): https://github.com/amnon3234/PTM_Project/tree/master/PTM_Project_Final/src/view
specific parts: My canvas:
public class HeightMapDisplayer extends Canvas {
public HeightMapDisplayer() {
GraphicsContext gc = this.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillOval(10,10,40,40);
}
}
My controller:
public class MainWindowController implements Initializable {
@FXML
HeightMapDisplayer _heightMap;
// Constructor , Initialize data
public MainWindowController() {
_heightMap = new HeightMapDisplayer();
}
}
My XML:
<BorderPane xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.MainWindowController">
<left>
...
<HeightMapDisplayer fx:id="heightMap" height="400.0" width="400.0" />
...
</BorderPane>
Result:
Upvotes: 0
Views: 959
Reputation: 209339
The problem is that you are trying to draw on the canvas before you set the width and height. Because your Canvas
subclass implicitly invokes the default Canvas
constructor, which sets the width and height to zero, then in the constructor you perform the drawing. Since this drawing happens outside the bounds of the canvas, nothing appears. The width
and height
attributes in the FXML cause the setWidth()
and setHeight()
methods to be invoked, but of course this happens after the constructor call is complete, by which time it is too late.
A "quick fix" is to allow the width and height to be passed to your constructor, so you can pass them on to the Canvas
constructor:
public class HeightMapDisplayer extends Canvas {
public HeightMapDisplayer(
@NamedArg("width") double width,
@NamedArg("height") double height) {
super(width, height);
GraphicsContext gc = this.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillOval(10,10,40,40);
}
}
However, it is generally a bad idea to subclass JavaFX node classes (other than those specifically written with the intention of subclassing them, such as Cell
s). A much better approach is to use a MVC approach: define a model class encapsulating the data you want to represent on the canvas, define a controller to do the drawing, and just use a plain Canvas
.
E.g. the (mock) model class might look like
public class HeightMap {
// data you need here...
private int offset ;
private int size ;
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
}
with a controller class
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class HeightMapController {
private final Canvas view ;
private HeightMap model ;
public HeightMapController(Canvas view) {
this.view = view ;
}
public void setModel(HeightMap model) {
this.model = model ;
GraphicsContext gc = view.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillOval(model.getOffset(), model.getOffset(), model.getSize(), model.getSize());
}
}
Here's an updated MainWindowController
:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
public class MainWindowController implements Initializable {
@FXML
private Canvas heightMap;
private HeightMapController heightMapController ;
@Override
public void initialize(URL location, ResourceBundle resources) {
heightMapController = new HeightMapController(heightMap);
}
public void setHeightMapModel(HeightMap heightMapModel) {
heightMapController.setModel(heightMapModel);
}
}
and FXML file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/10.0.2-internal"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jamesd.examples.canvas.MainWindowController">
<left>
<Canvas fx:id="heightMap"
height="400" width="400" />
</left>
</BorderPane>
and finally the code that assembles everything:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
/**
* JavaFX App
*/
public class App extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
Scene scene = new Scene(loader.load());
MainWindowController controller = loader.getController();
HeightMap heightMap = new HeightMap();
heightMap.setOffset(10);
heightMap.setSize(40);
controller.setHeightMapModel(heightMap);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
There are other techniques that can make this a bit cleaner, e.g. using a controller factory to instantiate controllers with the model already present, or using a dependency injection framework to achieve the same thing, but this should give you the idea of the correct structure.
Upvotes: 1