I have this class that generates a Alert Dialog with a field to enter a password, and want to activate the OK button when pressing Enter on the password field.
public class PasswordDialog extends Dialog<String> {
private PasswordField passwordField;
public PasswordDialog(boolean usuario) {
setTitle("Senha");
if (usuario == true){
setHeaderText("Por favor insira a senha do usuário.");
}else{
setHeaderText("Por favor insira a senha do administrador.");
}
ButtonType passwordButtonType = new ButtonType("OK", ButtonData.OK_DONE);
getDialogPane().getButtonTypes().addAll(passwordButtonType, ButtonType.CANCEL);
passwordField = new PasswordField();
passwordField.setPromptText("Password");
HBox hBox = new HBox();
hBox.getChildren().add(passwordField);
hBox.setPadding(new Insets(20));
HBox.setHgrow(passwordField, Priority.ALWAYS);
getDialogPane().setContent(hBox);
Platform.runLater(() -> passwordField.requestFocus());
setResultConverter(dialogButton -> {
if (dialogButton == passwordButtonType) {
return passwordField.getText();
}
return null;
});
}
public PasswordField getPasswordField() {
return passwordField;
}
}
Actually this should happen by default (at least that's the behaviour on JavaFX 11/Win 10), but you can also close the Dialog yourself by calling setResult and close.
Example closing on arrow keys:
// in constructor
passwordField.setOnKeyPressed(evt -> {
if (evt.getCode().isArrowKey()) {
setResult(passwordField.getText());
close();
}
});
For closing on pressing enter, use the onAction event of the PasswordField:
// in constructor
passwordField.setOnAction(evt -> {
setResult(passwordField.getText());
close();
});
For more complicated behaviour of the resultConverter, you could also use it for setting the result to avoid duplicate code:
setResult(getResultConverter().call(passwordButtonType));
Related
I might be missing something very obvious, but I can't find out how to set the Icon for a Dialog component (ProgressDialog to be more precise). I know how to do that for a Stage:
this.primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/icon/Logo.png")));
But I don't find anything for the Dialog family. And somehow, setting the Stage Icon does not influence the Dialog Icon.
Thanks
There's an excellent tutorial here by Marco Jakob, where you can find not only how to use dialogs, but also how to solve your problem.
Both for the new dialogs (in JDK8u40 early versions or with openjfx-dialogs with JDK 8u25), or for those in ControlsFX, in order to set the icon of your dialog, you can use this solution:
Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
stage.getIcons().add(
new Image(this.getClass().getResource("<image>.png").toString()));
This code snippet shows how to use a ProgressDialog, from ControlsFX, and set an icon for the dialog:
#Override
public void start(Stage primaryStage) {
Service<Void> service = new Service<Void>() {
#Override protected Task<Void> createTask() {
return new Task<Void>() {
#Override protected Void call() throws InterruptedException {
updateMessage("Message . . .");
updateProgress(0, 10);
for (int i = 0; i < 10; i++) {
Thread.sleep(300);
updateProgress(i + 1, 10);
updateMessage("Progress " + (i + 1) + " of 10");
}
updateMessage("End task");
return null;
}
};
}
};
Button btn = new Button("Start Service");
btn.setOnAction(e -> {
ProgressDialog dialog = new ProgressDialog(service);
dialog.setTitle("Progress Dialog");
dialog.setHeaderText("Header message");
Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
stage.getIcons().add(new Image(this.getClass().getResource("<image>.png").toString()));
service.start();
});
Scene scene = new Scene(new StackPane(btn), 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
Just Do like this:
Alert(AlertType.ERROR, "Erreur de connexion! Verifiez vos Identifiants",FINISH); //Cancel..
setTitle("XNotes FX Erreur");
stage = (Stage) alert.getDialogPane().getScene().getWindow();
stage.getIcons().add(new Image("indiza/XnotesErrorIdz.png")); // To add an icon
showAndWait();
Here is the result
**My friends, is it computer science that we do? : No, we do crafts
**
You can easily use the icon of your application for the alert-icon by setting your application-window as owner of the alert box:
#FXML
Button buShow;
...
Alert alert = new Alert(AlertType.INFORMATION, "Nice Box.", ButtonType.CLOSE);
alert.initOwner(buShow.getScene().getWindow()); // Alert uses the Windows Icon
alert.show();
This is a method that I include in my JavaFX projects, simply calling this method and passing the Alert as a parameter will set both the title bar icon and the header graphic.
public class Msg {
public void showInfo(String title, String header, String message) {
Alert alertShowInfo = new Alert(Alert.AlertType.INFORMATION);
addDialogIconTo(alertShowInfo); //add icon and header graphic
alertShowInfo.setTitle(title);
alertShowInfo.setHeaderText(header);
alertShowInfo.setContentText(message);
alertShowInfo.showAndWait();
}
//this adds images to Alert
public void addDialogIconTo(Alert alert) {
// Add custom Image to Dialog's title bar
final Image APPLICATION_ICON = new Image("icon.png");
Stage dialogStage = (Stage) alert.getDialogPane().getScene().getWindow();
dialogStage.getIcons().add(APPLICATION_ICON);
// Add custom ImageView to Dialog's header pane.
final ImageView DIALOG_HEADER_ICON = new ImageView("icon.png");
DIALOG_HEADER_ICON.setFitHeight(48); // Set size to API recommendation.
DIALOG_HEADER_ICON.setFitWidth(48);
alert.getDialogPane().setGraphic(DIALOG_HEADER_ICON);
}
}
Then, in whatever class I wish to use the Alert, it will already have the customized icon and header graphic.
public static void main(String[] args){
Msg msg = new Msg();
// Alert will now include custom icon and header graphic.
msg.showInfo("Sucess!", "Program succeeded", "Now exiting program");
}
Just similar to any dialog, instead this is inside a button handler.
Alert alert = new Alert(
AlertType.WARNING,
"Alert message here.",
ButtonType.OK
);
alert.initOwner(((Button)event.getSource()).getScene().getWindow());
alert.setTitle("Alert window title");
alert.showAndWait();
So I followed this example on using context menu with TableViews from here. I noticed that using this code
row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)
.otherwise((ContextMenu)null));
does not show up on first right click on a row with values. I need to right click on that row again for the context menu to show up. I also tried this code(which is my first approach, but not using it anymore because I've read somewhere that that guide is the best/good practice for anything related about context menu and tableview), and it displays the context menu immediately
if (row.getItem() != null) {
rowMenu.show(row, event.getScreenX(), event.getScreenY());
}
else {
// do nothing
}
but my problem with this code is it throws a NullPointerException whenever i try to right click on a row that has no data.
What could I possibly do to prevent NullPointerException while having the context menu show up immediately after a right click? In my code, I also have a code that a certain menu item in the context menu will be disabled based on the property of the myObject binded to row, that's why i need the context menu to pop up right away.
I noticed this too with the first block of code. Even if the property of myObject has already changed, it still has a menu item enabled/disabled unless I right click on that row again. I hope that you could help me. Thank you!
Here is a MCVE:
public class MCVE_TableView extends Application{
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane myBorderPane = new BorderPane();
TableView<People> myTable = new TableView<>();
TableColumn<People, String> nameColumn = new TableColumn<>();
TableColumn<People, Integer> ageColumn = new TableColumn<>();
ContextMenu rowMenu = new ContextMenu();
ObservableList<People> peopleList = FXCollections.observableArrayList();
peopleList.add(new People("John Doe", 23));
nameColumn.setMinWidth(100);
nameColumn.setCellValueFactory(
new PropertyValueFactory<>("Name"));
ageColumn.setMinWidth(100);
ageColumn.setCellValueFactory(
new PropertyValueFactory<>("Age"));
myTable.setItems(peopleList);
myTable.getColumns().addAll(nameColumn, ageColumn);
myTable.setRowFactory(tv -> {
TableRow<People> row = new TableRow<>();
row.setOnContextMenuRequested((event) -> {
People selectedRow = row.getItem();
rowMenu.getItems().clear();
MenuItem sampleMenuItem = new MenuItem("Sample Button");
if (selectedRow != null) {
if (selectedRow.getAge() > 100) {
sampleMenuItem.setDisable(true);
}
rowMenu.getItems().add(sampleMenuItem);
}
else {
event.consume();
}
/*if (row.getItem() != null) { // this block comment displays the context menu instantly
rowMenu.show(row, event.getScreenX(), event.getScreenY());
}
else {
// do nothing
}*/
// this requires the row to be right clicked 2 times before displaying the context menu
row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)
.otherwise((ContextMenu)null));
});
return row;
});
myBorderPane.setCenter(myTable);
Scene scene = new Scene(myBorderPane, 500, 500);
primaryStage.setTitle("MCVE");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main (String[] args) {
launch(args);
}
}
Here is the People Class
public class People {
SimpleStringProperty name;
SimpleIntegerProperty age;
public People(String name, int age) {
this.name = new SimpleStringProperty(name);
this.age = new SimpleIntegerProperty(age);
}
public SimpleStringProperty NameProperty() {
return this.name;
}
public SimpleIntegerProperty AgeProperty() {
return this.age;
}
public String getName() {
return this.name.get();
}
public int getAge() {
return this.age.get();
}
}
Edit: MCVE added
Edit2: Updated the MCVE. Still requires to be right-clicked twice before the contextMenu pops up
Below's a code snippet as a quick demonstration of how-to/where-to instantiate and configure a per-row ContextMenu. It
creates a ContextMenu/MenuItem for each TableRow at the row's instantiation time
creates a conditional binding that binds the menu to the row's contextMenuProperty if not empty (just the same as you did)
configures the contextMenu in an onShowing handler, depending on the current item (note: no need for a guard against null, because the conditional binding will implicitly guarantee to not show the the menu in that case)
The snippet:
myTable.setRowFactory(tv -> {
TableRow<People> row = new TableRow<>() {
ContextMenu rowMenu = new ContextMenu();
MenuItem sampleMenuItem = new MenuItem("Sample Button");
{
rowMenu.getItems().addAll(sampleMenuItem);
contextMenuProperty()
.bind(Bindings
.when(Bindings.isNotNull(itemProperty()))
.then(rowMenu).otherwise((ContextMenu) null));
rowMenu.setOnShowing(e -> {
People selectedRow = getItem();
sampleMenuItem.setDisable(selectedRow.getAge() > 100);
});
}
};
return row;
});
I have a JavaFX application which will initially show a Login dialog for user to key in user name and password. See below source codes.
If the user clicks on the "Connect" button, the application will perform the login with the entered user name and password, hides the Login dialog, and then shows the main window.
If the user clicks on the "Exit" button or the "X" close button, an alert will be shown to get the user's confirmation. If the user confirms, the application exits.
My problem is what happens when the user press the Escape key when the Login dialog is showing. When this key is pressed, the exit confirmation alert will be shown and immediately after that it will be closed. So what we see is the exit confirmation dialog showing up momentarily whenever the Escape key is pressed.
Why is this happening?
I want pressing the Escape key to be equivalent to clicking on the "Exit" or "X" button. That is, when Escape key is pressed, the exit confirmation dialog is shown.
Alternatively, is it possible to disable the Escape key altogether?
Thanks in advance.
public class TestApp extends Application {
private Stage primaryStage;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
this.primaryStage = stage;
HBox pane = new HBox();
pane.setAlignment(Pos.CENTER);
Scene scene = new Scene(pane, 300, 300);
stage.setScene(scene);
showLoginDialog();
}
public void showLoginDialog() {
Dialog<String> loginDialog = new Dialog<>();
loginDialog.setTitle("Login");
loginDialog.setHeaderText("Enter User Name and Password to login.");
loginDialog.setResizable(false);
Label userNameLabel = new Label("User Name:");
Label passwordLabel = new Label("Password:");
TextField userNameField = new TextField();
userNameField.setMaxWidth(Double.MAX_VALUE);
PasswordField passwordField = new PasswordField();
passwordField.setMaxWidth(Double.MAX_VALUE);
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 35, 20, 35));
grid.add(userNameLabel, 1, 1);
grid.add(userNameField, 2, 1);
grid.add(passwordLabel, 1, 2);
grid.add(passwordField, 2, 2);
loginDialog.getDialogPane().setContent(grid);
loginDialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
loginDialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Button connectButton = (Button) loginDialog.getDialogPane().lookupButton(ButtonType.OK);
connectButton.setText("Connect");
connectButton.addEventFilter(ActionEvent.ACTION, event -> {
// perform login here
loginDialog.hide();
primaryStage.show();
event.consume();
});
Button exitButton = (Button) loginDialog.getDialogPane().lookupButton(ButtonType.CANCEL);
exitButton.setText("Exit");
exitButton.addEventFilter(ActionEvent.ACTION, event -> {
handleExit();
event.consume();
});
Stage stage = (Stage) loginDialog.getDialogPane().getScene().getWindow();
stage.setOnCloseRequest(event -> {
handleExit();
event.consume();
});
stage.show();
}
private void handleExit() {
Alert alert = new Alert(AlertType.CONFIRMATION, "", ButtonType.YES, ButtonType.NO);
alert.setHeaderText("Confirm exit?");
alert.resultProperty().addListener((observable, previous, current) -> {
if (current == ButtonType.YES) {
System.exit(1);
}
});
alert.show();
}
}
See if this makes a difference:
private void handleExit() {
Alert alert = new Alert(AlertType.CONFIRMATION, "", ButtonType.YES, ButtonType.NO);
alert.setHeaderText("Confirm exit?");
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
Platform.exit();
}
alert.show();
}
Everything works fine until I create a new tab. Then when I go to the previous and try to use any of the buttons they affect the latest tab not the one I have selected. But if I go to the latest tab it works like normal. Here is the class that I use to make my tabs. So, why is the previous tabs affecting the lastest? And how do I fix it?
public class JTab {
private javafx.scene.control.Tab tab;
private ImageView imgView;
private Image logo;
private BorderPane root;
private Button reloadButton, backButton, forwardButton;
private TextField field;
private WebView view;
private WebEngine engine;
private static JTab instance;
private JBrowser jBrowser;
private JTab(JBrowser jBrowser) {
this.jBrowser = jBrowser;
}
public static JTab getInstance(JBrowser browser) {
if(instance == null)
instance = new JTab(browser);
return instance;
}
public javafx.scene.control.Tab addTab() {
tab = new Tab();
tab.setText("New Tab");
tab.setOnClosed(event2 -> {
if(jBrowser.getTabPane().getTabs().size() == 1) {
jBrowser.getTabPane().setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
}
});
logo = new Image("unknown-document.png");
imgView = new ImageView(logo);
tab.setGraphic(imgView);
HBox hBox = new HBox(5);
hBox.setAlignment(Pos.CENTER);
reloadButton = new Button("Reload");
backButton = new Button("<");
forwardButton = new Button(">");
reloadButton.setOnAction(event1 -> engine.reload());
backButton.setOnAction(event1 -> loadData(goBack()));
forwardButton.setOnAction(event1 -> loadData(goForward()));
//The TextField for entering web addresses.
field = new TextField("Enter URL");
field.setPrefColumnCount(50); //make the field at least 50 columns wide.
field.focusedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { //When click on field entire thing selected
Platform.runLater(() -> {
if (field.isFocused() && !field.getText().isEmpty()) {
field.selectAll();
}
});
});
field.setOnKeyPressed(event -> { //When ENTER is pressed it will load page
if (event.getCode() == KeyCode.ENTER) {
if (!field.getText().isEmpty()) {
loadData(field.getText());
}
}
});
//Add all out navigation nodes to the vbox.
hBox.getChildren().addAll(backButton, forwardButton, reloadButton, field);
view = new WebView();
engine = view.getEngine();
engine.setJavaScriptEnabled(true);
engine.getLoadWorker().stateProperty().addListener(
(ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
tab.setText(getTitle());
//TODO setGraphic
}
});
loadData("google.com");
root = new BorderPane();
root.setPrefSize(1024, 768);
root.setTop(hBox);
root.setCenter(view);
tab.setContent(root);
return tab;
}
public void loadData(String URL) {
if(!URL.startsWith("http://")) {
URL = "http://" + URL;
}
field.setText(URL);
tab.setText(URL);
engine.load(URL);
}
private String getTitle() {
Document doc = engine.getDocument();
NodeList heads = doc.getElementsByTagName("head");
String titleText = engine.getLocation() ; // use location if page does not define a title
if (heads.getLength() > 0) {
Element head = (Element)heads.item(0);
NodeList titles = head.getElementsByTagName("title");
if (titles.getLength() > 0) {
Node title = titles.item(0);
titleText = title.getTextContent();
}
}
return titleText;
}
private String goBack() {
final WebHistory history = engine.getHistory();
ObservableList<WebHistory.Entry> entryList = history.getEntries();
int currentIndex=history.getCurrentIndex();
Platform.runLater(() -> history.go(-1));
return entryList.get(currentIndex>0?currentIndex-1:currentIndex).getUrl();
}
private String goForward() {
final WebHistory history = engine.getHistory();
ObservableList<WebHistory.Entry> entryList=history.getEntries();
int currentIndex=history.getCurrentIndex();
Platform.runLater(() -> history.go(1));
return entryList.get(currentIndex<entryList.size()-1?currentIndex+1:currentIndex).getUrl();
}
}
Remove getInstance(jBrowser) method
Make the constructor public.
Then to add a tab to a tabPane do
tabPane.getTabs().add(new JTab(jBrowser).addTab());
A JavaFX MenuItem can respond to most KeyPress events by setting an ActionEvent EventHandler. However, while the event handler does catch a KeyPress of KeyCode.ENTER, it does not catch a KeyCode.TAB KeyPress event. Apparently, some key events like TAB are handled at a deeper level. For example, the arrow keys enable traversal of the menu.
My ContextMenu is a list of completions of an email address string the user has started typing in a TextField. The users want to press the arrow keys to select the desired item, and the TAB key to execute the completion.
I can attach an event handler to the ContextMenu itself and catch the TAB keypress. But the event's Source is then the ContextMenu, and I can find no variables in the ContextMenu indicating which MenuItem was highlighted when the TAB key was pressed. MenuItem allows css style to control appearance of the menu item in focus, but it does not have any properties telling whether it is in focus or not.
I have tried futzing with the EventDispatchChain via MenuItem buildEventDispatchChain() to no avail. There seems to be no way to intercept the TAB KeyPress or otherwise determine which menu item was in focus when the TAB key was pressed.
Any suggestions?
If I get this right, you want to override the default keypressed listener to add your own response, so for that we have to find where it's applied.
To get this working, we've got to get our hands dirty with private API...
ContextMenu skin (ContextMenuSkin) uses a ContextMenuContent object, as a container with all the items. Each of these items are also in a ContextMenuContent.MenuItemContainer container.
We can override the keypressed listener on the parent container, while we can add a focusedProperty listener to the items on the items container.
Using this private API
import com.sun.javafx.scene.control.skin.ContextMenuContent;
this is working for me:
private ContextMenuContent.MenuItemContainer itemSelected=null;
#Override
public void start(Stage primaryStage) {
MenuItem cmItem1 = new MenuItem("Item 1");
cmItem1.setOnAction(e->System.out.println("Item 1"));
MenuItem cmItem2 = new MenuItem("Item 2");
cmItem2.setOnAction(e->System.out.println("Item 2"));
final ContextMenu cm = new ContextMenu(cmItem1,cmItem2);
Scene scene = new Scene(new StackPane(), 300, 250);
scene.setOnMouseClicked(t -> {
if(t.getButton()==MouseButton.SECONDARY || t.isControlDown()){
cm.show(scene.getWindow(),t.getScreenX(),t.getScreenY());
ContextMenuContent cmc= (ContextMenuContent)cm.getSkin().getNode();
cmc.setOnKeyPressed(ke->{
switch (ke.getCode()) {
case UP: break;
case DOWN: break;
case TAB: ke.consume();
if(itemSelected!=null){
itemSelected.getItem().fire();
}
cm.hide();
break;
default: break;
}
});
VBox itemsContainer = cmc.getItemsContainer();
itemsContainer.getChildren().forEach(n->{
ContextMenuContent.MenuItemContainer item=(ContextMenuContent.MenuItemContainer)n;
item.focusedProperty().addListener((obs,b,b1)->{
if(b1){
itemSelected=item;
}
});
});
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
Excellent! Thank you #jose! I ended up writing somewhat different code but
the key is using com.sun.javafx.scene.control.skin.ContextMenuContent, which provides
access to the ContextMenuContent.MenuItemContainer objects that hold the MenuItems.
In order to not break the existing UP/DOWN key behavior, I added a new handler
to the ContextMenuContent object; this handler only consumes the TAB KeyPress and
everthing else passes through to their normal handlers.
Looking at the ContextMenuContent class, I borrowed their existing method for
finding the focused item, so didn't have to add focusedProperty listeners.
Also, I'm on Java 1.7 and don't have lambdas and I use a very basic programming style.
public class MenuItemHandler_CMC <T extends Event> implements EventHandler {
public ContextMenuContent m_cmc;
public AddressCompletionMenuItemHandler_CMC(ContextMenuContent cmc){
m_cmc = cmc;
}
#Override
public void handle(Event event){
KeyEvent ke = (KeyEvent)event;
switch(ke.getCode()){
case TAB:
ke.consume();
MenuItem focused_menu_item = findFocusedMenuItem();
if(focused_menu_item != null){
focused_menu_item.fire();
}
break;
default: break;
}
}
public MenuItem findFocusedMenuItem() {
VBox items_container = m_cmc.getItemsContainer();
for (int i = 0; i < items_container.getChildren().size(); i++) {
Node n = items_container.getChildren().get(i);
if (n.isFocused()) {
ContextMenuContent.MenuItemContainer menu_item_container = (ContextMenuContent.MenuItemContainer)n;
MenuItem menu_item = menu_item_container.getItem();
return menu_item;
}
}
return null;
}
}
...Attach the additional handler
if(m_context_menu.getSkin() != null){
ContextMenuContent cmc = (ContextMenuContent)m_context_menu.getSkin().getNode();
MenuItemHandler_CMC menu_item_handler_cmc = new MenuItemHandler_CMC(cmc);
cmc.addEventHandler(KeyEvent.KEY_PRESSED, menu_item_handler_cmc);
}