I am drawing a Circle and a Text on a Pane and what I want is to have a text appear next to the cursor that says "Mouse is over the circle" when it is over the circle and "Mouse is outside the circle" when its outside. What happens instead is the text always says "Mouse is outside the circle" except in some locations over the circle (and even then it tends to flash back to the wrong one). I also tried setting the text directly from the mouseEntered and mouseExited events and its even worse. What am I doing wrong? Better yet, is there another way of determining whether the cursor is over a certain node? Also if you could explain to me why I get a "variable used in lambda expression should be effectively final" when I move the definition of s inside the start method, it would be great :)
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Ex1512 extends Application {
String s="";
#Override
public void start(Stage primaryStage) throws Exception {
Text text = new Text();
Pane pane = new Pane();
Circle circle = new Circle(100, 100, 50);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
circle.setOnMouseEntered(e -> s = "Mouse is over the circle");
circle.setOnMouseExited(e -> s = "Mouse is outside the circle");
pane.setOnMouseMoved(e -> {
text.setText(s);
text.setX(e.getX());
text.setY(e.getY());
});
pane.getChildren().addAll(circle,text);
Scene scene = new Scene(pane,300,300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
You are putting the text over the circle. So the text receives the event, then the circle again. Just relocate the text away from the mouse cursor.
This solves your problem:
text.setX(e.getX()+20);
Or you could use setMouseTransparent on the text.
Related
I want to hover over a shape I created in Canvas (JavaFx) and when hovering over with the mouse I want a text output pop-up to display. Is there a built in function for this? I can't find how to do it anywhere...
Unlike with the scene graph, a Canvas has no notion of what it contains. It's nothing but a two-dimensional array of pixels and provides no further distinctions than that. If you want to know if and when the mouse hovers over a "shape" in the Canvas you'll have to keep track of where the "shape" is and do the necessary computations manually. Here's an example which shows a popup at the mouse's location only while within the drawn rectangle:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
// used to test if mouse is within the rectangle
var bounds = new Rectangle2D(200, 100, 100, 100);
var canvas = new Canvas(500, 300);
// draw rectangle using above bounds
canvas.getGraphicsContext2D().setFill(Color.FIREBRICK);
canvas
.getGraphicsContext2D()
.fillRect(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight());
var popup = createPopup();
canvas.setOnMouseMoved(
e -> {
// test if local mouse coordinates are within rectangle
if (bounds.contains(e.getX(), e.getY())) {
// convert local coordinates to screen coordinates
var point = canvas.localToScreen(e.getX(), e.getY());
// show the popup at the mouse's location on the screen
popup.show(canvas, point.getX(), point.getY());
} else if (popup.isShowing()) {
// hide popup if showing and mouse no longer within rectangle
popup.hide();
}
});
primaryStage.setScene(new Scene(new Pane(canvas)));
primaryStage.show();
}
private Popup createPopup() {
var content = new StackPane(new Label("Hello, World!"));
content.setPadding(new Insets(10, 5, 10, 5));
content.setBackground(
new Background(new BackgroundFill(Color.WHITE, new CornerRadii(10), null)));
content.setEffect(new DropShadow());
var popup = new Popup();
popup.getContent().add(content);
return popup;
}
}
As you can see, this is relatively simple for a static image consisting of a single, rectangular shape. This can quickly become more complicated just by making the image dynamic, let alone by having to test the bounds of irregular shapes.
An easier approach would be to use the scene graph. Instead of drawing to a Canvas you would add a Rectangle to a layout. Then you can use the Node API to know when the mouse enters and exits the Rectangle (e.g. setOnMouseXXX, hover property, etc.). It also makes it easier to use something like a Tooltip, which can simply be "installed" on the Node.
I'm trying to create a draggable selection box for a sketching program in JavaFX, one like this:
I'm only not sure how to do it. I initially wanted to do it like this: capture the mouse coordinates when the mouse is pressed and do it again at the end of a drag, then calculate the height and width and make a transparent button with a black border with these properties.
But, then I realized that when I do it like this, it is not possible to see the button while you are scaling the plane, unless you draw and delete a lot of buttons.
So, I wondered if there is a better way to do something like this or is my reasoning above right? Thanks
I would use a Rectangle instead of a Button. Just do what you describe, but update the size (and position) of the rectangle on mouse drag, instead of only adding it when the mouse is released.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class SelectionRectangle extends Application {
private double mouseDownX ;
private double mouseDownY ;
#Override
public void start(Stage primaryStage) {
Rectangle selectionRectangle = new Rectangle();
selectionRectangle.setStroke(Color.BLACK);
selectionRectangle.setFill(Color.TRANSPARENT);
selectionRectangle.getStrokeDashArray().addAll(5.0, 5.0);
Pane pane = new Pane();
pane.setMinSize(600, 600);
pane.getChildren().add(selectionRectangle);
pane.setOnMousePressed(e -> {
mouseDownX = e.getX();
mouseDownY = e.getY();
selectionRectangle.setX(mouseDownX);
selectionRectangle.setY(mouseDownY);
selectionRectangle.setWidth(0);
selectionRectangle.setHeight(0);
});
pane.setOnMouseDragged(e -> {
selectionRectangle.setX(Math.min(e.getX(), mouseDownX));
selectionRectangle.setWidth(Math.abs(e.getX() - mouseDownX));
selectionRectangle.setY(Math.min(e.getY(), mouseDownY));
selectionRectangle.setHeight(Math.abs(e.getY() - mouseDownY));
});
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can use a mouse released handler to figure out what's selected, by looking at the x, y, width, and height properties of the rectangle, as needed.
I need a bunch of widgets, including a TabPane, inside a scrollable and zoomable view, basically this:
ScrollPane[ Group[ widgets .. including TabPane ] ]
The ScrollPane is obviously needed for scrolling, and the Group holds all the widgets and supports zooming.
The initial problem with that approach is that the ScrollPane shows scroll bars based on the original size of the widgets, not based on the actual size.
In the screenshot, note how scrollbars are shown even though the tab pane is much smaller than the viewport, so no scrollbars are needed.
The web site https://pixelduke.wordpress.com/2012/09/16/zooming-inside-a-scrollpane explains how to solve that by adding another nested Group:
ScrollPane[ Group[ Group[ widgets .. including TabPane ] ] ]
The inner Group, as before, holds all the widgets and supports zooming.
The outer Group automatically gets the layout bounds of the zoomed
widgets in the inner group, allowing the ScrollPane to correctly configure the scroll bars.
.. but now the TabPane will fail to properly draw itself.
All you see is the red background of the TabPane:
The complete tab pane only shows up once it's somehow forced to refresh.
The example code toggles the 'side' property of the tab pane when you press 'SPACE'.
Now I have it all: Tab Pane draws OK, inner group can be zoomed, scroll bars appear as soon as the zoomed content no longer fits the viewport. But having to force the Tab Pane refresh is certainly a hack.
Is there a fault in my scene graph?
Is this a bug in the TabPane rendering?
The problem certainly seems limited to the TabPane. When I add other groups, rectangles, buttons, text nodes to the 'widgets' in the inner group, they all render fine. Only the TabPane refuses to show its tabs.
Tried this with both JDK 1.8.0_51 and 1.8.0_73, also tried on Windows, Linux and Mac OS X.
import javafx.application.Application;
import javafx.geometry.Side;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class TabDemo extends Application
{
#Override
public void start(final Stage stage)
{
// TabPane with some tabs
final TabPane tabs = new TabPane();
tabs.setStyle("-fx-background-color: red;");
for (int i=0; i<3; ++i)
{
final Rectangle rect = new Rectangle(i*100, 100, 10+i*100, 20+i*80);
rect.setFill(Color.BLUE);
final Pane content = new Pane(rect);
final Tab tab = new Tab("Tab " + (i+1), content);
tab.setClosable(false);
tabs.getTabs().add(tab);
}
tabs.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
tabs.setPrefSize(400, 300);
final Group widgets = new Group(tabs);
widgets.setScaleX(0.5);
widgets.setScaleY(0.5);
final Group scroll_content = new Group(widgets);
final ScrollPane scroll = new ScrollPane(scroll_content);
final Scene scene = new Scene(scroll);
stage.setTitle("Tab Demo");
stage.setScene(scene);
stage.show();
// Unfortunately, the setup of ScrollPane -> Group -> Group -> TabPane
// breaks the rendering of the TabPane.
// While the red background shows the area occupied by TabPane,
// the actual Tabs are missing..
System.out.println("See anything?");
scene.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) ->
{
if (event.getCode() == KeyCode.SPACE)
{ // .. until 'side' or 'tabMinWidth' or .. are twiddled to force a refresh
tabs.setSide(Side.BOTTOM);
tabs.setSide(Side.TOP);
System.out.println("See it now?");
}
});
}
public static void main(String[] args)
{
launch(args);
}
}
I am currently working on an assignment where i must print a circle in the center of the primary stage and 4 buttons on the bottom center area of the primary stage which move the circle, up, down, left, and right when clicked. when i run my code, my circle is filled in with the color black. I have set the stroke of the circle to be black but i have not set the circle to be filled black. I know i can just set my circle to be filled white and somewhat solve the problem, but i am wondering if anyone knows why this is happening. Also, i cannot get the Circle and the buttons to print into the same window. I can get either the circle to print by setting the primaryStage to the scene or print the buttons by setting the scene to hBox and then setting the primaryStage to the scene. How should i best change my code so that the buttons and the circle are both displayed?
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.geometry.Pos;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
public class Btest extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
// Create a border pane
BorderPane pane = new BorderPane();
// create Hbox, set to bottom center
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.BOTTOM_CENTER);
Button btLeft = new Button("Left");
Button btDown = new Button("Down");
Button btUp = new Button("Up");
Button btRight = new Button("Right");
hBox.getChildren().addAll(btLeft, btDown, btUp, btRight);
// Lambda's
btLeft.setOnAction((e) -> {
System.out.println("Process Left");
});
btDown.setOnAction((e) -> {
System.out.println("Process Down");
});
btUp.setOnAction(e -> {
System.out.println("Process Up");
});
btRight.setOnAction((e) -> {
System.out.println("Process Right");
});
pane.setCenter(new CenteredCircle("Center"));
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 300, 300);
//set stage and display
primaryStage.setTitle("ShowBorderPane"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
public static void main(String[] args) {
Application.launch(args);
}
}
// create custom class for circle
class CenteredCircle extends StackPane {
public CenteredCircle(String title) {
setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
Circle circle = new Circle();
circle.setStroke(Color.BLACK);
circle.setCenterX(50);
circle.setCenterY(50);
circle.setRadius(50);
getChildren().add(circle);
}
}
"Why is my circle filled Black even though i haven't set it to be filled?"
Because the default color is black. See the doc of Shape.setFill() method:
Defines parameters to fill the interior of an Shape using the settings
of the Paint context. The default value is Color.BLACK for all shapes
except Line, Polyline, and Path. The default value is null for those
shapes.
"... Also, i cannot get the Circle and the buttons to print into the same window."
Put the Hbox to the parent BorderPane, for instance into the bottom:
pane.setBottom( hBox );
I am working through some coursework and am running into an odd issue. I'm working with javafx learning how to build shapes and work with alignment. Anyway my circle object will not respond to setCenterX or setCenterY commands (the radius definition statement does work) in the original definition statements nor in the commands issued by my event handlers which should be redefining these set x and set y values. I cannot figure out why. Please see my code below. When working correctly my code would allow me to move the circle object around the screen with the buttons and event handlers I've created. If I can figure out why the setCenterX and setCenterY don't work, I'm sure I can get the rest. Thanks for your help in advance.
package bravo15;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.scene.Scene;
public class FifteenDotThreeVersionThree extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
Circle circle = new Circle();
circle.setCenterX(300);
circle.setCenterY(300);
circle.setRadius(50);
// Hold four buttons in an HBox
// Define hbox
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER);
// define buttons
Button btLeft = new Button("Left");
Button btRight = new Button("Right");
Button btUp = new Button("Up");
Button btDown = new Button("Down");
// add defined buttons into the hbox
hBox.getChildren().add(btLeft);
hBox.getChildren().add(btRight);
hBox.getChildren().add(btUp);
hBox.getChildren().add(btDown);
// Create and register the handlers for the four buttons
btLeft.setOnAction(e -> circle.setCenterX(circle.getCenterX() - 10));
btRight.setOnAction(e -> circle.setCenterX(circle.getCenterX() + 10));
btUp.setOnAction(e -> circle.setCenterY(circle.getCenterY() + 10));
btDown.setOnAction(e -> circle.setCenterY(circle.getCenterY() - 10));
BorderPane borderPane = new BorderPane();
borderPane.setTop(circle);
borderPane.setBottom(hBox);
BorderPane.setAlignment(hBox, Pos.CENTER);
// Create a scene and place it in the stage
Scene scene = new Scene(borderPane, 200, 200);
primaryStage.setTitle("ControlCircle Version 3"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
A BorderPane manages the layout of its components, so it positions the circle for you by setting its layoutX and layoutY properties so that it appears at the top left.
Wrap it in a Pane, which performs no layout, and place the Pane in the top of the border pane:
borderPane.setTop(new Pane(circle));
Note that you have things set up so that it is initially off-screen. You probably want to increase the size of the scene:
Scene scene = new Scene(borderPane, 600, 600);
You can do it this way to shift the circle:
btLeft.setOnAction(e -> circle.setTranslateX(circle.getTranslateX() - 10));
btRight.setOnAction(e -> circle.setTranslateX(circle.getTranslateX() + 10));
btUp.setOnAction(e -> circle.setTranslateY(circle.getTranslateY() - 10));
btDown.setOnAction(e -> circle.setTranslateY(circle.getTranslateY() + 10));
setTranslateX():
Defines the x coordinate of the translation that is added to this
Node's transform. The node's final translation will be computed as
layoutX + translateX, where layoutX establishes the node's stable
position and translateX optionally makes dynamic adjustments to that
position. This variable can be used to alter the location of a node
without disturbing its layoutBounds, which makes it useful for
animating a node's location.
And it looks better with borderPane.setCenter(circle); than borderPane.setTop(circle);.
I have also removed the following lines:
circle.setCenterX(300);
circle.setCenterY(300);