JavaFx VBox children not growing past a certain size - java

I been trying to create a desktop app with javafx and have been hardstuck trying to figure out why the GridPane and HBox in the VBox have been refusing to expand past a certain point in the vertical direction. I have set their .vgrow properties to Priority.ALWAYS and they still seem to not want to expand when I resize the window.
The code is shown below:
package display;
import java.io.File;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.event.Event;
import javafx.event.EventHandler;
public class OxygenDisplay
{
private String openFileName;
private String fileContents;
private String activeVaultPath;
private final Stage primaryStage;
private final Scene primaryScene;
private final MenuBar topMenu;
private final GridPane middleStruct;
private final HBox bottomOptions;
public OxygenDisplay(String fileName, String fileContents, String path)
{
this.openFileName = fileName;
this.fileContents = fileContents;
this.activeVaultPath = path;
// Create node structure for menu bar
this.topMenu = this.generateTopMenuBar();
// Create node structure for vault display and text editor
this.middleStruct = this.generateMiddleStructure();
// Create node structure for lower options bar
this.bottomOptions = this.generateBottomOptions();
// Post view to user
VBox root = new VBox();
for (Node child: root.getChildren())
{
VBox.setVgrow(child, Priority.ALWAYS);
}
root.getChildren().addAll(
this.topMenu,
this.middleStruct,
this.bottomOptions
);
this.primaryScene = new Scene(root, 400, 300);
this.primaryStage = new Stage();
// Init all listeners
this.initListeners();
this.primaryStage.setTitle("Oxygen");
this.primaryStage.setScene(primaryScene);
this.primaryStage.show();
}
private void initListeners()
{
// Add window height and width listeners
this.primaryStage.widthProperty().addListener((obsv, oldv, newv) ->
{
// this.mdEditor.prefWidth(newv.doubleValue());
// this.mdEditor.maxWidth(newv.doubleValue());
});
this.primaryStage.heightProperty().addListener((obsv, oldv, newv) ->
{
// this.mdEditor.prefHeight(newv.doubleValue());
// this.mdEditor.maxHeight(newv.doubleValue());
});
}
public String getOpenFileName()
{
return this.openFileName;
}
public void setOpenFileName(String file)
{
this.openFileName = file;
}
public String getFileContents()
{
return this.fileContents;
}
public void setFileContents(String data)
{
this.fileContents = data;
}
private MenuBar generateTopMenuBar()
{
MenuBar topBar = new MenuBar();
Menu file = new Menu("File");
Menu edit = new Menu("Edit");
Menu view = new Menu("View");
Menu help = new Menu("Help");
SeparatorMenuItem fileSeparator = new SeparatorMenuItem();
SeparatorMenuItem editSeparator = new SeparatorMenuItem();
// TODO: Add event handlers for these options
MenuItem open = new MenuItem("Open");
MenuItem newNote = new MenuItem("New Note");
MenuItem newVault = new MenuItem("New Vault");
MenuItem save = new MenuItem("Save");
MenuItem saveAs = new MenuItem("Save as");
file.getItems().addAll(
open,
newNote,
newVault,
fileSeparator,
save,
saveAs
);
// TODO: Add event handlers for these options
MenuItem copy = new MenuItem("Copy");
MenuItem paste = new MenuItem("Paste");
MenuItem cut = new MenuItem("Cut");
MenuItem find = new MenuItem("Find");
edit.getItems().addAll(
cut,
copy,
paste,
editSeparator,
find
);
// TODO: Add event handlers for these options
MenuItem appearance = new MenuItem("Appearance");
view.getItems().addAll(
appearance
);
// TODO: Add event handlers for these options
MenuItem about = new MenuItem("About");
help.getItems().addAll(
about
);
topBar.getMenus().addAll(
file,
edit,
view,
help
);
return topBar;
}
private void generateFileStructure(File node, TreeItem<String> root)
{
// TODO: Order the tree structure so that files are always before
// directories. Currently it is based on file names.
if (node.isDirectory())
{
TreeItem<String> item = new TreeItem<>(node.getName());
root.getChildren().add(item);
for (File f: node.listFiles())
{
TreeItem<String> temp = new TreeItem<>();
item.getChildren().add(temp);
// When parent is expanded continue the recursion
item.addEventHandler(TreeItem.branchExpandedEvent(),
new EventHandler() {
#Override
public void handle(Event event)
{
generateFileStructure(f, item);
item.getChildren().remove(temp);
item.removeEventHandler(TreeItem.branchExpandedEvent(),
this);
}
});
}
} else
{
root.getChildren().add(new TreeItem<>(node.getName()));
}
}
private TreeView<String> generateVaultStructure()
{
File fileInputDirLocation = new File(this.activeVaultPath);
File[] fileList = fileInputDirLocation.listFiles();
TreeView<String> vaultStruct = new TreeView<>();
TreeItem<String> root =
new TreeItem<>(fileInputDirLocation.getAbsoluteFile().getName());
// create tree
assert fileList != null;
for (File file: fileList)
{
this.generateFileStructure(file, root);
}
vaultStruct.setRoot(root);
return vaultStruct;
}
private GridPane generateMiddleStructure()
{
GridPane gridPane = new GridPane();
TreeView<String> vaultStruct = this.generateVaultStructure();
TextArea mdEditor = new TextArea();
mdEditor.setText(this.fileContents);
gridPane.add(vaultStruct, 0, 0);
gridPane.add(mdEditor, 1, 0);
// For resizing purposes
GridPane.setHgrow(vaultStruct, Priority.NEVER);
GridPane.setHgrow(mdEditor, Priority.ALWAYS);
GridPane.setVgrow(vaultStruct, Priority.ALWAYS);
GridPane.setVgrow(mdEditor, Priority.ALWAYS);
return gridPane;
}
private HBox generateBottomOptions()
{
HBox bottomOptions = new HBox();
Label openFileDisplay = new Label("");
openFileDisplay.textProperty().bind(
new SimpleStringProperty(this.openFileName)
);
bottomOptions.getChildren().addAll(
openFileDisplay
);
// For resizing purposes
HBox.setHgrow(bottomOptions, Priority.ALWAYS);
return bottomOptions;
}
}
Example image:
The red area is what I need to remove
Any help is appreciated in finding out why this is occuring? Let me know if anyone wants to run the above code as it requires a seperate class that extends Application for javafx.

The fix was the order of the for loop which gave all children process Priority.ALWAYS:
Old:
// Post view to user
VBox root = new VBox();
for (Node child: root.getChildren())
{
VBox.setVgrow(child, Priority.ALWAYS);
}
root.getChildren().addAll(
this.topMenu,
this.middleStruct,
this.bottomOptions
);
New:
// Post view to user
VBox root = new VBox();
root.setMaxHeight(Double.MAX_VALUE);
root.getChildren().addAll(
this.topMenu,
this.middleStruct,
this.bottomOptions
);
for (Node child: root.getChildren())
{
VBox.setVgrow(child, Priority.ALWAYS);
}
Although now the GridPane and Label/HBox expand together due to both of the panes having Priority.ALWAYS. Hence, to overcome this is to give GridPane maximum priority.
New Version 2:
// Post view to user
VBox root = new VBox();
root.setMaxHeight(Double.MAX_VALUE);
root.getChildren().addAll(
this.topMenu,
this.middleStruct,
this.bottomOptions
);
VBox.setVgrow(this.middleStruct, Priority.ALWAYS);
VBox.setVgrow(this.bottomOptions, Priority.NEVER);
Cheers.

Related

Transparent JavaFX stage capture scrolling events if there is another window behind

Mouse events and scroll events behave in different ways
Mouse Events:
The event is captured by mainStage
The event is captured by mainStage
The event is not captured
Scroll Events:
The event is captured by mainStage
The event is captured by secondStage
The event is not captured
Is there any way that transparent secondStage does not capture scroll events?
My code:
Pane mainPane = new Pane(new Label("Main Stage"));
mainPane.setPrefSize(300, 300);
mainStage.setScene(new Scene(mainPane));
Stage secondStage = new Stage();
Pane secondPane = new Pane(new Label("Second Stage"));
secondPane.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
secondPane.setBorder(new Border(
new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2))));
secondPane.setPrefSize(300, 300);
secondStage.setScene(new Scene(secondPane, Color.TRANSPARENT));
secondStage.initStyle(StageStyle.TRANSPARENT);
mainStage.getScene().setOnScroll(event -> System.out.println("Scroll in main stage"));
secondStage.getScene().setOnScroll(event -> System.out.println("Scroll in second stage"));
mainStage.getScene().setOnMouseClicked(event -> System.out.println("Click in main stage"));
secondStage.getScene().setOnMouseClicked(event -> System.out.println("Click in second stage"));
mainStage.show();
secondStage.show();
Java version: 1.8.0_201 (64 bits), Windows 10
edit:
The example is a simplification with only two windows. Fire the event programmatically implies discovering which stage is immediately lower and that is another problem in itself.
It might be a great coincidence, that we also came with the same solution of transparent window because of not having the feature of managing z-index of stages. And We encountered the exact same issue as yours. ie, scroll events not propagating to underlying Stages. We used the below approach, not sure whether this can help you:
Firstly, We constructed a Singleton class that keeps a reference of Node that is currently hovered on.
Then, when we create any normal stage, we include the below handlers to the scene of that new stage. The key thing here is that, the mouse events are still able to pass through the transparent stage to the underlying window, keep track of node which sits under the mouse.
scene.addEventFilter(MouseEvent.MOUSE_EXITED_TARGET, e -> {
hoverNode.set(null);
});
scene.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
hoverNode.set(e.getTarget());
});
In the scene of the transparent window, we included the below handlers to delegate the scroll events to the underlying node.
scene.addEventFilter(ScrollEvent.SCROLL, e -> {
if (hoverNode.get() != null) {
Event.fireEvent(hoverNode.get(), e);
}
});
scene.addEventHandler(ScrollEvent.SCROLL, e -> {
if (hoverNode.get() != null) {
Event.fireEvent(hoverNode.get(), e);
}
});
I am pretty sure this is not the most desired way. But this addressed our issue. :)
Below is the quick demo code of what I mean.
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.stream.IntStream;
public class ScrollThroughTransparentStage_Demo extends Application {
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("Main Window");
VBox root = new VBox(buildScrollPane());
root.setStyle("-fx-background-color:#888888;");
root.setSpacing(10);
root.setPadding(new Insets(10));
Button normalStageBtn = new Button("Normal Stage");
normalStageBtn.setOnAction(e -> {
Stage normalStage = new Stage();
normalStage.initOwner(stage);
Scene normalScene = new Scene(buildScrollPane(), 300, 300);
addHandlers(normalScene);
normalStage.setScene(normalScene);
normalStage.show();
});
CheckBox allowScrollThrough = new CheckBox("Allow scroll through transparency");
allowScrollThrough.setSelected(true);
HBox buttons = new HBox(normalStageBtn);
buttons.setSpacing(20);
root.getChildren().addAll(allowScrollThrough,buttons);
Scene scene = new Scene(root, 600, 600);
addHandlers(scene);
stage.setScene(scene);
stage.show();
/* Transparent Stage */
Stage transparentStage = new Stage();
transparentStage.initOwner(stage);
transparentStage.initStyle(StageStyle.TRANSPARENT);
Pane mainRoot = new Pane();
Pane transparentRoot = new Pane(mainRoot);
transparentRoot.setStyle("-fx-background-color:transparent;");
Scene transparentScene = new Scene(transparentRoot, Color.TRANSPARENT);
transparentStage.setScene(transparentScene);
transparentScene.addEventFilter(ScrollEvent.SCROLL, e -> {
if (allowScrollThrough.isSelected() && HoverNodeSingleton.getInstance().getHoverNode() != null) {
Event.fireEvent(HoverNodeSingleton.getInstance().getHoverNode(), e);
}
});
transparentScene.addEventHandler(ScrollEvent.SCROLL, e -> {
if (allowScrollThrough.isSelected() && HoverNodeSingleton.getInstance().getHoverNode() != null) {
Event.fireEvent(HoverNodeSingleton.getInstance().getHoverNode(), e);
}
});
determineStageSize(transparentStage, mainRoot);
transparentStage.show();
Button transparentStageBtn = new Button("Transparent Stage");
transparentStageBtn.setOnAction(e -> {
MiniStage miniStage = new MiniStage(mainRoot);
ScrollPane scrollPane = buildScrollPane();
scrollPane.setPrefSize(300, 300);
miniStage.setContent(scrollPane);
miniStage.show();
});
buttons.getChildren().add(transparentStageBtn);
}
private static void determineStageSize(Stage stage, Node root) {
DoubleProperty width = new SimpleDoubleProperty();
DoubleProperty height = new SimpleDoubleProperty();
DoubleProperty shift = new SimpleDoubleProperty();
Screen.getScreens().forEach(screen -> {
Rectangle2D bounds = screen.getVisualBounds();
width.set(width.get() + bounds.getWidth());
if (bounds.getHeight() > height.get()) {
height.set(bounds.getHeight());
}
if (bounds.getMinX() < shift.get()) {
shift.set(bounds.getMinX());
}
});
stage.setX(shift.get());
stage.setY(0);
stage.setWidth(width.get());
stage.setHeight(height.get());
root.setTranslateX(-1 * shift.get());
}
private void addHandlers(Scene scene) {
scene.addEventFilter(MouseEvent.MOUSE_EXITED_TARGET, e -> {
HoverNodeSingleton.getInstance().setHoverNode(null);
});
scene.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
HoverNodeSingleton.getInstance().setHoverNode(e.getTarget());
});
}
private ScrollPane buildScrollPane() {
VBox vb = new VBox();
vb.setSpacing(10);
vb.setPadding(new Insets(15));
IntStream.rangeClosed(1, 100).forEach(i -> vb.getChildren().add(new Label(i + "")));
ScrollPane scrollPane = new ScrollPane(vb);
return scrollPane;
}
class MiniStage extends Group {
private Pane parent;
double sceneX, sceneY, layoutX, layoutY;
protected BorderPane windowPane;
private BorderPane windowTitleBar;
private Label labelTitle;
private Button buttonClose;
public MiniStage(Pane parent) {
this.parent = parent;
buildRootNode();
getChildren().add(windowPane);
addEventHandler(MouseEvent.MOUSE_PRESSED, e -> toFront());
}
#Override
public void toFront() {
parent.getChildren().remove(this);
parent.getChildren().add(this);
}
public void setContent(Node content) {
// Computing the bounds of the content before rendering
Group grp = new Group(content);
new Scene(grp);
grp.applyCss();
grp.requestLayout();
double width = grp.getLayoutBounds().getWidth();
double height = grp.getLayoutBounds().getHeight() + 30; // 30 title bar height
grp.getChildren().clear();
windowPane.setCenter(content);
// Centering the stage
Rectangle2D screenBounds = Screen.getPrimary().getBounds();
setX(screenBounds.getWidth() / 2 - width / 2);
setY(screenBounds.getHeight() / 2 - height / 2);
}
public Node getContent() {
return windowPane.getCenter();
}
public void setX(double x) {
setLayoutX(x);
}
public void setY(double y) {
setLayoutY(y);
}
public void show() {
if (!parent.getChildren().contains(this)) {
parent.getChildren().add(this);
}
}
public void hide() {
parent.getChildren().remove(this);
}
private void buildRootNode() {
windowPane = new BorderPane();
windowPane.setStyle("-fx-border-width:2px;-fx-border-color:#444444;");
labelTitle = new Label("Mini Stage");
labelTitle.setStyle("-fx-font-weight:bold;");
labelTitle.setMaxHeight(Double.MAX_VALUE);
buttonClose = new Button("X");
buttonClose.setFocusTraversable(false);
buttonClose.setStyle("-fx-background-color:red;-fx-background-radius:0;-fx-background-insets:0;");
buttonClose.setOnMouseClicked(evt -> hide());
windowTitleBar = new BorderPane();
windowTitleBar.setStyle("-fx-border-width: 0 0 2px 0;-fx-border-color:#444444;-fx-background-color:#BBBBBB");
windowTitleBar.setLeft(labelTitle);
windowTitleBar.setRight(buttonClose);
windowTitleBar.setPadding(new Insets(0, 0, 0, 10));
windowTitleBar.getStyleClass().add("nonfocus-title-bar");
windowPane.setTop(windowTitleBar);
assignTitleBarEvents();
}
private void assignTitleBarEvents() {
windowTitleBar.setOnMousePressed(this::recordWindowLocation);
windowTitleBar.setOnMouseDragged(this::moveWindow);
windowTitleBar.setOnMouseReleased(this::resetMousePointer);
}
private final void recordWindowLocation(final MouseEvent event) {
sceneX = event.getSceneX();
sceneY = event.getSceneY();
layoutX = getLayoutX();
layoutY = getLayoutY();
getScene().setCursor(Cursor.MOVE);
}
private final void resetMousePointer(final MouseEvent event) {
// Updating the new layout positions
setLayoutX(layoutX + getTranslateX());
setLayoutY(layoutY + getTranslateY());
// Resetting the translate positions
setTranslateX(0);
setTranslateY(0);
getScene().setCursor(Cursor.DEFAULT);
}
private final void moveWindow(final MouseEvent event) {
double offsetX = event.getSceneX() - sceneX;
double offsetY = event.getSceneY() - sceneY;
setTranslateX(offsetX);
setTranslateY(offsetY);
event.consume();
}
}
}
/**
* Singleton class.
*/
class HoverNodeSingleton {
private static HoverNodeSingleton INSTANCE = new HoverNodeSingleton();
private EventTarget hoverNode;
private HoverNodeSingleton() {
}
public static HoverNodeSingleton getInstance() {
return INSTANCE;
}
public EventTarget getHoverNode() {
return hoverNode;
}
public void setHoverNode(EventTarget hoverNode) {
this.hoverNode = hoverNode;
}
}
I don't know that's right or not, but you can bind properties:
secondStage.getScene().onScrollProperty().bind(mainStage.getScene().onScrollProperty());
You can create a custom event dispatcher that will ignore events you don't want:
public class CustomEventDispatcher extends BasicEventDispatcher {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if(event instanceof ScrollEvent) {
return null;
} else {
return super.dispatchEvent(event, tail);
}
}
}
Then set that on your stage:
secondStage.setEventDispatcher(new CustomEventDispatcher());
I don't know how this works in the context of stages but for simple shapes it makes a difference whether you set the fill color to Color.TRANSPARENT or just null. Using any Color catches events, whereas null does not.
You can do so by ignoring the event on the second stage using event dispatcher using this answer by #Slaw you can understand everything about EventDispatcher
https://stackoverflow.com/a/51015783/5303683
Then you can fire your own event using this answer by DVarga
https://stackoverflow.com/a/40042513/5303683
Sorry I don't have time to try and make a full example of it

Javafx show virtual keyboard

I want never to hide the virtual keyboard like Programmatically show/hide virtual keyboard
OR (if above point is not possible then)
I want to remove/hide/disable below (highlighted green left bottom) button from virtual keyboard.
I am using Java 8 and Javafx.
Update 1
I have integrated José Pereda's code. The hide button (.hide) is hidden successfully. Now I am having hard time to keep displaying the keyboard for all time.
Problem:
Whenever there is focus out from the textarea the keyboard hides. So when user clicks on Convert Now! button the keyboard hides. I tried to stay focused on textarea by using textarea.requestFocus();, but there is a blinking of keyboard (hide then show).
My Goal
To never hide the keyboard at any case unless the program is not terminated.
Code: You can check my code on github too.
package com.binaryname.view;
import java.util.Iterator;
import com.sun.javafx.print.PrintHelper;
import com.sun.javafx.print.Units;
import com.sun.javafx.scene.control.skin.FXVK;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.print.PageLayout;
import javafx.print.PageOrientation;
import javafx.print.Paper;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Path;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.transform.Scale;
import javafx.stage.PopupWindow;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
public class Main extends Application {
private PopupWindow keyboard;
private final Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
private final Rectangle2D bounds = Screen.getPrimary().getBounds();
private final double taskbarHeight = bounds.getHeight() - visualBounds.getHeight();
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Binary Name");
Label helloLbl = new Label("Hello");
helloLbl.setAlignment(Pos.CENTER);
helloLbl.setFont(Font.font("Comic Sans MS", FontWeight.BOLD, 68));
helloLbl.setStyle("-fx-background-color: red;padding: 20px;");
helloLbl.setTextFill(Color.web("#ffffff"));
Label myNameLbl = new Label("my name is");
myNameLbl.setAlignment(Pos.CENTER);
myNameLbl.setFont(Font.font("Comic Sans MS", 48));
myNameLbl.setStyle("-fx-background-color: red;padding: 20px;");
myNameLbl.setTextFill(Color.web("#ffffff"));
TextArea nameTxtArea = new TextArea();
nameTxtArea.setWrapText(Boolean.TRUE);
nameTxtArea.getStyleClass().add("center-text-area");
nameTxtArea.setFont(Font.font("Comic Sans MS", 28));
nameTxtArea.setStyle("padding: 20px;");
Button printBtn = new Button("PRINT");
printBtn.setId("ipad-grey");
printBtn.setDisable(Boolean.TRUE);
Button convertBtn = new Button("Convert Now!");
convertBtn.setId("ipad-grey");
convertBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
nameTxtArea.requestFocus();
convertBtn.setDisable(Boolean.TRUE);
printBtn.setDisable(Boolean.FALSE);
}
});
HBox hBox = new HBox(100);
hBox.setAlignment(Pos.CENTER);
hBox.getChildren().addAll(convertBtn, printBtn);
VBox vBox = new VBox(10);
vBox.setAlignment(Pos.TOP_CENTER);
vBox.getChildren().addAll(helloLbl, myNameLbl, nameTxtArea, hBox);
vBox.setStyle("-fx-background-color: red;margin: 20px;");
printBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
nameTxtArea.requestFocus();
// Start printing
print(vBox, nameTxtArea.getText());
convertBtn.setDisable(Boolean.FALSE);
printBtn.setDisable(Boolean.TRUE);
nameTxtArea.setText("");
}
});
Scene scene = new Scene(vBox);
scene.getStylesheets().add(Main.class.getResource("/style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setScene(scene);
primaryStage.setX(visualBounds.getMinX());
primaryStage.setY(visualBounds.getMinY());
primaryStage.setWidth(visualBounds.getWidth());
primaryStage.setHeight(visualBounds.getHeight());
adjustTextAreaLayout(nameTxtArea);
primaryStage.show();
// attach keyboard to first node on scene:
Node first = scene.getRoot().getChildrenUnmodifiable().get(0);
if (first != null) {
FXVK.init(first);
FXVK.attach(first);
keyboard = getPopupWindow();
}
nameTxtArea.focusedProperty().addListener((ob, b, b1) -> {
if (keyboard == null) {
keyboard = getPopupWindow();
}
keyboard.setHideOnEscape(Boolean.FALSE);
keyboard.setAutoHide(Boolean.FALSE);
keyboard.centerOnScreen();
keyboard.requestFocus();
keyboard.yProperty().addListener(obs -> {
Platform.runLater(() -> {
Double y = bounds.getHeight() - taskbarHeight - keyboard.getY();
nameTxtArea.setMaxHeight((bounds.getHeight() - y) * 0.4);
nameTxtArea.setMinHeight((bounds.getHeight() - y) * 0.4);
});
});
});
}
public static void main(String[] args) {
launch(args);
}
private void print(Node node1, String text) {
// Create a printer job for the default printer
Printer printer = Printer.getDefaultPrinter();
Paper label = PrintHelper.createPaper("2.5x3.5", 2.5, 3.5, Units.INCH);
PageLayout pageLayout = printer.createPageLayout(label, PageOrientation.LANDSCAPE, Printer.MarginType.EQUAL);
PrinterJob job = PrinterJob.createPrinterJob();
Node node = createFullNode(text);
double scaleX = pageLayout.getPrintableWidth() / node1.getBoundsInParent().getWidth();
double scaleY = pageLayout.getPrintableHeight() / node1.getBoundsInParent().getHeight();
node.getTransforms().add(new Scale(scaleX, scaleY));
if (job != null) {
// Print the node
boolean printed = job.printPage(node);
if (printed) {
// End the printer job
job.endJob();
} else {
// Write Error Message
System.out.println("Printing failed.");
}
} else {
// Write Error Message
System.out.println("Could not create a printer job.");
}
node.getTransforms().remove(node.getTransforms().size() - 1);
}
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
if (window.getScene() != null && window.getScene().getRoot() != null) {
Parent root = window.getScene().getRoot();
if (root.getChildrenUnmodifiable().size() > 0) {
Node popup = root.getChildrenUnmodifiable().get(0);
if (popup.lookup(".fxvk") != null) {
FXVK vk = (FXVK) popup.lookup(".fxvk");
// hide the key:
vk.lookup(".hide").setVisible(false);
return (PopupWindow) window;
}
}
}
return null;
}
}
return null;
}
private Node createFullNode(String text) {
Label helloLbl = new Label("Hello");
helloLbl.setAlignment(Pos.CENTER);
helloLbl.setFont(Font.font("Comic Sans MS", FontWeight.BOLD, 68));
helloLbl.setStyle("-fx-background-color: red;padding: 20px;");
helloLbl.setTextFill(Color.web("#ffffff"));
Label myNameLbl = new Label("my name is");
myNameLbl.setAlignment(Pos.CENTER);
myNameLbl.setFont(Font.font("Comic Sans MS", 48));
myNameLbl.setStyle("-fx-background-color: red;padding: 20px;");
myNameLbl.setTextFill(Color.web("#ffffff"));
TextArea nameTxtArea = new TextArea();
nameTxtArea.setWrapText(Boolean.TRUE);
nameTxtArea.setFont(Font.font("Comic Sans MS", 28));
nameTxtArea.setStyle("padding: 20px;");
nameTxtArea.setText(text);
nameTxtArea.getStyleClass().add("center-text-area");
HBox hBox = new HBox(1000);
hBox.setAlignment(Pos.CENTER);
VBox vBox = new VBox(10);
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().addAll(helloLbl, myNameLbl, nameTxtArea, hBox);
vBox.setStyle("-fx-background-color: red;margin: 20px;");
vBox.getStylesheets().add(Main.class.getResource("/style.css").toExternalForm());
return vBox;
}
private void adjustTextAreaLayout(TextArea textArea) {
textArea.applyCss();
textArea.layout();
ScrollPane textAreaScroller = (ScrollPane) textArea.lookup(".scroll-pane");
Text text = (Text) textArea.lookup(".text");
ChangeListener<? super Bounds> listener =
(obs, oldBounds, newBounds) -> centerTextIfNecessary(textAreaScroller, text);
textAreaScroller.viewportBoundsProperty().addListener(listener);
text.boundsInLocalProperty().addListener(listener);
}
private void centerTextIfNecessary(ScrollPane textAreaScroller, Text text) {
double textHeight = text.getBoundsInLocal().getHeight();
double viewportHeight = textAreaScroller.getViewportBounds().getHeight();
double offset = Math.max(0, (viewportHeight - textHeight) / 2 );
text.setTranslateY(offset);
Parent content = (Parent)textAreaScroller.getContent();
for (Node n : content.getChildrenUnmodifiable()) {
if (n instanceof Path) { // caret
n.setTranslateY(offset);
}
}
}
}
You can show and hide the virtual keyboard on demand.
You just need to provide a node that the keyboard will be attached to, and it doesn't need to be a TextField.
Obviously, you show the virtual keyboard for being able to type in your input control (TextField, TextArea,...), but I'll leave this part to you.
Without knowing your scene hierarchy, I'll just pick the first node on it:
#Override
public void start(Stage stage) {
...
stage.setScene(scene);
stage.show();
// attach keyboard to first node on scene:
Node first = scene.getRoot().getChildrenUnmodifiable().get(0);
if (first != null) {
FXVK.init(first);
FXVK.attach(first);
}
}
That will show the keyboard right after showing the stage.
If you want to programmatically close it at any point, you will just call:
FXVK.detach();
About hiding one of the keys of the keyboard, this is a little bit more tricky, since the keyboard is placed in a popup, so first of all you need to get a handle of it. But you can find already solutions for this, like this one.
The following snippet will look for the popup window. As for the deprecated method, on JavaFX 9 it will be a public method (javafx.stage.Windows.getWindows()).
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
if (window.getScene() != null && window.getScene().getRoot() != null) {
Parent root = window.getScene().getRoot();
if (root.getChildrenUnmodifiable().size() > 0) {
Node popup = root.getChildrenUnmodifiable().get(0);
if (popup.lookup(".fxvk") != null) {
FXVK vk = (FXVK) popup.lookup(".fxvk");
return (PopupWindow) window;
}
}
}
return null;
}
}
return null;
}
Once you have the virtual keyboard instance, you will be able to find any of its nodes with lookups, based on their style classes. Luckily, all the keys have assigned the styleClass key, so you can easily interact with all of them. As for the particular key to hide the keyboard, this one also has special and hide style classes:
// hide the key:
vk.lookup(".hide").setVisible(false);
Full sample code:
public class FXVKAlwaysOn extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 1280, 600);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
Node first = scene.getRoot().getChildrenUnmodifiable().get(0);
if (first != null) {
FXVK.init(first);
FXVK.attach(first);
getPopupWindow();
}
}
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
if (window.getScene() != null && window.getScene().getRoot() != null) {
Parent root = window.getScene().getRoot();
if (root.getChildrenUnmodifiable().size() > 0) {
Node popup = root.getChildrenUnmodifiable().get(0);
if (popup.lookup(".fxvk") != null) {
FXVK vk = (FXVK) popup.lookup(".fxvk");
// hide the key:
vk.lookup(".hide").setVisible(false);
return (PopupWindow) window;
}
}
}
return null;
}
}
return null;
}
}

how to add remove button in combobox

I would like to create a ComboBox with the remove button like the picture below:
The picture uses Java Swing, and I don't know how to do this with JavaFX. I would like to create two ComboBoxes (a,b). When I click the "cross" in ComboBox a, I would like to remove a's item and add this item to ComboBox b, and ComboBox b so on.
ComboBox a:
(1)click item then remove it from a and add on b
ComboBox b:
(1)click item then do something(ex:print item)
(2)click cross then remove it from b and add on a
package UnitTest;
import Peer.Peer_Manager;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.chart.XYChart;
import javafx.geometry.Insets;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
public class temp extends Application {
final int height = 200;
final int weight = 300;
final int offset = 5;
Peer_Manager p_management;
XYChart.Series series_hop;
XYChart.Series series_gd;
#Override
public void start(Stage primaryStage) {
VBox vbox = new VBox();
vbox.setPadding(new Insets(5, 5, 5, 5));
vbox.setStyle("-fx-background-color: CORNSILK;");
Scene scene = new Scene(vbox, weight, height);
primaryStage.setScene(scene);
HBox hbBtn = new HBox();
Text t1=new Text(" A:");
Text t2=new Text(" B:");
String[] filename = {"A","B","C"};//conf.load_all();
ComboBox<String> cb = new ComboBox<String>();
cb.setItems(FXCollections.observableArrayList(filename));
cb.setVisibleRowCount(10);
ComboBox<String> cb2 = new ComboBox<String>();
cb.setVisibleRowCount(10);
vbox.getChildren().add(hbBtn);
hbBtn.getChildren().add(t1);
hbBtn.getChildren().add(cb);
hbBtn.getChildren().add(t2);
hbBtn.getChildren().add(cb2);
cb.setOnAction(e -> {
try {
Object object = cb.getValue();
if (object != null) {
cb2.getItems().add(object);
cb.getSelectionModel().clearSelection();
cb.getItems().remove(object);
}
} catch (Exception e1) {
e1.printStackTrace();
}
});
//would like to do something(ex:print item),but don't remove
//add the "cross" beside items,click "cross" to remove item and add on cb
cb2.setOnAction(e -> {
try {
Object object = cb2.getValue();
System.out.println(object);
if (object != null) {
cb1.getItems().add(object);
cb2.getSelectionModel().clearSelection();
cb2.getItems().remove(object);
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
});
primaryStage.setTitle("SimulatorFX");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The correct way is to use a CellFactory and create graphic nodes that contain the elements you wish to have. Here is an example:
public void start(Stage primaryStage) throws Exception {
ComboBox<String> cba = new ComboBox<>();
ComboBox<String> cbb = new ComboBox<>();
cba.getItems().addAll("A", "B", "C");
cbb.getItems().addAll("123", "456", "789");
// Set a cell factory for ComboBox A. A similar thing should be done for B.
cba.setCellFactory(lv ->
new ListCell<String>() {
// This is the node that will display the text and the cross.
// I chose a hyperlink, but you can change to button, image, etc.
private HBox graphic;
// this is the constructor for the anonymous class.
{
Label label = new Label();
// Bind the label text to the item property. If your ComboBox items are not Strings you should use a converter.
label.textProperty().bind(itemProperty());
// Set max width to infinity so the cross is all the way to the right.
label.setMaxWidth(Double.POSITIVE_INFINITY);
// We have to modify the hiding behavior of the ComboBox to allow clicking on the hyperlink,
// so we need to hide the ComboBox when the label is clicked (item selected).
label.setOnMouseClicked(event -> cba.hide());
Hyperlink cross = new Hyperlink("X");
cross.setVisited(true); // So it is black, and not blue.
cross.setOnAction(event ->
{
// Since the ListView reuses cells, we need to get the item first, before making changes.
String item = getItem();
System.out.println("Clicked cross on " + item);
if (isSelected()) {
// Not entirely sure if this is needed.
cba.getSelectionModel().select(null);
}
// Remove the item from A and add to B. You can add any additional logic in here.
cba.getItems().remove(item);
cbb.getItems().add(item);
}
);
// Arrange controls in a HBox, and set display to graphic only (the text is included in the graphic in this implementation).
graphic = new HBox(label, cross);
graphic.setHgrow(label, Priority.ALWAYS);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(graphic);
}
}
});
// We have to set a custom skin, otherwise the ComboBox disappears before the click on the Hyperlink is registered.
cba.setSkin(new ComboBoxListViewSkin<String>(cba) {
#Override
protected boolean isHideOnClickEnabled() {
return false;
}
});
VBox vb = new VBox(cba, cbb);
primaryStage.setScene(new Scene(vb));
primaryStage.show();
}

Button Click to Send User to Start of Program

I have an equations program that I'm working on, which randomly selects one of 50 equations, then takes the user through a series of scenes in order to solve it. Once the user solves the equation, they're asked if they want another equation. If they answer no, the program closes. If they answer yes, the program is supposed to randomly select another equation, then take them through the scenes to solve that one.
The program works just as I want it to the first time through. However, if the user selects "yes" for another equation, the program displays the END of the first scene, showing them the previous problem that they've already solved.
How can I send the user to the beginning of the scene, so that a new equation is randomly selected?
Here’s the relevant code for Scene 1:
package Equations;
import java.util.Random;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.scene.control.*;
import javafx.event.*;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
public class equationsapp extends Application
implements EventHandler<ActionEvent> {
public static void main(String[] args) {
launch(args);
}
#Override public void start(Stage primaryStage) {
stage = primaryStage;
Random eqrdmzr = new Random();
int randomNumber = eqrdmzr.nextInt(3) + 1;
if (randomNumber == 1) {
isolCounterCoeff = 2;
isolVrblb = new Label("+");
isolCounter1a = 7;
isolCounter2a = 17;
slvCoeff = 2;
slvEqVrblTerm = new Text("2n");
slvEqWhlNmbrInt = 10;
slvEqWhlNmbr = new Text("10");
}
if(randomNumber == 2) {
isolCounterCoeff = 2;
isolVrblb = new Label("+");
isolVrblb.setVisible(false);
isolCounter1a = -18;
isolCounter2a = 4;
slvCoeff = 2;
slvEqVrblTerm = new Text("2n");
slvEqWhlNmbrInt = 22;
slvEqWhlNmbr = new Text("22");
}
if(randomNumber == 3) {
isolCounterCoeff = 3;
isolVrblb = new Label("+");
isolVrblb.setVisible(false);
isolCounter1a = -5;
isolCounter2a = 19;
slvCoeff = 3;
slvEqVrblTerm = new Text("3n");
slvEqWhlNmbrInt = 24;
slvEqWhlNmbr = new Text("24");
}
//Build Scene 1 - Top BorderPane
Text isolText = new Text("Isolate the Variable Term");
isolText.setStyle("-fx-font-size: 16pt");
//Build Scene 1 - Center BorderPane
Label isolCoeff = new Label();
isolCoeff.setStyle("-fx-font-size: 24pt;");
isolCoeff.setText(Integer.toString(isolCounterCoeff));
Label isolVrbl = new Label("n");
isolVrbl.setStyle("-fx-font-size: 24pt;");
isolVrblb.setStyle("-fx-font-size: 24pt;");
isolVrblb.managedProperty().bind(isolVrblb.visibleProperty());
Label isolEqIntLeft = new Label();
isolEqIntLeft.setStyle("-fx-font-size: 24pt;");
isolEqIntLeft.setPadding(new Insets(0, 10, 0, 0));
isolEqIntLeft.setText(Integer.toString(isolCounter1a));
isolEqIntLeft.managedProperty().bind(isolEqIntLeft.visibleProperty());
Label isolEqualSign = new Label("=");
isolEqualSign.setStyle("-fx-font-size: 24pt;");
Label isolEqIntRight = new Label();
isolEqIntRight.setStyle("-fx-font-size: 24pt;");
isolEqIntRight.setPadding(new Insets(0, 0, 0, 10));
isolEqIntRight.setText(Integer.toString(isolCounter2a));
//Build Scene 1 - Bottom BorderPane
Label isolLbl1 = new Label();
isolLbl1.setStyle("-fx-font-size: 22pt;");
isolEqIntLeft.setText(Integer.toString(isolCounter1a));
isolLbl1.setText(Integer.toString(isolCounter1b));
//Create GridPanes and Fill Them
GridPane isolGridPane1 = new GridPane();
isolGridPane1.setAlignment(Pos.CENTER);
isolGridPane1.add(isolText, 0, 0);
GridPane isolGridPane2 = new GridPane();
isolGridPane2.setAlignment(Pos.CENTER);
isolGridPane2.add(isolCoeff, 0, 0);
isolGridPane2.add(isolVrbl, 1, 0);
isolGridPane2.add(isolVrblb, 2, 0);
isolGridPane2.add(isolEqIntLeft, 3, 0);
isolGridPane2.add(isolEqualSign, 4, 0);
isolGridPane2.add(isolEqIntRight, 5, 0);
GridPane isolGridPane3 = new GridPane();
isolGridPane3.setAlignment(Pos.CENTER);
isolGridPane3.setHgap(25.0);
isolGridPane3.setVgap(10.0);
isolGridPane3.setPadding(new Insets(0, 0, 20, 0));
isolGridPane3.add(isolbtn1, 0, 0);
isolGridPane3.add(isolLbl1, 1, 0);
isolGridPane3.add(isolBtn2, 2, 0);
isolGridPane3.add(isolBtn3, 4, 0);
isolGridPane3.add(isolLbl2, 5, 0);
isolGridPane3.add(isolBtn4, 6, 0);
isolGridPane3.add(isolContinueBtn, 3, 1);
//Add GridPane to BorderPane
BorderPane isolBorderPane = new BorderPane();
isolBorderPane.setTop(isolGridPane1);
isolBorderPane.setCenter(isolGridPane2);
isolBorderPane.setBottom(isolGridPane3);
//Add BorderPane to Scene
scene1 = new Scene(isolBorderPane, 500, 300);
//Add the scene to the stage, set the title and show the stage
primaryStage.setScene(scene1);
primaryStage.setTitle("Equations");
primaryStage.show();
Here’s the event handler that’s supposed to send them back to the start of Stage 1:
Button yesBtn = new Button("Yes");
yesBtn.setStyle("-fx-font-size: 12pt;");
yesBtn.setOnAction(new EventHandler<ActionEvent>() {
public void handle (ActionEvent event) {
if (event.getSource() == yesBtn) {
stage.setScene(scene1);
}
}
});
Just setting the scene on the stage doesn't reload the contents of the scene..
How to resolve this.. ?
As far as I see, you do not need to change scene. Create a simple method called loadMainDisplay(), which creates the BorderPane isolBorderPane by the adding the grid to it with all the required controls.
BorderPane loadMainDisplay() {
...
}
You can call it initially while loading the contents. Later, when the user selects YES for another equation, call this method, again.
yesBtn.setOnAction(event -> {
if (event.getSource() == yesBtn) {
scene.setRoot(loadMainDisplay());
}
});
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ChangePaneExample extends Application{
/**
* #param args
*/
public static void main( String[] args ){
launch( args );
}
int screenNumber = 1;
private GridPane root;
private Scene rootScene;
private StackPane changingPane;
/**
* #see javafx.application.Application#start(javafx.stage.Stage)
* #param primaryStage
* #throws Exception
*/
#Override
public void start( Stage primaryStage ) throws Exception{
root = new GridPane();
rootScene = new Scene( root );
primaryStage.setScene( rootScene );
changingPane = new StackPane();
changeScreen();
Button changeBtn = new Button();
changeBtn.setText( "Change Screen" );
changeBtn.setOnAction( new EventHandler<ActionEvent>(){
#Override
public void handle( ActionEvent arg0 ){
changeScreen();
}
} );
root.addRow( 1, changeBtn );
root.addRow( 2, changingPane );
primaryStage.show();
}
/**
*/
private void changeScreen(){
if( screenNumber > 2 ) screenNumber = 1;
changingPane.getChildren().clear();
changingPane.getChildren().add( getDisplayPane( screenNumber + "" ) );
screenNumber++;
}
public static Pane getDisplayPane( String uniqueIdOfScreen ){
switch( uniqueIdOfScreen ){
case "1":
return getIsoletedGridPane2();
case "2":
return getIsoletedGridPane1();
default:
break;
}
return null;
}
public static Pane getIsoletedGridPane2(){
GridPane isolGridPane3 = new GridPane();
Label label = new Label();
label.setText( "this is isolated GridPane--------------- 2 ----------------------" );
isolGridPane3.getChildren().add( label );
return isolGridPane3;
}
public static Pane getIsoletedGridPane1(){
HBox isolGridPane3 = new HBox();
Label label = new Label();
label.setText( "this is isolated HBox --------------------------- 1 ----------------------------" );
isolGridPane3.getChildren().add( label );
return isolGridPane3;
}
}
This is one example of changing the Panes on the scene.
Changing the entire scene is not recommended way.

Customized context menu on javafx webview/webengine

How may I have a customized context menu for whole entry of the document in WebEngine javafx?
Something like this
+------------+
|Reload |
|Save page |
|Hide Images |
+------------+
I like to invoke and show this context popup for whole document entry(same for every node). Thanks.
I don't see a way to interact with the default context menu. However, it's not hard to disable it and implement your own.
Disable the default context menu with
webView.setContextMenuEnabled();
Then create your own context menu, and register a mouse listener with the web view to show it on right click:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class WebViewContextMenuTest extends Application {
private final String START_URL =
"http://stackoverflow.com/questions/27047447/customized-context-menu-on-javafx-webview-webengine/27047830#27047830";
#Override
public void start(Stage primaryStage) {
TextField locationField = new TextField(START_URL);
WebView webView = new WebView();
webView.getEngine().load(START_URL);
webView.setContextMenuEnabled(false);
createContextMenu(webView);
locationField.setOnAction(e -> {
webView.getEngine().load(getUrl(locationField.getText()));
});
BorderPane root = new BorderPane(webView, locationField, null, null, null);
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
}
private void createContextMenu(WebView webView) {
ContextMenu contextMenu = new ContextMenu();
MenuItem reload = new MenuItem("Reload");
reload.setOnAction(e -> webView.getEngine().reload());
MenuItem savePage = new MenuItem("Save Page");
savePage.setOnAction(e -> System.out.println("Save page..."));
MenuItem hideImages = new MenuItem("Hide Images");
hideImages.setOnAction(e -> System.out.println("Hide Images..."));
contextMenu.getItems().addAll(reload, savePage, hideImages);
webView.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.SECONDARY) {
contextMenu.show(webView, e.getScreenX(), e.getScreenY());
} else {
contextMenu.hide();
}
});
}
private String getUrl(String text) {
if (text.indexOf("://")==-1) {
return "http://" + text ;
} else {
return text ;
}
}
public static void main(String[] args) {
launch(args);
}
}
There's no easy solution for this, since there's no public API, and a request is still unresolved.
The hacky solution uses some private API, so it's not very advisable since it could change without notice.
The ContextMenu shown when the user right clicks on the web page is in another window, so using some lookups we'll try to find it, then access to its content and then modify existing or add more MenuItems.
These are the private classes required:
import com.sun.javafx.scene.control.skin.ContextMenuContent;
import com.sun.javafx.scene.control.skin.ContextMenuContent.MenuItemContainer;
In our application, we listen for a context menu request:
#Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
Scene scene = new Scene(webView);
primaryStage.setScene(scene);
primaryStage.show();
webView.setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() {
#Override
public void handle(ContextMenuEvent e) {
getPopupWindow();
}
});
}
where getPopupWindow() will:
Look for the new window being instance of ContextMenu
With lookup find the CSS selector context-menu. This is a node having as its only child a ContextMenuContent instance.
This object has an VBox as a container for all the items, which are MenuItem in an special container, MenuItemContainer.
We can access to any of the existing items, like reload page, go back, ... and customize them, modifying its text or adding a graphic.
We can add our custom items to this box, providing our own actions.
Customize the items as you need to:
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof ContextMenu) {
if(window.getScene()!=null && window.getScene().getRoot()!=null){
Parent root = window.getScene().getRoot();
// access to context menu content
if(root.getChildrenUnmodifiable().size()>0){
Node popup = root.getChildrenUnmodifiable().get(0);
if(popup.lookup(".context-menu")!=null){
Node bridge = popup.lookup(".context-menu");
ContextMenuContent cmc= (ContextMenuContent)((Parent)bridge).getChildrenUnmodifiable().get(0);
VBox itemsContainer = cmc.getItemsContainer();
for(Node n: itemsContainer.getChildren()){
MenuItemContainer item=(MenuItemContainer)n;
// customize text:
item.getItem().setText("My Custom: "+item.getItem().getText());
// customize graphic:
item.getItem().setGraphic(new ImageView(new Image(getClass().getResource("unlock24.png").toExternalForm())));
}
// remove some item:
// itemsContainer.getChildren().remove(0);
// adding new item:
MenuItem menuItem = new MenuItem("Save page");
menuItem.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
System.out.println("Save Page");
}
});
// add new item:
cmc.getItemsContainer().getChildren().add(cmc.new MenuItemContainer(menuItem));
return (PopupWindow)window;
}
}
}
return null;
}
}
return null;
}
This is how it looks like:

Categories