JavaFX imageview actions - javafx

I'm trying to build a replica of MineSweeper, to do this I'm trying to setup some premade images (100x100 pixels made in Photoshop) into an imageview and then when clicked hiding it (to reveal the number below). Without much complexity -- Just the image going visible and invisible I am finding a lot of issues and difficulties.
It is likely due to a complete lack of knowledge on Javafx in general but I even following tutorials to the t I am still unable to implement this feature. I will attach to this my Main.Java code, my sample.fxml code (although it's not called anymore), and then the image I'm trying to hide when clicked.
I have done a lot of research on this (past couple of days) and haven't found anything that solves my problems.
Main.java:
package sample;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
ImageView button;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("MineSweeper XP");
button = new ImageView();
button.setOnMouseClicked(new EventHandler<MouseEvent>)(){
public void handle(MouseEvent event){
}
}
StackPane layout = new StackPane();
layout.getChildren().add(button);
Scene scene = new Scene(layout,1000, 1000);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
sample.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="100.0" prefWidth="100.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ImageView fx:id="button" fitHeight="150.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#../textures/Button_Custom.png" />
</image>
</ImageView>
</children>
</AnchorPane>
The "Button" that will be used for all the MineSweeper keys
My singular goal at this given moment is to create a window of any size where I can put the button anywhere and then when the user clicks that button it disappears.

One possible solution is to create your own ImageView that holds the code for mouse clicks within.
Here's a sample TileButton class:
class TileButton extends ImageView {
public TileButton() {
// Set parameters for the image
Image graphic = new Image("minesweeper/tile.png");
setImage(graphic);
setFitWidth(24);
setFitHeight(24);
// When this button is click, set its visibility to false.
setOnMouseClicked(e -> {
setVisible(false);
});
}
}
Using this custom class, all the logic for the "button" is contained within the TileButton itself.
Now, you can populate your mine field with a GridPane of StackPane containers in each cell. The StackPane allows you to stack nodes on top of each other. So, place a Label with the number you want into each StackPane and then add your new TileButton on top of it.
Below is a complete sample application to demonstrate. Note, I am not implementing any of the actual game logic here, just providing a sample you can copy/paste and see in action. I also have not spent any time formatting and styling the ImageView, but your could use CSS to make it act like a standard button as well.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// GridPane to hold the cells
GridPane gridPane = new GridPane();
gridPane.setGridLinesVisible(true);
gridPane.setHgap(2);
gridPane.setVgap(2);
// Populate the Gridpane with a 10x10 grid
int number = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
// Add a new StackPane for each grid cell. The Stackpane will hold a number and the
// TileButton. When the TileButton is clicked, it disappears, revealing the number below
StackPane pane = new StackPane();
pane.getChildren().add(new Label(String.valueOf(number)));
pane.getChildren().add(new TileButton());
gridPane.add(pane, j, i);
// Just increment our sample number
number++;
}
}
root.getChildren().add(gridPane);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
class TileButton extends ImageView {
public TileButton() {
// Set parameters for the image
Image graphic = new Image("minesweeper/tile.png");
setImage(graphic);
setFitWidth(24);
setFitHeight(24);
// When this button is click, set its visibility to false.
setOnMouseClicked(e -> {
setVisible(false);
});
}
}
The above sample application produces this:
Note I am not using FXML for this as creating a grid of multiple custom objects is much simpler in Java than FXML.
Per kleopatra's suggestion, this can be accomplished using a custom Button instead. With the new TileButton class below, you can add the buttons in our loop using gridPane.add(new TileButton(String.valueOf(number)), j, i);:
class TileButton extends Button {
public TileButton(String text) {
// Set the button's size
setPrefSize(24,24);
setStyle("-fx-padding: 0");
// Set the graphic to our tile.png image
setGraphic(new ImageView("sample/done/minesweeper/tile.png"){{
setFitWidth(24);
setFitHeight(24);
}});
// Set the button to display only our graphic initially
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
// Set the action to remove the graphic when the button is clicked
setOnAction(event -> {
setGraphic(new Label(text));
});
}
}

Related

Selecting ComboBox option on key press. I would like the selection model to auto scroll/jump to the selected option

What I have so far:
When I press for example the C key button, this happens:
First item starts with C is selected.
The selectOptionOnKey (see below) function selected the option that starts with letter C.
The problem:
The combo box stores more option that can be displayed at once. So when the option which is selected is not in the displayed area I want the combo box to scroll down/jump to that option but I don't know how to do that.
Selected an option by pressing a letter key - option is not in the displayed area. - This will happen with the current code.
Selected an option by pressing a letter key - option is not in the displayed area. - This is what I want to happen!
Sample code:
Main:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Scene scene = new Scene(root, 850.0, 650.0);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Controller:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.input.KeyCode;
public class SampleController implements Initializable {
#FXML private ComboBox<String> cb;
//Entered random options
private final ObservableList<String> options = FXCollections.observableArrayList(
"Aab",
"Aer",
"Aeq",
"Arx",
"Byad",
"Csca",
"Csee",
"Cfefe",
"Cead",
"Defea",
"Dqeqe",
"Fefaf",
"Gert",
"Wqad",
"Xsad",
"Zzz"
);
#Override
public void initialize(URL location, ResourceBundle resources) {
cb.getItems().addAll(options);
selectOptionOnKey();
}
/* When you press a letter key
* this method will search for an option(item) that starts with the input letter key
* and it selects the first occurrence in combo box
*/
public void selectOptionOnKey() {
cb.setOnKeyPressed(e -> {
KeyCode keyCode = e.getCode();
if (keyCode.isLetterKey()) {
char key = keyCode.getName().charAt(0);
SingleSelectionModel<String> cbSelectionModel = cb.getSelectionModel();
cbSelectionModel.select(0);
for (int i = 0; i < options.size(); i++) {
if(cbSelectionModel.getSelectedItem().charAt(0) == key) {
// option which starts with the input letter found -> select it
cbSelectionModel.select(i);
/* Before exiting the function it would be nice if after the selection,
the combo box would auto slide/jump to the option which is selected.
I don't know how to do that. */
return;
}
else
cbSelectionModel.selectNext();
}
}
});
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="SampleController">
<children>
<ComboBox fx:id="cb" layoutX="300.0" layoutY="300.0" prefHeight="25.0" prefWidth="173.0" />
</children>
</AnchorPane>
Any help is much appreciated.
As general behaviour, selection in a virtual control does not scroll the selected index/item into the visible region. Slightly astonished that the list in a comboBox' dropdown is no exception to that rule - would have expected at the one that's selected on opening to be visible.
The way out is to scroll the newly selected item in code. This involves:
get hold of the comboBox' skin
get the listView by skin.getPopupContent(): while that method is public, its return type is an implementation detail ..
call list.scrollTo(index)
A code snippet, could be called whenever the selection changed, f.i. in your key handler:
cbSelectionModel.select(i);
ComboBoxListViewSkin<?> skin = (ComboBoxListViewSkin<?>) cb.getSkin();
ListView<?> list = (ListView<?>) skin.getPopupContent();
list.scrollTo(i);

javafx button positoning in scene

how can I set a button's position in the middle for all size of anchorpane in javafx scenebuilder.....
like this code...
the button should be in the middle even if i set the scene into the middle
i have tried so many times but couldent get it...
thanks in advance
if you dont understand try to run the code in IDE and then maximize it and again minimize it...
observe the position of the button
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.*;
import javafx.stage.WindowEvent;
import java.util.Optional;
public class Javafxpopupmessage extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(100));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button)
closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be
seen.
closeConfirmation.setX(mainStage.getX());
closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}
This can not be done directly in AnchorPane because positions are fixed there. You must use a container that allows "floating positioning". For example HBox.
<AnchorPane xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
<children>
<HBox alignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button text="Exit" />
</children>
</HBox>
</children>
</AnchorPane>

Javafx8 stackpane children blocking mouse events

I have a javafx8 app with a complex scene. It consists of a splitpane where, in the top segment I have a stackpane with multitple levels (children). The rear most is a background that would only need redraws on resizing, another with gridlines, lat/long. Another for drawing and, finally, another with a sliding composite control. When the composite (buttons, labels, comboboxes, and textfields, in the last child of the stackframe, it properly intercepted and acted on mouse and keyboard events. However, with it on top no events were getting propagated to the child panes below. I switched the order and now the buttons and axis control work but nothing now gets propagated to the composite control below it. Buttons fail to see the mouse action.
This seems similar to Stackpane - MouseClick to be listened by both its children but it's not real clear that it is nor is it clear that has a correct answer.
I simplified my scenario with a very simple test, source to follow. In this I have a StackPane with two children, both VBox, one if which with top left alignment, the other with top right. Only the second button, top right responds to being pressed.
Why. The reason I have my app with multiple panes is performance. Both drawing actions are expensive and freq and I didn't want to have to constantly redraw static or near static layouts.
fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<StackPane fx:id="containerPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mouseeventdemo.MouseEventDemoController">
<children>
<VBox fx:id="container1" prefHeight="200.0" prefWidth="100.0">
<children>
<Button fx:id="leftButton" mnemonicParsing="false" onAction="#leftButtonDown" text="left button" />
</children>
</VBox>
<VBox fx:id="container2" alignment="TOP_RIGHT" prefHeight="200.0" prefWidth="100.0">
<children>
<Button fx:id="rightButton" mnemonicParsing="false" onAction="#rightButtonDown" text="Right Button" />
</children>
</VBox>
</children>
</StackPane>
Controller:
package mouseeventdemo;
/**
* Sample Skeleton for 'FXMLDocument.fxml' Controller Class
*/
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
public class MouseEventDemoController {
#FXML // ResourceBundle that was given to the FXMLLoader
private ResourceBundle resources;
#FXML // URL location of the FXML file that was given to the FXMLLoader
private URL location;
#FXML // fx:id="containerPane"
private StackPane containerPane; // Value injected by FXMLLoader
#FXML // fx:id="container1"
private VBox container1; // Value injected by FXMLLoader
#FXML // fx:id="leftButton"
private Button leftButton; // Value injected by FXMLLoader
#FXML // fx:id="container2"
private VBox container2; // Value injected by FXMLLoader
#FXML // fx:id="rightButton"
private Button rightButton; // Value injected by FXMLLoader
#FXML
void leftButtonDown(ActionEvent event) {
System.out.println("leftButtonPressed");
}
#FXML
void rightButtonDown(ActionEvent event) {
System.out.println("rightButtonPressed");
}
#FXML // This method is called by the FXMLLoader when initialization is complete
void initialize() {
assert containerPane != null : "fx:id=\"containerPane\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert container1 != null : "fx:id=\"container1\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert leftButton != null : "fx:id=\"leftButton\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert container2 != null : "fx:id=\"container2\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
assert rightButton != null : "fx:id=\"rightButton\" was not injected: check your FXML file 'FXMLDocument.fxml'.";
}
}
main
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mouseeventdemo;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
*
* #author walt
*/
public class MouseEventDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
In my app the rear most pane is a background canvas. It need be drawn but once and redrawn when the window is resized.
The next is a canvas that had lat/long grids. It need only be redrawn when the window size changes or either the vertical or horizontal scales change.
The next is the main drawing canvas in the following
<vbox>
<hbox>
drawing canvas
vertical scale
</hbox>
horizontal scale
</vbox>
The next carries one or more flagpoles where the vertical pole is a slide rule like reticle. These need to be dragable. If this layer is on top it slides perfectly but the two scales fail to get focus. If the top two panes are reversed, the scales work perfectly but the flagpole objects never receive events.
The two button example duplicates very simply what I see.
Thanks!
In the eventhandling of javafx only the topmost node (in your example container2) under the mouseposition will be determined as the target for the mouse click event, that's why container1 doesn't receive the event.
The target node is the end node in the event dispatch chain. When you click on leftButton, the mouse clicked event travels from the root node of your scene graph to the target node(container2), and back if it doesn't get consumed. Container1 as not being on the dispatch chain, won't receive the event:
http://docs.oracle.com/javafx/2/events/processing.htm
You can try to use container.setPickOnBounds(false)

JavaFX JFXtras CalendarTextField withShowTime

I feel that I am missing something when using the CalendarTextField so I come here for some help. Here is the SSCCE that I am using:
Main.java:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("CalendarTextFieldTest.fxml"));
Scene scene = new Scene((StackPane) loader.load());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
CalendarTextFieldTest.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<?import jfxtras.scene.control.CalendarTextField?>
<StackPane xmlns:fx="http://javafx.com/fxml/1" prefWidth="200" fx:controller="com.example.test.CalendarTextFieldTestController">
<CalendarTextField fx:id="test"/>
</StackPane>
CalendarTextFieldController.java:
package com.example.test;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javafx.fxml.FXML;
import jfxtras.scene.control.CalendarTextField;
public class CalendarTextFieldTestController {
#FXML
CalendarTextField test;
public void initialize()
{
test.withCalendar(Calendar.getInstance());
//test.withShowTime(Boolean.TRUE);
test.setDateFormat(SimpleDateFormat.getDateTimeInstance());
}
}
In this state the test works fine. But when I comment out the test.showShowTime(Boolean.TRUE) I no longer get any type of response when clicking a date.
Any help as to what I may be overlooking would be appreciated.
I just ran your example and it runs flawlessly, so it is not a bug in JFXtras, it seems.
Maybe you are over looking the two icons in the top right corner? That is, if you use the latests R4-SNAPSHOT, because JavaFX has made some changes to the way they do CSS in 8.20 and those icons are not visible with earlier versions.
Problem with the "show time mode" is that when the time sliders are shown, there are suddenly multiple active buttons to press and slide around. If only date is shown, the popup knows it can close when a date is clicked. But in time mode it cannot; maybe the user still wants to modify the hours, or minutes, or maybe not. There is no way of telling. So in time mode there is an explicit accept or cancel icon in the top right corner.

Javafx make combobox expand upward

I have a window which has a combobox at the bottom. When I click on it, not all the options are visible because they are not inside the screen area anymore. How can I make the drop-down list display upwards instead of downards?
I have the ComboBox defined in SceneBuilder. I define it in my code this way:
#FXML
ComboBox fontsComboBox;
In my initialize() method of the controller assigned to that window I have set some properties:
fontComboBox.setVisibleRowCount(3);
fontComboBox.getItems().addAll(fontList);
fontComboBox.setValue(fontList[0]);
I'm pretty sure I need to add something here in order to make the dropdown list fo up.
Thanks,
Serban
This was a known bug till Java 8u45. It is fixed in Java 8u60.
https://bugs.openjdk.java.net/browse/JDK-8092942 (fixed in 8u60)
You can download JDK/JRE 8u60ea (early access) here to give it a try: https://jdk8.java.net/download.html
Java 8 Update 60 is scheduled GA for August 2015.
This example shows the problem. Simply open the combobox, even if you move the window down to screen, it won't show the list above the box.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ComboBoxTester extends Application {
#Override
public void start(Stage primaryStage) {
List<Integer> values = new ArrayList<>();
for (int i = 1; i < 100; i++) {
values.add(i);
}
ObservableList<Integer> items = FXCollections.observableArrayList(values);
ComboBox<Integer> comboBox = new ComboBox<>(items);
comboBox.getSelectionModel().clearAndSelect(0);
comboBox.setVisibleRowCount(5);
BorderPane root = new BorderPane();
root.setBottom(comboBox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Java 8 Update 45 behaviour
Java 8 Update 60 ea behaviour

Resources