I want to create a switch button like above
I am a swt developer where i used to get this widget Switchbutton.
Can i have something similar in javafx
First inclination is to extend the JavaFX Label and add a Button as a graphic and a SimpleBooleanProperty for listening. Set an ActionEvent handler on the button that toggles the Label's text, style, and graphic content alignment. The code below will get you started and you can play with styling and bounding.
package switchbutton;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
public class SwitchButton extends Label
{
private SimpleBooleanProperty switchedOn = new SimpleBooleanProperty(true);
public SwitchButton()
{
Button switchBtn = new Button();
switchBtn.setPrefWidth(40);
switchBtn.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent t)
{
switchedOn.set(!switchedOn.get());
}
});
setGraphic(switchBtn);
switchedOn.addListener(new ChangeListener<Boolean>()
{
#Override
public void changed(ObservableValue<? extends Boolean> ov,
Boolean t, Boolean t1)
{
if (t1)
{
setText("ON");
setStyle("-fx-background-color: green;-fx-text-fill:white;");
setContentDisplay(ContentDisplay.RIGHT);
}
else
{
setText("OFF");
setStyle("-fx-background-color: grey;-fx-text-fill:black;");
setContentDisplay(ContentDisplay.LEFT);
}
}
});
switchedOn.set(false);
}
public SimpleBooleanProperty switchOnProperty() { return switchedOn; }
}
Couldn't this be done with two toggle buttons that are bound together (bind()) where each button get's it's own CSS styling? It actually seems like the CSS would be the tricky (but doable) part to get right.
Then you would just have your app listen to the toggle button of the two that actually does what you want?
The controls FX project provides a switch button like you seek.
Related
I use javafx, I have a TextField and a Button, when the button is pressed, it saves what is written in the TextField in a String. What I want to create is a method to mark a pause, while waiting for the Button to get pressed.
I have a class named pause.java, where I tried to put a obj.wait(); and a notifyAll(); in the event where the button is pressed, but the window isn't accessible during this time, I can't press the button or enter anything in the TextField.
So what I found was to put the obj.wait(); in a task, then I don't know why but it directly breaks out of the wait.
Here is my pause.java
package net.jpajavafx;
import java.util.logging.*;
import javafx.concurrent.Task;
public class pause {
Logger logger = Logger.getLogger(pause.class.getName());
MainController obj = new MainController();
public void waitinput() {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
synchronized (obj) {
try {
String write = "Waiting for input...";
logger.log(Level.INFO, write);
obj.wait();
logger.log(Level.INFO, "Done");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
};
new Thread(sleeper).start();
}
}
How do I have to modify it to make it wait, while still having access to the GUI?
Here's my code simplified for the problem:
AlbumManager.java, where my main is.
package net.jpajavafx;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.*;
import javafx.application.*;
import javafx.fxml.FXMLLoader;
public class AlbumManager extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("Album Manager");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java:
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
notifyAll();
}
public void startprogram() {
int etat = 0;
int run = 1;
while (run == 1) {
textarea.setText("1: launch method");
pause.waitinput(); // here I want to wait for an input
etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
}
It's really not clear what you're trying to achieve here that needs a separate thread: all the separate thread seems to try to do is wait until the button is pressed, and then execute some code. That functionality is already provided by the event management system in JavaFX (and the same is true for any UI toolkit): just execute the code in the event handler.
(As an aside, your use of wait() is incorrect, and if you fix that, the thread will never wake up because you are not calling notifyAll() on the same object on which you are calling wait().)
You can achieve what you seem to be trying to do simply with
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
processInput();
}
public void processInput() {
int etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
You have to start another thread using a Runnable, so your UI thread does not get blocked while the save-operation completes.
You can do this by placing a listener on the button that will start the save-operation on a new thread when the button is clicked.
The code for adding a listener to a button that starts a new thread would look something like this:
//Creating the mouse event handler
EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
MainController controller = new MainController();
controller.start();
}
};
//Registering the event filter
button.addEventFilter(MouseEvent.MOUSE_CLICKED, eventHandler);
The code you posted doesn't really do anything. Your call to waitinput() only logs and calls wait(). wait() is not what you want, since this operation is intended for putting a thread on hold until it is notified, not for executing a task in a seperate thread. Remove the obj.wait(), and add a listener that calls your logging method when the button is clicked. Also, get rid of the while-loop. The EventHandler will take care of events in the background.
I want to create a custom Dialog, which just displays options (see figure 1). If the user selects one of those options, the dialog should close and return the corresponding result instantly.
So far, I can only accomplish this by adding an arbitrary ButtonType to the Dialog, hiding it by using setVisible(false) and applying fire() in the EventHandler of the clicked option.
This weird workaround actually works fine, but seems to me very unprofessional ...
So, how to do this in a more professional or proper way without using the ButtonType trick?
My workaround-code looks like this (Dialog class):
public class CustomDialog extends Dialog<String> {
private static final String[] OPTIONS
= new String[]{"Option1", "Option2", "Option3", "Option4"};
private String selectedOption = null;
Button applyButton;
public CustomDialog() {
super();
initStyle(StageStyle.DECORATED);
VBox vBox = new VBox();
for (String option : OPTIONS) {
Button optionButton = new Button(option);
optionButton.setOnAction((event) -> {
selectedOption = option;
applyButton.fire();
});
vBox.getChildren().add(optionButton);
}
getDialogPane().setContent(vBox);
getDialogPane().getButtonTypes().add(ButtonType.APPLY);
applyButton = (Button) getDialogPane().lookupButton(ButtonType.APPLY);
applyButton.setVisible(false);
setResultConverter((dialogButton) -> {
return selectedOption;
});
}
}
Using the dialog class:
CustomDialog dialog = new CustomDialog();
Optional<String> result = dialog.showAndWait();
String selected = null;
if (result.isPresent()) {
selected = result.get();
} else if (selected == null) {
System.exit(0);
}
A Dialog is just a window displaying a DialogPane, and, quoting the Javadocs for DialogPane:
DialogPane operates on the concept of ButtonType. A ButtonType is a
descriptor of a single button that should be represented visually in
the DialogPane. Developers who create a DialogPane therefore must
specify the button types that they want to display
(my emphasis). Therefore, while you've shown one possible workaround and in the other answer Slaw has shown another, if you're trying to use a Dialog without using ButtonType and its associated result converter, you're really using the Dialog class for something for which it's not intended.
The functionality you describe is perfectly achievable with a regular modal Stage. For example, the following gives the same basic behavior you describe and involves no ButtonTypes:
package org.jamesd.examples.dialog;
import java.util.Optional;
import java.util.stream.Stream;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
public class CustomDialog {
private static final String[] OPTIONS
= {"Option 1", "Option 2", "Option 3", "Option 4"};
private final Stage stage ;
private String selectedOption = null ;
public CustomDialog() {
this(null);
}
public CustomDialog(Window parent) {
var vbox = new VBox();
// Real app should use an external style sheet:
vbox.setStyle("-fx-padding: 12px; -fx-spacing: 5px;");
Stream.of(OPTIONS)
.map(this::createButton)
.forEach(vbox.getChildren()::add);
var scene = new Scene(vbox);
stage = new Stage();
stage.initOwner(parent);
stage.initModality(Modality.WINDOW_MODAL);
stage.setScene(scene);
}
private Button createButton(String text) {
var button = new Button(text);
button.setOnAction(e -> {
selectedOption = text ;
stage.close();
});
return button ;
}
public Optional<String> showDialog() {
selectedOption = null ;
stage.showAndWait();
return Optional.ofNullable(selectedOption);
}
}
Here's a simple application class which uses this custom dialog:
package org.jamesd.examples.dialog;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) throws Exception {
var root = new VBox();
// Real app should use an external style sheet:
root.setStyle("-fx-padding: 12px; -fx-spacing: 5px;");
var showDialog = new Button("Show dialog");
var label = new Label("No option chosen");
showDialog.setOnAction(e ->
new CustomDialog(stage)
.showDialog()
.ifPresentOrElse(label::setText, Platform::exit));
root.getChildren().addAll(showDialog, label);
stage.setScene(new Scene(root));
stage.show();
}
}
As pointed out by both #Sedrick and #James_D, the Dialog API is built around the concept of "button types". Not using ButtonType goes against the API and, because of this, will always seem hacky/wrong. That said, there is a slight alteration you could make to your current code that satisfies your "without using any 'ButtonType'-controls" goal. It doesn't appear to be documented, but if you set the result property manually it triggers the close-and-return-result process. This means you don't need to add any ButtonType and can bypass the resultConverter completely. Here's a proof-of-concept:
OptonsDialog.java:
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Dialog;
import javafx.scene.layout.VBox;
public class OptionsDialog<T extends OptionsDialog.Option> extends Dialog<T> {
public interface Option {
String getDisplayText();
}
#SafeVarargs
public OptionsDialog(T... options) {
if (options.length == 0) {
throw new IllegalArgumentException("must provide at least one option");
}
var content = new VBox(10);
content.setAlignment(Pos.CENTER);
content.setPadding(new Insets(15, 25, 15, 25));
for (var option : options) {
var button = new Button(option.getDisplayText());
button.setOnAction(
event -> {
event.consume();
setResult(option);
});
content.getChildren().add(button);
}
getDialogPane().setContent(content);
}
}
App.java:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Window;
public class App extends Application {
private enum Foo implements OptionsDialog.Option {
OPTION_1("Option Number 1"),
OPTION_2("Option Number 2"),
OPTION_3("Option Number 3"),
OPTION_4("Option Number 4");
private final String displayText;
Foo(String displayText) {
this.displayText = displayText;
}
#Override
public String getDisplayText() {
return displayText;
}
}
#Override
public void start(Stage primaryStage) {
var button = new Button("Click me!");
button.setOnAction(
event -> {
event.consume();
showChosenOption(primaryStage, promptUserForOption(primaryStage));
});
primaryStage.setScene(new Scene(new StackPane(button), 500, 300));
primaryStage.show();
}
private static Foo promptUserForOption(Window owner) {
var dialog = new OptionsDialog<>(Foo.values());
dialog.initOwner(owner);
dialog.setTitle("Choose Option");
return dialog.showAndWait().orElseThrow();
}
private static void showChosenOption(Window owner, OptionsDialog.Option option) {
var alert = new Alert(AlertType.INFORMATION);
alert.initOwner(owner);
alert.setHeaderText("Chosen Option");
alert.setContentText(String.format("You chose the following: \"%s\"", option.getDisplayText()));
alert.show();
}
}
It's not that different from your current workaround and it's still working against the API. This also relies on undocumented behavior (that setting the result property manually closes the dialog and returns the result). The ButtonBar at the bottom still takes up some space, though less than when you add an invisible button. It's possible, however, to get rid of this empty space by adding the following CSS:
.options-dialog-pane .button-bar {
-fx-min-height: 0;
-fx-pref-height: 0;
-fx-max-height: 0;
}
Note the above assumes you've modified the code to add the "options-dialog-pane" style class to the DialogPane used with the OptionsDialog.
I think you should read the following from the Java Docs:
Dialog Closing Rules:
It is important to understand what happens when a Dialog is closed, and also how a Dialog can be closed, especially in abnormal closing situations (such as when the 'X' button is clicked in a dialogs title bar, or when operating system specific keyboard shortcuts (such as alt-F4 on Windows) are entered). Fortunately, the outcome is well-defined in these situations, and can be best summarised in the following bullet points:
JavaFX dialogs can only be closed 'abnormally' (as defined above) in
two situations:
When the dialog only has one button, or
When the dialog has multiple buttons, as long as one of them meets one of the following requirements:
The button has a ButtonType whose ButtonBar.ButtonData is of type ButtonBar.ButtonData.CANCEL_CLOSE.
The button has a ButtonType whose ButtonBar.ButtonData returns true when ButtonBar.ButtonData.isCancelButton() is called.
In all other situations, the dialog will refuse to respond to all close requests, remaining open until the user clicks on one of the available buttons in the DialogPane area of the dialog.
If a dialog is closed abnormally, and if the dialog contains a button which meets one of the two criteria above, the dialog will attempt to set the result property to whatever value is returned from calling the result converter with the first matching ButtonType.
If for any reason the result converter returns null, or if the dialog is closed when only one non-cancel button is present, the result property will be null, and the showAndWait() method will return Optional.empty(). This later point means that, if you use either of option 2 or option 3 (as presented earlier in this class documentation), the Optional.ifPresent(java.util.function.Consumer) lambda will never be called, and code will continue executing as if the dialog had not returned any value at all.
If you don't mind the Buttons being horizontal, you should use ButtonType and setResultConverter to return a String based on which button is pressed.
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.stage.StageStyle;
import javafx.util.Callback;
/**
*
* #author blj0011
*/
public class CustomDialog extends Dialog<String>
{
String result = "";
public CustomDialog()
{
super();
initStyle(StageStyle.DECORATED);
setContentText(null);
setHeaderText(null);
ButtonType buttonOne = new ButtonType("Option1");
ButtonType buttonTwo = new ButtonType("Option2");
ButtonType buttonThree = new ButtonType("Option3");
ButtonType buttonFour = new ButtonType("Option4");
getDialogPane().getButtonTypes().addAll(buttonOne, buttonTwo, buttonThree, buttonFour);
setResultConverter(new Callback<ButtonType, String>()
{
#Override
public String call(ButtonType param)
{
if (param == buttonOne) {
return buttonOne.getText();
}
else if (param == buttonTwo) {
return buttonTwo.getText();
}
else if (param == buttonThree) {
return buttonThree.getText();
}
else if (param == buttonFour) {
return buttonFour.getText();
}
return "";
}
});
}
}
Update: As #Slaw stated in the comments, you can replace setResultConverter(...) with setResultConverter(ButtonType::getText).
I'm using javaFX to create an application.
I have a hyper-link somewhere and I've set an (onAction) for it as shown below
Hyperlink studentList = ...; // It's given proper object
studentList.setOnAction(...);
now somewhere else i used this method to simluate a click on this hyperlink
studentList.fire();
now my problem is that how can i distinguish real click/keyPress from fire() method ?
Here's one way to do it. Just add an EventHandler to the setOnMousePressed property. Be sure to add it to setOnMousePressed and not e.g. setOnMouseClicked, since setOnMousePressed is invoked before the fire() is invoked while setOnMouseClicked is invoked after.
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MCVE extends Application {
#Override
public void start(Stage stage) {
VBox content = new VBox(5);
content.setPadding(new Insets(10));
Hyperlink link = new Hyperlink("Hyperlink");
Button fireButton = new Button("Fire hyperlink");
fireButton.setOnAction(e -> link.fire());
BooleanProperty mouseClicked = new SimpleBooleanProperty(false);
link.setOnMousePressed(e -> {
System.out.println("Mouse click");
mouseClicked.set(true);
});
link.setOnAction(e -> {
if (!mouseClicked.get()) {
System.out.println("No mouse click");
}
mouseClicked.set(false);
});
content.getChildren().addAll(link, fireButton);
stage.setScene(new Scene(content));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I am trying to create my very first application in JavaFX and I have a problem with Button that calls a method (for example to open another window) - I always have to click it twice in order to trigger action.
Here's my code from the Controller:
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class ControllerSignIn {
#FXML
private Button forgot;
#FXML
private Button back;
#FXML
private Button signin;
public void forgetPasswordClicked() {
forgot.setOnAction(e -> ForgotPassword.setUpWindow()); //works on 2nd click
}
public void backClicked() {
back.setOnAction(e -> ForgotPassword.closeWindow()); //works on 2nd click
}
public void signInClicked() {
System.out.println("Sign In CLICKED"); //works on first click
}
}
My methods are implemented here:
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
public class ForgotPassword {
static Stage window;
static Scene scene;
static Parent root;
private static void loadFXML() {
try {
root = FXMLLoader.load(ForgotPassword.class.getResource("ForgotPassword.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void setUpWindow() {
loadFXML();
scene = new Scene(root);
scene.getStylesheets().add("signin/SignIn.css");
window = new Stage();
window.initModality(Modality.APPLICATION_MODAL);
window.setTitle("Forgot Password?");
window.setScene(scene);
window.showAndWait();
}
public static void closeWindow() {
window.close();
}
}
Most likely you have the following in your FXML:
<Button fx:id="forgot" onAction="#forgetPasswordClicked" />
This makes your button forgot call your method forgetPasswordClicked(). But instead of defining your logic to be executed when your button is clicked, the first time you say: "When this button is clicked, place an action event on my button which will call setUpWindow()"
forgot.setOnAction(e -> ForgotPassword.setUpWindow());
Therefore, your first click "sets up" the logic of your button. The second click, actually executes it. To solve this, either immediately use your logic as such:
public void forgetPasswordClicked() {
ForgotPassword.setUpWindow();
}
or don't define the method to be called in your fxml, and move the initialization of your button (setting the action listener) to your initialization as following:
public class ControllerSignIn implements Initializable {
#FXML
private Button forgot;
#FXML
private Button back;
#Override
public void initialize(URL location, ResourceBundle resources) {
forgot.setOnAction(e -> ForgotPassword.setUpWindow());
back.setOnAction(e -> ForgotPassword.closeWindow());
}
}
This is also why your signInClicked() method works from the first click, because it actually executes the logic instead of setting up the handler first.
I'm trying to stop the arrow keys from navigating through the controls i have in my example. I'm not sure how to do this. Here is the example i have created:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class KeyEventTest extends Application{
private EventHandler<KeyEvent> keyEventHandler;
#Override
public void start(Stage stage) throws Exception {
// TODO Auto-generated method stub
Group root = new Group();
FlowPane f = new FlowPane();
Button r = new Button("button1");
Button r2 = new Button("button1");
Button r3 = new Button("button1");
f.getChildren().addAll(r,r2,r3);
root.getChildren().add(f);
Scene scene = new Scene(root,600,600);
keyEventHandler = new EventHandler<KeyEvent>() {
public void handle(final KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.LEFT || keyEvent.getCode() == KeyCode.UP) {
System.out.println("arrow keys");
}else{
System.out.println(keyEvent);
}
}
};
stage.addEventHandler(KeyEvent.KEY_PRESSED, keyEventHandler);
stage.setScene(scene);
stage.show();
}
public static void main(String args[]){
Application.launch(args);
}
}
Any help would be appreciated
Thanks
I had a similar Problem and found an easy workaround. Since the controls navigating Part triggers after the self-implemented EventHandler you can simply stop any further propagation of the KeyEvent by calling
keyevent.consume()
at the end of your handle() method.
First, the answer on this question is similar to answer on this question : JavaFX: How to change the focus traversal policy?
You can change traversal policy, so that traverse will not be done, for some situations.
The other decision - is to add listener of focused property, and drop focus back, when it is not wished (but it will work for all navigation keys):
node.focusedProperty().addListener(new ChangeListener<Boolean>(){
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
Platform.runLater(new Runnable(){
public void run() {
node.requestFocus();
}
});
}
});
You should use runLater, because of such issue : JavaFx: After dialog, two textfields gains focus instead one
The way how you do this - handler on key press may not work always, because actions may happen on key release too.
In common case, behavior on key presses is determined in behavior class, which is accessible by skin class. If you want to change behavior on keys pressing, you can change behavior class.