Custom minimize button do not call MouseLeave event - java

I have a simple stage with StageStyle.TRANSPARENT (no default buttons).
Therefore I tried to create my own custom buttons, represented each by an ImageView with the next events activated: setOnMouseEntered, setOnMouseExited and of course setOnMouseClicked.
Problem is for the Minmized Button. Is a simple implementation like below
ImageView.setOnMouseClicked((MouseEvent event) -> {
stage.setIconified(true);
});
Lets imagine that my ImageView is a White rectangle. On mouse enter event, it changes its color into Black. On mouse exit, it is going back to White color.
When the ImageView is clicked, the window will be minimized, everything perfectly workable until now.
Problem is when the application is restored (maximized), the Minimized custom button is stuck with color Black (the color that represent the button is hovered), instead of White (default color when is not focused).
P.S. it seems that everything like relocate, setImage etc. inside the onMouseClicked handler is cut by the the setInconified(true);
Any help would be most appreciated.
Thank you for your time of reading this.
Updates to clear a bit the question
The normal print-screen image (when it is not hovered)
The hover print-screen (when it is hovered)
As you can observe, everything works perfectly. In the moment when "-" button (minimize button) is pressed, when the application is restored, it will remain stuck in hover mode, until the mouse cursor will hover again the button (then everything comes back to normal). Sadly neither CSS approach or event listeners on image view dose not seems to solve this issue.
Update code loaded
This is a simple one source file with just a button that call minimize
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
private Scene scene;
private Stage stage;
#Override
public void start(Stage stage) {
try {
this.stage = stage;
stage.initStyle(StageStyle.TRANSPARENT);
stage.setAlwaysOnTop(true);
stage.setFullScreen(true);
stage.setFullScreenExitHint("");
createScene(stage);
stage.setScene(scene);
stage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
private void createScene(Stage stage) {
Pane layer = new Pane();
layer.setPickOnBounds(false);
scene = new Scene(layer, 800, 600);
scene.getStylesheets().add("application/application.css");
layer.getChildren().add(buildMinimizeImage());
}
private ImageView buildMinimizeImage() {
ImageView imv = new ImageView();
int width = 43 ;
int height = 36;
imv.setId("myImage");
imv.setFitWidth(width);
imv.setFitHeight(height);
imv.setOnMouseClicked((MouseEvent event) -> {
stage.setIconified(true);
});
imv.relocate(100, 100);
return imv;
}
public static void main(String[] args) {
launch(args);
}
}
And the application.css is very simple as well
#myImage
{
-fx-image: url("minimize.png");
}
#myImage:hover
{
-fx-image: url("minimizeIn.png");
}
Issue is reproducible on Ubuntu 14.04 and Windows 10. I do not think is an OS problem
RESOLVED
Please find enclose the Harry Mitchel solution (thank you one more time for it). It is perfectly workable.
If you want to fix the code from above I by adding the setOnMousePressed event.
imv.setOnMousePressed((MouseEvent event) -> {
imv.setImage(image);
});

You can listen to the maximized property of the Stage class. Inside the changed() method, set the ImageView's image.
stage.maximizedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
//Display the desired icon here.
}
});
Here is a custom minimize button. You provide the two images and the stage as parameters. When the mouse is not over the button, it will show the image referenced in the constructor's first parameter. When the mouse is over the button, it will show the image referenced in the constructor's second parameter. When you click the image the stage will be minimized.
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class MinimizeButton extends Button {
/**
*
* #param img the image when the button is NOT selected
* #param imgHover the image when button is selected
* #param stage the stage that will be minimized
*/
public MinimizeButton(Image img, Image imgHover, Stage stage) {
ImageView imgView = new ImageView(img);
this.setGraphic(imgView);
this.addEventHandler(MouseEvent.MOUSE_ENTERED, (MouseEvent e) -> {
imgView.setImage(imgHover);
});
this.addEventHandler(MouseEvent.MOUSE_EXITED, (MouseEvent e) -> {
imgView.setImage(img);
});
this.setOnAction((ActionEvent event) -> {
stage.setIconified(true);
imgView.setImage(img);
});
}
}
Here is an example app that uses the MinimizeButton class.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class CustomMinimize extends Application {
#Override
public void start(Stage stage) {
Image imgWhite = new Image(getClass().getResourceAsStream("imgWhite.png")); //your image here
Image imgGreen = new Image(getClass().getResourceAsStream("imgGreen.png")); //your hover image here
MinimizeButton btnMinimize = new MinimizeButton(imgWhite, imgGreen, stage);
btnMinimize.setStyle("-fx-background-color: black;");
btnMinimize.setPrefSize(50, 50);
Button btnExit = new Button("X");
btnExit.setMinSize(50,50);
btnExit.setOnAction((ActionEvent event) -> {
System.exit(0);
});
btnExit.setStyle("-fx-background-color: black;");
HBox hBox = new HBox();
hBox.setSpacing(2);
hBox.getChildren().addAll(btnMinimize, btnExit);
AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().addAll(hBox);
AnchorPane.setRightAnchor(hBox, 5.0);
AnchorPane.setTopAnchor(hBox, 5.0);
Scene scene = new Scene(anchorPane);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Your question is not very clear (not that it is very unclear though), so I will attempt to solve your problem.
I am assuming that your color change is done through ImageView.setOnMouseEntered() and ImageView.setOnMouseExited(). If this is so, you should instead use CSS.
.myImageView
{
-fx-image: url("my_white_image.png");
}
.myImageView:hovered
{
-fx-image: url("my_black_image.png");
}
For the things in your "PS" section, I couldn't understand, so I would not be able to give any advice on that.

Related

How to modify JavaX Button (if decorated with CSS) so it gets darker when needed?

Modify the color of a JavaFX button so it gets darker when I pass the mouse, but when it is decorated with CSS:
When you modify the color of a Button in JavaFX, you lose the effect that makes it darker when the mouse passes. So seeing that we get 2 functions to do some action when the mouse gets over the button setOnMouseEntered/setOnMouseExited I can just get the color and use the function .darker()like this:
private Color last_color = null;
public void change_color(Button button) {
Color color;
if (last_color == null) {
color = (Color)button.getBackground().getFills().get(0).getFill();
last_color = color;
color = color.darker();
} else {
color = last_color;
last_color = null;
}
button.setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
}
This actually works, except when the color of the buttons is in CSS, looks like the CSS style is beyong the background color set with Java code.
The buttons are probably going to be styled with CSS, and I would like to have a generic function to work in any Button. Is possible to get the color from CSS and modify it?
If not, how could I make the Button darker when the mouse is over it?
To change the color of the button on hover, you can add the corresponding css in the CSS file.
.myButton:hover{
-fx-color: derive(-fx-base, -45%);
}
But if you say, you need to do some calculations, or other logic to determine the color, then you see the below approach.The below approach is to give you some idea only. THIS IS NOT A SOLUTION. I always recommend to get things done using CSS.
The key point to note is : the background of a button is made up of many fills. You need to know which fill you need to modify.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class ButtonBackgroundDemo extends Application {
public static void main(String[] args) {
launch(args);
}
BackgroundFill lastFill;
#Override
public void start(Stage primaryStage) {
Button button = new Button("Api Test");
button.addEventFilter(MouseEvent.MOUSE_ENTERED, e->changeColor(button, e));
button.addEventFilter(MouseEvent.MOUSE_EXITED, e->changeColor(button, e));
Button button2 = new Button("Css Test");
button2.getStyleClass().add("myButton");
VBox root = new VBox(button,button2);
root.setAlignment(Pos.CENTER);
root.setSpacing(20);
Scene scene = new Scene(root, 200, 200);
scene.getStylesheets().add(getClass().getResource("button.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* I really think this is an overkill. Instead, use CSS implementation
*/
private void changeColor(Button button, MouseEvent e) {
int fillSize = button.getBackground().getFills().size();
List<BackgroundFill> fills = new ArrayList<>();
button.getBackground().getFills().forEach(fills::add);
if(e.getEventType() == MouseEvent.MOUSE_ENTERED){
lastFill = fills.get(fillSize-1);
fills.remove(lastFill);
// Use your logic to darken the lastFill.getFill(). This may be Linear-Gradient as well. so get the correct color from gradient as well.
BackgroundFill fill = new BackgroundFill(Color.DARKGREY, lastFill.getRadii(),lastFill.getInsets());
fills.add(fill);
}else {
fills.remove(fillSize-1);
fills.add(lastFill);
}
button.setBackground(new Background(fills,null));
}
}
And in the button.css:
.myButton:hover{
-fx-color: derive(-fx-base, -45%);
}

Is there a way to "autofit" elements on a page so they take up the entire canvas

In JavaFX, is there a way to "autofit" elements on a page so they take up the entire thing?
Currently, I'm trying to make the window have two buttons that together take up the entire canvas, but I am not sure how to do that, given that it is possible to stretch the window, etc. I've tried playing around with Button.setPrefSize, but the button size stays the same, it just shows you a window with two outsized buttons, the text of which is not visible.
What I currently have
What I want (but for any window size)
Here's one way (code here but also possible in Scene Builder and FXML):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class TestApplication extends Application {
#Override
public void start(Stage stage) throws Exception {
Button button1 = new Button("Button1");
HBox.setHgrow(button1, Priority.SOMETIMES);
button1.setMaxWidth(Double.MAX_VALUE);
button1.setMaxHeight(Double.MAX_VALUE);
Button button2 = new Button("Button2");
HBox.setHgrow(button2, Priority.SOMETIMES);
button2.setMaxWidth(Double.MAX_VALUE);
button2.setMaxHeight(Double.MAX_VALUE);
HBox hBox = new HBox(button1, button2);
AnchorPane.setLeftAnchor(hBox, 0.0);
AnchorPane.setRightAnchor(hBox, 0.0);
AnchorPane.setTopAnchor(hBox, 0.0);
AnchorPane.setBottomAnchor(hBox, 0.0);
AnchorPane rootContainer = new AnchorPane(hBox);
Scene scene = new Scene(rootContainer, 600, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

How to position a JavaFX context menu inside the main window?

Hi I am a JavaFX newbie and I am trying to write my first application. I want to start with an empty window and provide a popup menu that allows users to add 3D elements to the window.
I have created a simple Group containing a few trivial geometric shapes and added this group as the parent to a Scene. I define a mouse event handler for the scene and call setScene to make this the scene for my Stage (passed in to my Application's start method).
Unfortunately, I can't seem to find a way of positioning the menu correctly in response to a mouse pressed event. I get it that I need to get the X and Y coordinates from the Event, but when I pass these unchanged to the context menu show method, the menu appears in the top left-hand corner of my laptop display, rather than inside my application window.
Clearly, I need to offset these values by the origin of some other window, but what? I have tried the Scene, the Group and and the Stage, but with no success :-( This ought to be a trivial problem - where am I going wrong??
Code sample shown below:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Box;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
public class PopupTest extends Application {
private static final ContextMenu contextMenu = new ContextMenu();
public static void main(String[] args) {
MenuItem cut = new MenuItem("Cut");
MenuItem copy = new MenuItem("Copy");
MenuItem paste = new MenuItem("Paste");
contextMenu.getItems().addAll(cut, copy, paste);
Application.launch(args);
}
#Override
public void start(Stage stage) {
// Create a Box
Box box = new Box(100, 100, 100);
box.setTranslateX(150);
box.setTranslateY(0);
box.setTranslateZ(400);
// Create a Sphere
Sphere sphere = new Sphere(50);
sphere.setTranslateX(300);
sphere.setTranslateY(-5);
sphere.setTranslateZ(400);
// Create a Cylinder
Cylinder cylinder = new Cylinder(40, 120);
cylinder.setTranslateX(500);
cylinder.setTranslateY(-25);
cylinder.setTranslateZ(600);
// Create a Light
PointLight light = new PointLight(Color.YELLOW);
light.setTranslateX(350);
light.setTranslateY(100);
light.setTranslateZ(300);
// Create a Camera to view the 3D Shapes
PerspectiveCamera camera = new PerspectiveCamera(false);
camera.setTranslateX(100);
camera.setTranslateY(-50);
camera.setTranslateZ(300);
// Add the Shapes and the Light to the Group
Group root = new Group(box, sphere, cylinder, light);
// Create a Scene with depth buffer enabled
Scene scene = new Scene(root, 400, 300, true);
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("mouse click detected!");
if (event.isPopupTrigger()) {
// similar results with getX() vs getSceneX() etc.
System.out.println("Display menu at (" + event.getSceneX() + "," + event.getSceneY() + ")");
contextMenu.show(root, event.getSceneX(), event.getSceneY());
}
}
});
// Add the Camera to the Scene
scene.setCamera(camera);
// Add the Scene to the Stage
stage.setScene(scene);
// Set the Title of the Stage
stage.setTitle("Trying to get popup menu working");
// Display the Stage
stage.show();
}
}

How do I make JavaFX MediaView stretch media to fill parent?

I want make JavaFX MediaView stretch to fill parent container.
I tried some methods from google before,but nothing helpful(maybe i'm not apply it right),and there is a question with same name as this,but solutions in that question maybe not suit me.
first i use the ahchorpane to anchor mediaview but find it can not stretch mediaview,and I don't know why because other controls like button can work.
Then i try bind its width and height to anchorpane(parent of mediaview)
Region region=(Region) mediaplayer.getParent();
mediaplayer.setPreserveRatio(false);
mediaplayer.fitWidthProperty().bind(region.widthProperty());
mediaplayer.fitHeightProperty().bind(region.heightProperty());
it's can expand mediaview exactly when i resize window,but can't Shrink down!
I guess it's maybe because size of region depend on it childs?
finally,i try to bind mediaview'size to stage'size,it's work,but the code looks like ugly,because i need calculate size manully.
mediaplayer.setPreserveRatio(false);
mediaplayer.fitWidthProperty().bind(stage.widthProperty().subtract(200));
mediaplayer.fitHeightProperty().bind(stage.heightProperty().subtract(135));
are there have any better solutions?
Using the code below you will see that you are able to stretch and shrink the mediaView as much as you like ( depending of the stage dimensions)
import java.io.File;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class TestApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
BorderPane pane = new BorderPane();
FileChooser fc = new FileChooser();
File mediaFile = fc.showOpenDialog(null);
MediaView moviePlayer;
if (mediaFile != null) {
MediaPlayer player = new MediaPlayer(new Media(mediaFile.toURI().toString()));
moviePlayer = new MediaView(player);
moviePlayer.setPreserveRatio(false);
moviePlayer.fitWidthProperty().bind(pane.widthProperty());
moviePlayer.fitHeightProperty().bind(pane.heightProperty());
pane.getChildren().add(moviePlayer);
player.play();
}
Scene scene = new Scene(pane, 300, 300);
stage.setScene(scene);
stage.show();
}
}
This code works with AnchorPane as well so I guess there is something else wrong with your code if you still have the issue or I haven't understand what you need. Make a simple runnable program to demonstrate the issue.
Try to add this Action-Listener for rescaling the size:
MediaView.getParent().layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
mediaView.setFitHeight(newValue.getHeight());
mediaView.setFitWidth(newValue.getWidth());
}
});
This should be called every time the parent-size got changed or maximized and then the view should be resized to parent, too.

JavaFX drag & drop with custom node beside mouse icon

What's the best way to show a semi-transparent "copy" of a Node next to the mouse icon during a drag/drop?
Basically I have HBoxes with colored backgrounds and text labels in them, and I'd like to give the appearance that they "stick" to the mouse cursor when they're being dragged.
It's nice if users can visually verify WHAT they're dragging, rather than just seeing the mouse cursor change into the various drag icons. Scene Builder tends to do this when you drag some components, like a RadioButton.
The "semi-transparent "copy" of a Node" is accomplished by calling snapshot(null, null) on a node, which returns a WritableImage. Then you set this WritableImage as the drag view of the DragBoard. Here is a small example on how to do this:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DragAndDrop extends Application {
private static final DataFormat DRAGGABLE_HBOX_TYPE = new DataFormat("draggable-hbox");
#Override
public void start(Stage stage) {
VBox content = new VBox(5);
for (int i = 0; i < 10; i++) {
Label label = new Label("Test drag");
DraggableHBox box = new DraggableHBox();
box.getChildren().add(label);
content.getChildren().add(box);
}
stage.setScene(new Scene(content));
stage.show();
}
class DraggableHBox extends HBox {
public DraggableHBox() {
this.setOnDragDetected(e -> {
Dragboard db = this.startDragAndDrop(TransferMode.MOVE);
// This is where the magic happens, you take a snapshot of the HBox.
db.setDragView(this.snapshot(null, null));
// The DragView wont be displayed unless we set the content of the dragboard as well.
// Here you probably want to do more meaningful stuff than adding an empty String to the content.
ClipboardContent content = new ClipboardContent();
content.put(DRAGGABLE_HBOX_TYPE, "");
db.setContent(content);
e.consume();
});
}
}
public static void main(String[] args) {
launch();
}
}

Categories