JavaFx: Context menu dynamic style - css

I have a problem with context menus. The items in the context menu can be styled, but it doesn't work dynamic. I mean for example at the initialize I add a styleclass to the MenuItem and by an event I remove it, but the style still remains there. How can it be solved?
Here is a simple example to play with it:
Controller.java:
public class Controller implements Initializable {
private static final String STYLED = "styled";
#FXML
private ListView<String> listView;
#FXML
private Button change;
#Override
public void initialize(URL location, ResourceBundle resources) {
ContextMenu cm = new ContextMenu();
MenuItem miAdd = new MenuItem("Add");
miAdd.setOnAction(event -> listView.getItems().add("Apple"));
MenuItem miRemove = new MenuItem("Remove");
miRemove.disableProperty().bind(listView.getSelectionModel().selectedItemProperty().isNull());
miRemove.setOnAction(event -> listView.getItems().remove(listView.getSelectionModel().getSelectedItem()));
cm.getItems().addAll(miAdd, miRemove);
listView.setContextMenu(cm);
miRemove.getStyleClass().add(STYLED);
change.setOnAction(event -> {
if (!miRemove.getStyleClass().contains(STYLED)) {
miRemove.getStyleClass().add(STYLED);
} else {
miRemove.getStyleClass().remove(STYLED);
}
});
}
}
View.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="textField.Controller"
stylesheets="css/layout.css">
<VBox>
<Button fx:id="change" text="ChangeStyle"/>
<ListView fx:id="listView"/>
</VBox>
</AnchorPane>
layout.css:
.styled .text {
-fx-strikethrough: true;
}
.styled {
-fx-background-color: gray;
}
How can I manage to add/remove style to my MenuItem ?

Related

Is there an effecient way to refresh multiple TableViews across multiple Stages in JavaFX?

Specifically, I have a Primary Stage and two other Stages initialized from the Primary Stage when a menu item is selected.
All three Stages contain TableViews that display different views of my data, and allow relevant actions. When the user is working in one of these Stages and performs an action that changes the data, I would like the changes to be reflected in all three TableViews.
Each of the TableViews is backed by an ObservableArrayList. They update automatically when an element is added or removed, but I have to call the TableView.refresh() method anytime the data changes in any other way and I want it to show.
From reading other posts it seems that it is possible to pass a reference of a Parent Controller object to a Child controller, but it is not considered good practice. It occurred to me that perhaps I could create a new class that would be responsible for refreshing the tables in all 3 Stages, however that would require obtaining a reference to each of the controller objects somehow.
I'm stuck and I'd be grateful for any suggestions!
In attempting to create a minimal reproducible example I figured out what I was doing wrong:
In my original code I was converting Simple Double Properties to Simple String Properties before displaying them in the table, in order to control how they were displayed. The conversion was executed in the overwritten Call() method of Column.setCellValueFactory(). Somehow this conversion was causing the table not to respond to data changes right away.
Here is some code to illustrate what I am talking about:
public class Controller {
#FXML
public TableView<Person> mainTable;
#FXML
public Button editButton;
#FXML
public BorderPane mainBorderPane;
public Button openSecondButton;
public Button refreshButton;
public void initialize(){
DataModel.getInstance().addPerson(new Person("Frank", 1, 20));
DataModel.getInstance().addPerson(new Person("Cindy", 2, 20));
DataModel.getInstance().addPerson(new Person("Eric", 3, 67));
mainTable.setItems(DataModel.getInstance().getPeople());
TableColumn<Person, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>(){
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> c){
return c.getValue().nameProperty();
}
});
TableColumn<Person, Integer> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
#Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().idProperty().asObject();
}
});
TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Integer>, ObservableValue<Integer>>() {
#Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Person, Integer> person) {
return person.getValue().ageProperty().asObject();
}
});
TableColumn<Person, String> ageStringColumn = new TableColumn<>("Age String");
ageStringColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Person, String> person) {
return new SimpleStringProperty(String.valueOf(person.getValue().getAge()));
}
});
mainTable.getColumns().addAll(nameColumn, idColumn, ageColumn, ageStringColumn);
}
#FXML
private void showSecondStage(ActionEvent actionEvent) throws IOException {
Stage secondStage = new Stage();
secondStage.setTitle("Secondary Stage");
secondStage.initModality(Modality.NONE);
secondStage.initStyle(StageStyle.UTILITY);
Parent parent = FXMLLoader.load(getClass().getResource("secondary.fxml"));
secondStage.setScene(new Scene(parent));
secondStage.initOwner(mainBorderPane.getScene().getWindow());
secondStage.show();
}
public boolean handleEditPersonRequest() {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(mainBorderPane.getScene().getWindow());
dialog.setTitle("Edit Person");
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(Controller.class.getResource("dialog.fxml"));
try {
dialog.getDialogPane().setContent(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
DialogController controller = fxmlLoader.getController();
controller.setFields(mainTable.getSelectionModel().getSelectedItem());
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Button okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
okButton.addEventFilter(ActionEvent.ACTION, event -> {
if (!controller.validateAndProcess()) {
event.consume();
System.out.println("Invalid entry, try again");
}});
Optional<ButtonType> result = dialog.showAndWait();
return result.isPresent() && result.get() == ButtonType.OK;
}
public void refreshTable(ActionEvent actionEvent) {
mainTable.refresh();
}
}
And the .fxml file
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane fx:id="mainBorderPane" fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" >
<left>
<VBox>
<Button text="Edit Person" fx:id="editButton" onAction="#handleEditPersonRequest"/>
<Button text = "Open Second Window" fx:id="openSecondButton" onAction="#showSecondStage"/>
<Button text="Refresh table" fx:id="refreshButton" onAction="#refreshTable"/>
</VBox>
</left>
<center>
<TableView fx:id="mainTable" />
</center>
</BorderPane>
Here is the dialog controller:
public class DialogController {
public TextField nameField;
public TextField idField;
public TextField ageField;
public Person person;
public void setFields(Person selectedPerson) {
person = selectedPerson;
nameField.setText(person.getName());
idField.setText(String.valueOf(person.getId()));
ageField.setText(String.valueOf(person.getAge()));
}
public boolean validateAndProcess(){
try{
String name = nameField.getText();
int id = Integer.parseInt(idField.getText());
int age = Integer.parseInt(ageField.getText());
person.setName(name);
person.setId(id);
person.setAge(age);
return true;
}catch (NumberFormatException | NullPointerException e){
e.printStackTrace();
return false;
}
}
}
And it's .fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="sample.DialogController"
prefHeight="400.0" prefWidth="600.0">
<Label text="Name"/>
<TextField fx:id="nameField"/>
<Label text="Id"/>
<TextField fx:id="idField"/>
<Label text="Age"/>
<TextField fx:id="ageField"/>
</VBox>
I'm not going to include the code for the second window, as it's not needed to see the problem.

JavaFX loading a new fxml file into the same scene

I have a class named Test1Controller where a Scene is executed and in another class, Test2Controller, I want to load another FXML but in the same Scene. Can someone help me with this?
public class Test1Controller {
private static AnchorPane page;
public static Stage initialicePage() {
primaryStage = new Stage();
page = (AnchorPane) FXMLLoader.load(Principal.class.getResource("Test1.fxml"));
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.setTitle("Test1");
primaryStage.setResizable(true);
primaryStage.setMinHeight(700);
primaryStage.setMinWidth(824);
primaryStage.setMaximized(true);
primaryStage.show();
}
}
public class SwitchScene 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();
}
public static void main(String[] args) {
launch(args);
}
}
public class FXMLDocumentController {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
try {
Parent root = FXMLLoader.load(getClass().getResource("SecondScreen.fxml"));
Scene dashboard=new Scene(root);
//This line gets the Stage Information
Stage window=(Stage)((Node)event.getSource()).getScene().getWindow();
window.setScene(dashboard);
window.show();
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public class SecondScreenController {
public void MoveBack(ActionEvent event){
try {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene dashboard = new Scene(root);
//This line gets the Stage Information
//here we get the stage from event action and setting the root element in the scene and display scene with stage object (window) which is retrieved from action event
Stage window=(Stage)((Node)event.getSource()).getScene().getWindow();
window.setScene(dashboard);
window.show();
} catch (IOException ex) {
Logger.getLogger(SecondScreenController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
//FXMLDocument.xml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchscene.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>
////SecondScreen fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="switchscene.SecondScreenController">
<children>
<Button fx:id="btnMove" layoutX="269.0" layoutY="188.0" mnemonicParsing="false" onAction="#MoveBack" text="Move Back" />
</children>
</AnchorPane>

Unable to get textfield input [JavaFX]

I am unable to extract user input from textfield in javafx.
When I click the button, I want the text from the textfield should be printed in console but it displays nothing. Here is a screenshot: 1
Controller Class:
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private TextField textField;
#FXML
private Button button;
#FXML
private void handleButtonAction(ActionEvent event) {
textField = new TextField();
System.out.println("You clicked me!" + textField.getText());
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="javafxapplication4.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="126" layoutY="90"
onAction="#handleButtonAction" text="Click Me!" />
<Label fx:id="label" layoutX="126" layoutY="120" minHeight="16"
minWidth="69" />
<TextField fx:id="textField" layoutX="86.0" layoutY="137.0" />
</children>
</AnchorPane>
What is the mistake, and can you provide an explanation if possible?
Thank You.
The problem is here textField = new TextField(); you are redefining the textField instance that's why you're getting empty value, but you're already instanciating the TextField with the #FXML annotation so to retrieve the text inside that textField (already defined before) change your method to this :
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!" + textField.getText());
label.setText("Hello World!");
}

javafx scene builder custom component

I would like to use the custom component in the scene builder.
I want to embed a canvas to custom component. so i try to change the canvas of attributes .
canvas code like this:
package test;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class DrawCanvas extends Canvas{
public DrawCanvas() {
draw();
}
private void draw() {
// TODO Auto-generated method stub
double width = getWidth();
double height = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.strokeLine(0,0,50,50);
}
}
Custom Component code like this:
package test;
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
public class Test extends BorderPane{
public Test() {
super();
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Test.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
}
fxml file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.canvas.Canvas?>
<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.BorderPane">
<center>
</center>
</fx:root>
I've tried in this way , but it failed.
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.canvas.Canvas?>
<?import org.korecky.myjavafx.fxml10.DrawCanvas?>
<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.BorderPane">
<center>
<DrawCanvas ></DrawCanvas>
</center>
</fx:root>
Please give me advice and tips .
Your approach is working for me, but you need to create a valid canvas, providing some dimensions, otherwise those will be 0x0. For instance:
private void draw() {
setWidth(50);
setHeight(50);
GraphicsContext gc = getGraphicsContext2D();
gc.strokeLine(0,0,50,50);
}
Now you can import your DrawCanvas component into SceneBuilder, as #jewelsea suggests, and you'll be able to drag it to your scene:
You could add some properties to the class, like canvasWidth and canvasHeight.
public class DrawCanvas extends Canvas {
private final GraphicsContext gc;
public DrawCanvas() {
gc = getGraphicsContext2D();
draw();
}
private void draw() {
setWidth(canvasWidth.get());
setHeight(canvasHeight.get());
gc.clearRect(0,0,canvasWidth.get(),canvasHeight.get());
gc.strokeLine(0,0,canvasWidth.get(),canvasHeight.get());
}
private final DoubleProperty canvasWidth = new SimpleDoubleProperty(50){
#Override
protected void invalidated() {
draw();
}
};
public double getCanvasWidth() {
return canvasWidth.get();
}
public void setCanvasWidth(double value) {
canvasWidth.set(value);
}
public DoubleProperty canvasWidthProperty() {
return canvasWidth;
}
private final DoubleProperty canvasHeight = new SimpleDoubleProperty(50){
#Override
protected void invalidated() {
draw();
}
};
public double getCanvasHeight() {
return canvasHeight.get();
}
public void setCanvasHeight(double value) {
canvasHeight.set(value);
}
public DoubleProperty canvasHeightProperty() {
return canvasHeight;
}
}
This will allow you to set them on the Inspector panel:
or in your fxml files:
<fx:root type="javafx.scene.layout.BorderPane" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
<center>
<DrawCanvas canvasWidth="150" canvasHeight="250" />
</center>
</fx:root>

javafx : pass parameter to the eventhandler function in fxml file

Is there a way to call the event handler method from the fxml file which holds parameter? please find the files:
My Fxml Looks like:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.cognizant.iotworkbench.View.AddImageController">
<children>
<Label layoutX="270.0" layoutY="14.0" text="Add Sensor" />
<BorderPane layoutY="-1.0" prefHeight="400.0" prefWidth="602.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<left>
<HBox fx:id="source" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<children>
<ImageView fx:id="sourceimg" fitHeight="114.0" fitWidth="142.0" onDragDetected="#setUpGestureSource" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="file:Images/Project.png" />
</image>
</ImageView>
</children>
</HBox>
</left>
<right>
<HBox fx:id="target" onDragDropped="#setUpGestureTarget" onDragOver="#setUpGestureTarget" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</right>
</BorderPane>
</children>
</AnchorPane>
My controller class looks like
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
/**
* FXML Controller class
*
*/
public class AddImage implements Initializable,ControlledScreen {
ScreensController myController;
/**
* Initializes the controller class.
*/
#FXML
final HBox target=new HBox();
#FXML
final HBox source=new HBox();
#FXML
final ImageView sourceimg=new ImageView();
#FXML
public void setUpGestureSource()
{
System.out.println("source");
source.setOnDragDetected(new EventHandler <MouseEvent>() {
#Override
public void handle(MouseEvent event) {
/* drag was detected, start drag-and-drop gesture*/
System.out.println("onDragDetected");
/* allow MOVE transfer mode */
Dragboard db = source.startDragAndDrop(TransferMode.COPY);
/* put a string on dragboard */
ClipboardContent content = new ClipboardContent();
Image sourceImage = sourceimg.getImage();
content.putImage(sourceImage);
db.setContent(content);
event.consume();
}
});
}
#FXML
public void setUpGestureTarget(){
System.out.println("target");
target.setOnDragOver(new EventHandler <DragEvent>() {
#Override
public void handle(DragEvent event) {
Dragboard db = event.getDragboard();
if(db.hasImage()){
event.acceptTransferModes(TransferMode.COPY);
}
event.consume();
}
});
target.setOnDragDropped(new EventHandler <DragEvent>() {
#Override
public void handle(DragEvent event) {
Dragboard db = event.getDragboard();
if(db.hasImage()){
insertImage(db.getImage(), target);
event.setDropCompleted(true);
}else{
event.setDropCompleted(false);
}
event.consume();
}
});
}
void insertImage(Image i, HBox hb){
ImageView iv = new ImageView();
iv.setImage(i);
setUpGestureSource();
hb.getChildren().add(iv);
}
#Override
public void setScreenParent(ScreensController screenParent) {
myController=screenParent;
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Drag and Drop event is not getting fired. I dont know what's wrong with the code. Please help
All methods that are annotated with #FXML may or may not have a parameter. If you need a parameter, you can directly pass the specific Event as a parameter to the method.
Let us take an example and say you have an onDragOver event added for a HBox in the fxml.
<HBox fx:id="targetBox" onDragOver="#setupGestureTarget">
...
</HBox>
Then you may have a method which does the needful inside the controller class.
#FXML
public void setupGestureTarget(DragEvent event) {
Dragboard db = event.getDragboard();
if(db.hasImage()){
event.acceptTransferModes(TransferMode.COPY);
}
event.consume();
}
If you need the source node for which the method was called, you can get an access to it by getSource() of the event, then type-caste it to Node.
public void setupGestureTarget(DragEvent event) {
...
((Node)event.getSource()).setId("myHbox");
System.out.println(targetBox.getId()); // Prints - myHbox
}
Note : For using methods specific to HBox, you can type-caste it to a HBox instead of a Node.
EDIT - As per user comments and updates
There are multiple issues with your approach. Let me address them all.
1.Values to the fields annotated with #FXML are injected while the FXML is getting loaded and should not carry new keyword.
#FXML
final HBox target=new HBox();
should be replaced by
#FXML
final HBox target;
2.As I have already stated your method can have a parameter which defines the Event. So the methods setUpGestureSource() and setUpGestureTarget() can be defined as :
#FXML
public void setUpGestureSource(DragEvent event) {
...
}
#FXML
public void setUpGestureTarget(DragEvent event) {
...
}
3.The method setUpGestureSource is called when a drag-event occurs on the ImageView, so you don't need to add another EventHandler for it.
#FXML
public void setUpGestureSource(MouseEvent event) {
/* drag was detected, start drag-and-drop gesture*/
System.out.println("onDragDetected");
* allow MOVE transfer mode */
Dragboard db = source.startDragAndDrop(TransferMode.COPY);
/* put a string on dragboard */
ClipboardContent content = new ClipboardContent();
Image sourceImage = sourceimg.getImage();
content.putImage(sourceImage);
db.setContent(content);
event.consume();
}
Similarly, change the other method as well.

Resources