Maven & JavaFX für Werner
Malen mit der Maus
Das Malen mit der Maus ist unter JavaFX ebenso kein Hexenwerk. Dazu benötigt man nur die passenden
Event-Handler. Diese richten wir einfach auf dem Canvas ein. Einen, wenn wir die linke Maustaste
drücken, einen, wenn wir die Maus mit gedrückter Maustaste bewegen, und einen, wenn wir die Maustaste
wieder loslassen.
Beim Drücken der Maustaste, initieren wir einen neuen Pfad. Beim Bewegen der Maus mit gedrückter
Maustaste, zeichnen wir eine Linie. Beim Loslassen der Maustaste beenden wir den Pfad und füllen die
gezeichnete Fläche mit eine Farbe.
// mouse event listener when pressing mouse button
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (pEvent) -> {
// if left mouse button pressed, start a new draw
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.beginPath();
gc.setStroke(strokeColor);
gc.setFill(fillColor);
gc.moveTo(pEvent.getSceneX(), pEvent.getSceneY());
}
});
Sieht doch sehr selbsterklärend aus. Auf dem Canvas fügen wir einen Event-Handler hinzu, der
angesprungen wird, wenn wir eine Maustaste drücken. Ist es die primäre Maustaste, initieren wir einen
neuen Pfad, setzen die Pfad-Farbe und bewegen die aktuelle Position dort hin.
// mouse event listener when holding mouse button and move the mouse
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, (pEvent) -> {
// if left mouse button is down while moving
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.lineTo(pEvent.getSceneX(), pEvent.getSceneY());
gc.stroke();
}
});
Das gleiche Spiel in Grün. Auf dem Canvas fügen wir einen Event-Handler hinzu, der angesprungen wird,
wenn wir bei gedrückter Maustaste die Maus bewegen. Ist die Maustaste die primäre Taste, zeichnen wir
eine Linie von der bisherigen Position zur neuen Position.
// mouse event listener when release mouse button
canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, (pEvent) -> {
// if left mouse button is released, fill drawn graphics
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.lineTo(pEvent.getSceneX(), pEvent.getSceneY());
gc.stroke();
gc.fill();
}
});
Und auch hier noch mal das gleiche in Grün. Nur malen wir noch die letzte Linie, und füllen dann die
gezeichnete Fläche mit unserer Füllfarbe.
Der komplette Code könnte dann so aussehen:
package de.wh.javafx;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* Hello world!
*
*/
public class App extends Application {
public static void main(String[] args) {
System.out.println("Hello World!");
launch(args);
}
private GraphicsContext gc; // used graphics context
private Color strokeColor = Color.GREEN; // used line color
private Color fillColor = Color.YELLOW; // used fill color
@Override
public void start(Stage primaryStage) throws Exception {
Pane root = new Pane();
Canvas canvas = new Canvas(900, 900);
root.getChildren().add(canvas);
Scene scene = new Scene(root, 900, 900);
primaryStage.setTitle("Hallo Werner");
primaryStage.setScene(scene);
primaryStage.show();
// get the graphics context for use in event handlers
gc = canvas.getGraphicsContext2D();
// mouse event listener when pressing mouse button
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (pEvent) -> {
// if left mouse button pressed, start a new draw
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.beginPath();
gc.setStroke(strokeColor);
gc.moveTo(pEvent.getSceneX(), pEvent.getSceneY());
}
});
// mouse event listener when holding mouse button and move the mouse
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, (pEvent) -> {
// if left mouse button is down while moving
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.lineTo(pEvent.getSceneX(), pEvent.getSceneY());
gc.stroke();
}
});
// mouse event listener when release mouse button
canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, (pEvent) -> {
// if left mouse button is released, fill drawn graphics
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.lineTo(pEvent.getSceneX(), pEvent.getSceneY());
gc.stroke();
gc.setFill(fillColor);
gc.fill();
}
});
}
}
Startet man nun dieses Programm, kann man mit der Maus eine grüne Kurve zeichnen, die beim Loslassen
Gelb eingefärbt wird.
Ein Löschen der Zeichenfläche, könnte man einfach durch ein Tastaturkürzel auslösen. Z.b. mit Strg-C.
Ein passenden Event-Handler könnte so aussehen:
// ctrl-c clears the area
scene.addEventHandler(KeyEvent.KEY_RELEASED, (pEvent) -> {
if (pEvent.getCode() == KeyCode.C && pEvent.isControlDown()) {
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 900, 900);
}
});
Man beachte, dass dieser Event-Handler nicht auf dem Canvas, sondern auf der Scene eingehängt wird.
Das liegt daran, dass ein Canvas zum Malen da ist, und daher keine Tastatur-Events erhält.
Jetzt wäre es noch schön, wenn man die Linie- und Flächenfarbe ändern könnte. Eine einfache Idee wäre,
diese Änderungen über ein Kontextmenü zu machen. Man drückt die rechte Maustaste, bekommt ein Menü mit
zwei Punkten ("Linie", "Fläche") angezeigt, und dadrunter gibt es die Farben zum Auswählen. Das geht
in JavaFX - natürlich - auch ganz einfach. Man muss nur ein neues Kontextmenü anlegen, die Menüeinträge
und Farbeinträge hinzufügen, und dem Canvas sagen, dass er dieses Kontextmenü anzeigen soll, wenn wir
die rechte Maustaste drücken:
// a new context menu to choose the colors
ContextMenu contextMenu = new ContextMenu();
// sub menu for stroke colors
Menu stroke = new Menu("stroke");
// stroke color red
MenuItem strokeRed = new MenuItem("red");
strokeRed.setOnAction((pEvent) -> {
strokeColor = Color.RED;
});
// stroke color green
MenuItem strokeGreen = new MenuItem("green");
strokeGreen.setOnAction((pEvent) -> {
strokeColor = Color.GREEN;
});
// stroke color blue
MenuItem strokeBlue = new MenuItem("blue");
strokeBlue.setOnAction((pEvent) -> {
strokeColor = Color.BLUE;
});
// add red, green blue to stroke color sub menu
stroke.getItems().addAll(strokeRed, strokeGreen, strokeBlue);
// sub menu for fill colors
Menu fill = new Menu("fill");
// fill color cyan
MenuItem fillCyan = new MenuItem("cyan");
fillCyan.setOnAction((pEvent) -> {
fillColor = Color.CYAN;
});
// fill color magenta
MenuItem fillMagenta = new MenuItem("magenta");
fillMagenta.setOnAction((pEvent) -> {
fillColor = Color.MAGENTA;
});
// fill color yellow
MenuItem fillYellow = new MenuItem("yellow");
fillYellow.setOnAction((pEvent) -> {
fillColor = Color.YELLOW;
});
// fill color black
MenuItem fillKey = new MenuItem("key");
fillKey.setOnAction((pEvent) -> {
fillColor = Color.BLACK;
});
// fill color none
MenuItem fillNone = new MenuItem("none");
fillNone.setOnAction((pEvent) -> {
fillColor = Color.TRANSPARENT;
});
// add cyan, magenta, yellow, key and none to fill color sub menu
fill.getItems().addAll(fillCyan, fillMagenta, fillYellow, fillKey, fillNone);
// add stroke and fill color to context menu
contextMenu.getItems().addAll(stroke, fill);
// if right click on canvas, show context menu
canvas.setOnContextMenuRequested((pEvent) -> {
contextMenu.show(scene.getWindow(), pEvent.getScreenX(), pEvent.getScreenY());
});
Fügt man diesen Code zum Programm hinzu, so kann man die Linie- und Flächenfarbe ändern. Die Logik ist
leicht zu erkennen. Wir legen ein ContextMenu an, hängen zwei Menu darunter (stroke, fill), unter jedem
Menu ein paar MenuItem (die Farben) und den MenuItem sagen wir noch, was passieren soll, wenn wir das
MenuItem auswählen (setzen der strokeColor bzw. fillColor).
Nur noch zwei Bonbon. Das Kontextmenü können wir natürlich auch zum Setzen der Liniestärke verwenden,
und es wäre schön, wenn man einen bereits begonnenen Pfad fortführen könnte. Beides super einfach.
Die Linienstärke wird genauso wie die Farben ins Kontextmenü eingehängt:
private Color strokeColor = Color.GREEN;
private double lineWidth = 1;
private Color fillColor = Color.YELLOW;
...
// sub menu for line width
Menu width = new Menu("width");
// one point
MenuItem one = new MenuItem("1px");
one.setOnAction((pEvent) -> {
lineWidth = 1;
});
// two point
MenuItem two = new MenuItem("2px");
two.setOnAction((pEvent) -> {
lineWidth = 2;
});
// four point
MenuItem four = new MenuItem("4px");
four.setOnAction((pEvent) -> {
lineWidth = 4;
});
// eight point
MenuItem eight = new MenuItem("8px");
eight.setOnAction((pEvent) -> {
lineWidth = 8;
});
width.getItems().addAll(one, two, four, eight);
...
contextMenu.getItems().addAll(stroke, width, fill);
...
// mouse event listener when pressing mouse button
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (pEvent) -> {
// if left mouse button pressed, start a new draw
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.beginPath();
gc.setStroke(strokeColor);
gc.setLineWidth(lineWidth);
gc.moveTo(pEvent.getSceneX(), pEvent.getSceneY());
}
});
...
... und beim Event-Handler für das Drücken der Maustaste fügen wir hinzu, dass nur ein neuer Pfad
anfängt, wenn wir die Shift-Taste nicht dazu drücken:
// mouse event listener when pressing mouse button
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (pEvent) -> {
// if left mouse button pressed and shift is not pressed, start a new draw
if (pEvent.getButton() == MouseButton.PRIMARY && !pEvent.isShiftDown()) {
...
Halten wir also die Shift-Taste beim drücken der Maustaste und bewegen dann die Maus, malen wir den
vorherigen Pfad weiter.
Der ganze Code könnte dann so aussehen:
package de.wh.javafx;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* Hello world!
*
*/
public class App extends Application {
public static void main(String[] args) {
System.out.println("Hello World!");
launch(args);
}
private GraphicsContext gc;
private Color strokeColor = Color.GREEN;
private double lineWidth = 1;
private Color fillColor = Color.TRANSPARENT;
@Override
public void start(Stage primaryStage) throws Exception {
Pane root = new Pane();
Canvas canvas = new Canvas(900, 900);
root.getChildren().add(canvas);
Scene scene = new Scene(root, 900, 900);
primaryStage.setTitle("Hallo Werner");
primaryStage.setScene(scene);
primaryStage.show();
gc = canvas.getGraphicsContext2D();
// a new context menu to choose the colors
ContextMenu contextMenu = new ContextMenu();
// sub menu for stroke colors
Menu stroke = new Menu("stroke");
// stroke color red
MenuItem strokeRed = new MenuItem("red");
strokeRed.setOnAction((pEvent) -> {
strokeColor = Color.RED;
});
// stroke color green
MenuItem strokeGreen = new MenuItem("green");
strokeGreen.setOnAction((pEvent) -> {
strokeColor = Color.GREEN;
});
// stroke color blue
MenuItem strokeBlue = new MenuItem("blue");
strokeBlue.setOnAction((pEvent) -> {
strokeColor = Color.BLUE;
});
// add red, green blue to stroke color sub menu
stroke.getItems().addAll(strokeRed, strokeGreen, strokeBlue);
// sub menu for line width
Menu width = new Menu("width");
// one point
MenuItem one = new MenuItem("1px");
one.setOnAction((pEvent) -> {
lineWidth = 1;
});
// two point
MenuItem two = new MenuItem("2px");
two.setOnAction((pEvent) -> {
lineWidth = 2;
});
// four point
MenuItem four = new MenuItem("4px");
four.setOnAction((pEvent) -> {
lineWidth = 4;
});
// eight point
MenuItem eight = new MenuItem("8px");
eight.setOnAction((pEvent) -> {
lineWidth = 8;
});
width.getItems().addAll(one, two, four, eight);
// sub menu for fill colors
Menu fill = new Menu("fill");
// fill color cyan
MenuItem fillCyan = new MenuItem("cyan");
fillCyan.setOnAction((pEvent) -> {
fillColor = Color.CYAN;
});
// fill color magenta
MenuItem fillMagenta = new MenuItem("magenta");
fillMagenta.setOnAction((pEvent) -> {
fillColor = Color.MAGENTA;
});
// fill color yellow
MenuItem fillYellow = new MenuItem("yellow");
fillYellow.setOnAction((pEvent) -> {
fillColor = Color.YELLOW;
});
// fill color black
MenuItem fillKey = new MenuItem("key");
fillKey.setOnAction((pEvent) -> {
fillColor = Color.BLACK;
});
// fill color none
MenuItem fillNone = new MenuItem("none");
fillNone.setOnAction((pEvent) -> {
fillColor = Color.TRANSPARENT;
});
// add cyan, magenta, yellow, key and none to fill color sub menu
fill.getItems().addAll(fillCyan, fillMagenta, fillYellow, fillKey, fillNone);
// add stroke and fill color to context menu
contextMenu.getItems().addAll(stroke, width, fill);
// if right click on canvas, show context menu
canvas.setOnContextMenuRequested((pEvent) -> {
contextMenu.show(scene.getWindow(), pEvent.getScreenX(), pEvent.getScreenY());
});
// mouse event listener when pressing mouse button
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (pEvent) -> {
// if left mouse button pressed and shift is not pressed, start a new draw
if (pEvent.getButton() == MouseButton.PRIMARY && !pEvent.isShiftDown()) {
gc.beginPath();
gc.setStroke(strokeColor);
gc.setLineWidth(lineWidth);
gc.setFill(fillColor);
gc.moveTo(pEvent.getSceneX(), pEvent.getSceneY());
}
});
// mouse event listener when holding mouse button and move the mouse
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, (pEvent) -> {
// if left mouse button is down while moving
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.lineTo(pEvent.getSceneX(), pEvent.getSceneY());
gc.stroke();
}
});
// mouse event listener when release mouse button
canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, (pEvent) -> {
// if left mouse button is released, fill drawn graphics
if (pEvent.getButton() == MouseButton.PRIMARY) {
gc.lineTo(pEvent.getSceneX(), pEvent.getSceneY());
gc.stroke();
gc.fill();
}
});
// ctrl-c clears the area
scene.addEventHandler(KeyEvent.KEY_RELEASED, (pEvent) -> {
if (pEvent.getCode() == KeyCode.C && pEvent.isControlDown()) {
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 900, 900);
}
});
}
}