Can someone suggest a workaround to add Superscript and Subscript controls in the existing javafx HTML editor.
I am trying to develop a Formula field editor having Bold, Italics, Superscript, Subscript and font selector as controls.
This isn't possible (AFAIK) without some pretty serious hacking, involving accessing parts of the API that are intended not to be accessed. The following more or less works; I based it on the source code for the HTMLEditorSkin. You may need to persuade your IDE to let you access the relevant packages. This is not particularly recommended, and it almost certainly will not work in Java 9.
import com.sun.javafx.webkit.Accessor;
import com.sun.webkit.WebPage;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.web.HTMLEditor;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class HTMLEditorHack extends Application {
#Override
public void start(Stage primaryStage) {
HTMLEditor editor = new HTMLEditor();
Scene scene = new Scene(editor);
editor.applyCss();
editor.layout();
WebView webView = (WebView) editor.lookup(".web-view");
ToolBar toolbar = (ToolBar) editor.lookup(".bottom-toolbar");
ToggleGroup toggleGroup = new ToggleGroup();
createToggleButton("superscript", "Super", toggleGroup, webView, toolbar);
createToggleButton("subscript", "Sub", toggleGroup, webView, toolbar);
primaryStage.setScene(scene);
primaryStage.show();
}
private void createToggleButton(String command, String label, ToggleGroup toggleGroup, WebView webView, ToolBar toolbar) {
ToggleButton button = new ToggleButton(label);
button.setFocusTraversable(false);
button.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
WebPage page = Accessor.getPageFor(webView.getEngine());
if (page.queryCommandState(command) != isSelected) {
page.executeCommand(command, null);
}
});
button.setToggleGroup(toggleGroup);
toolbar.getItems().add(button);
EventHandler<Event> updateState = e -> {
Platform.runLater(() -> {
WebPage page = Accessor.getPageFor(webView.getEngine());
button.setDisable(! page.queryCommandEnabled(command));
button.setSelected(page.queryCommandState(command));
});
};
webView.addEventHandler(KeyEvent.ANY, updateState);
webView.addEventHandler(MouseEvent.MOUSE_PRESSED, updateState);
webView.addEventHandler(MouseEvent.MOUSE_RELEASED, updateState);
}
public static void main(String[] args) {
launch(args);
}
}
If you need a robust approach for creating an equation editor, I would probably consider building your own, instead of using/hacking HTMLEditor. There is a third party library, RichTextFX that can be used to create an editable text area with varying styles. Start there and add your own controls for styling.
Related
In a simple drag and drop example program where strings can be dragged from a list cell to a text field, everything works fine, except that after a drop, the mouse does not behave normally: for example, I have to click twice on the window close button for the window to close.
However, in the following cases, everything works fine:
Using a label instead of a text field (but the problem remains with a text area)
Dropping a string into the text field from another application
Dragging the string from the list cell onto another application
Clicking anywhere in the window also 'frees' the close button - even outside the list or the text field.
Edited: Below is a minimal working example (class DnD in package dnd). Note in particular the drag and drop handlers used on the field and on the list cell. To reproduce the problem: run the program, drag Banana from the list to the text field and then try to close the window. In my case I have to click twice.
package dnd;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Dnd extends Application {
#Override
public void start(Stage stage) {
stage.setScene(new Scene(new DndPanel(), 200, 300));
stage.setTitle("Drag and drop");
stage.setResizable(false);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class DndPanel extends VBox {
public DndPanel() {
super(10);
TextField field = new TextField();
ListView<String> list = new ListView<>();
list.setCellFactory(Dnd.DnDCell::new);
list.getItems().addAll("Apple", "Pear", "Banana", "Grapes", "Peach", "Pineapple");
setPadding(new Insets(10));
getChildren().addAll(field, list);
field.setOnDragOver(ev -> {
if (ev.getDragboard().hasString()) {
ev.acceptTransferModes(TransferMode.COPY);
}
ev.consume();
});
field.setOnDragDropped(ev -> {
field.setText(ev.getDragboard().getString());
ev.setDropCompleted(true);
ev.consume();
});
}
}
public static class DnDCell extends TextFieldListCell<String> {
public DnDCell(ListView<String> listView) {
setOnDragDetected(ev -> {
Dragboard dragboard = startDragAndDrop(TransferMode.COPY);
ClipboardContent cc = new ClipboardContent();
cc.putString(listView.getItems().get(getIndex()));
dragboard.setContent(cc);
ev.consume();
});
}
}
}
Tested on JavaFX 11.0.2 from Gluon on Ubuntu Linux 18.04 with additional
JVM runtime option -Djdk.gtk.version=2.
I am trying to implement the Slider such that user can drag only by given increments. I tried in different ways by using the Slider API, but didnt get the desired results. Below is a quick demo of what I had tried. I am expecting to drag the thumb only in increments of 10 not with intermediate values. snapToTicks is doing what I required, but only after finishing the drag. I am trying to not move the thumb till the next desired block increment is reached.
Can anyone let me know how can i achieve this. Below is the screenshot while dragging.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SliderDemo extends Application {
public static void main(String... args){
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Label label = new Label();
label.setStyle("-fx-font-size:30px");
Slider slider = new Slider(5,240,5);
slider.setBlockIncrement(10);
slider.setMajorTickUnit(10);
slider.setMinorTickCount(0);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.setSnapToTicks(true);
slider.valueProperty().addListener((obs,old,val)->label.setText((int)Math.round(val.doubleValue())+""));
VBox root = new VBox(slider,label);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
root.setSpacing(20);
Scene scene = new Scene(root,600,200);
stage.setScene(scene);
stage.show();
}
}
The solution is to set the value of the slider directly inside of the listener. The listener will not be called again
final ChangeListener<Number> numberChangeListener = (obs, old, val) -> {
final double roundedValue = Math.floor(val.doubleValue() / 10.0) * 10.0;
slider.valueProperty().set(roundedValue);
label.setText(Double.toString(roundedValue));
};
slider.valueProperty().addListener(numberChangeListener);
If you use Math.floor() instead of round you get a more intuatuive behavior of the thumb.
I created an Alert that is undecorated and I want the content to be centered in the alert. However, there seems to be some padding at the bottom that I can not get rid of.
Here is an MCVE:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Alert alert = new Alert(Alert.AlertType.NONE);
DialogPane pane = new DialogPane();
HBox contentPane = new HBox(10);
contentPane.setPadding(new Insets(10));
contentPane.setAlignment(Pos.CENTER);
// Add progress indicator and label to contentPane
contentPane.getChildren().addAll(
new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS) {{
setPrefSize(30, 30);
}},
new Label("Loading ...")
);
// Add border to demonstate the lower padding
contentPane.setStyle("-fx-border-color: black");
pane.setPadding(new Insets(10));
pane.setStyle("-fx-border-color: black");
pane.setContent(contentPane);
alert.setDialogPane(pane);
alert.initStyle(StageStyle.UNDECORATED);
alert.showAndWait();
}
}
The result is this window:
How do I create an Alert or DialogPane without that gap at the bottom?
Using:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
private static Border createBorder(Paint stroke) {
return new Border(new BorderStroke(stroke, BorderStrokeStyle.SOLID,
null, new BorderWidths(2)));
}
#Override
public void start(Stage primaryStage) throws Exception {
ProgressIndicator indicator = new ProgressIndicator();
indicator.setPrefSize(30, 30);
Label label = new Label("Loading...");
label.setMinWidth(Label.USE_PREF_SIZE);
HBox content = new HBox(10, indicator, label);
content.setBorder(createBorder(Color.BLUE));
content.setPadding(new Insets(10));
content.setAlignment(Pos.CENTER);
Alert alert = new Alert(Alert.AlertType.NONE);
alert.initStyle(StageStyle.UNDECORATED);
alert.getDialogPane().getStylesheets()
.add(getClass().getResource("Main.css").toExternalForm());
alert.getDialogPane().setPadding(new Insets(10));
alert.getDialogPane().setContent(content);
alert.getDialogPane().setBorder(createBorder(Color.RED));
alert.show();
}
}
Where this is Main.css:
.dialog-pane .button-bar .container {
-fx-padding: 0px;
}
.dialog-pane:no-header .graphic-container {
-fx-padding: 0px;
}
Resulted in the following:
Some of the needed style-classes I got from here (JavaFX CSS Reference). However, I mostly figured this out from looking at modena.css (where they have the styles for dialog-pane).
If you don't want to use external CSS you can replace alert.getDialogPane().getStylesheets().add(...) with:
alert.getDialogPane().applyCss(); // Seems to stop the lookup calls
// from returning null
alert.getDialogPane()
.lookup(".button-bar")
.lookup(".container")
.setStyle("-fx-padding: 0px;");
alert.getDialogPane().lookup(".graphic-container").setStyle("-fx-padding: 0px;");
alert.show();
Update following your comments.
I initially tried this using Java 10.0.2 but I just tried using Java 8u181 and it still worked for me. I notice in your MCVE you are using a new DialogPane whereas I am using the DialogPane that initially comes with the Alert. Note that the applyCss() method will only work if the Node is a part of a Scene. Apparently the DialogPane is made part of a Scene as soon as it's part of the Alert. So one way you could avoid the NullPointerExceptions is to use the DialogPane that comes with the Alert, as I do.
If you still want to use your own DialogPane you just have to call alert.setDialogPane before calling applyCss(). Note that when I tried this, however, the call lookup(".graphic-container") still returned null. I guess that Node is only present on the original DialogPane. Removing that lookup call fixed it and the other lookup call (for .button-bar > .container) still worked as expected.
All this seems like implementation behavior that should not be relied upon but it works for me on both Java 8 and Java 10. I'd watch this code when changing Java versions just in case though.
I'm writing a small configuration-wizard-style JavaFX application. I want to force the user through different steps of configuration panes. So I've chosen an accordion container with a few panels. The user is only allowed to proceed to the NEXT or PREVIOUS accordion panel, but may not jump from panel 1 to 3.
I achived this by adding the "Previous" and "Next" button under the accordion and I set the mouse transparency of the accordion to true. Everything works fine so far. The user can walk from panel 1 to 5 and back...
Unfortunately (for any strange reason) the user cannot configure nothing inside the different panels. Wait... I set the mouse transparency to "true", right... But when I set it to false, the user can jump through the panels without any order.
Any suggestions?
One possible way is to set the collapsible property of all the panes to false, and to set each one to true just when it needs to be expanded or collapsed (which you can do programmatically). This is a little ugly, but here's an example:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class AccordionBasedWizard extends Application {
#Override
public void start(Stage primaryStage) {
Accordion accordion = new Accordion();
for (int i = 0 ; i < 5 ; i++) {
TitledPane pane = new TitledPane("Page "+(i+1), new Label("Wizard page "+(i+1)));
accordion.getPanes().add(pane);
pane.setCollapsible(false);
}
accordion.setExpandedPane(accordion.getPanes().get(0));
Button previousButton = new Button("Previous");
previousButton.disableProperty().bind(accordion.expandedPaneProperty().isEqualTo(accordion.getPanes().get(0)));
previousButton.setOnAction(e -> {
TitledPane current = accordion.getExpandedPane();
int index = accordion.getPanes().indexOf(current);
TitledPane previous = accordion.getPanes().get(index - 1);
current.setCollapsible(true);
previous.setCollapsible(true);
accordion.setExpandedPane(previous);
previous.setCollapsible(false);
current.setCollapsible(false);
});
Button nextButton = new Button("Next");
nextButton.disableProperty().bind(accordion.expandedPaneProperty().isEqualTo(accordion.getPanes().get(accordion.getPanes().size()-1)));
nextButton.setOnAction(e -> {
TitledPane current = accordion.getExpandedPane();
int index = accordion.getPanes().indexOf(current);
TitledPane next = accordion.getPanes().get(index + 1);
current.setCollapsible(true);
next.setCollapsible(true);
accordion.setExpandedPane(next);
next.setCollapsible(false);
current.setCollapsible(false);
});
HBox buttons = new HBox(5, previousButton, nextButton);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(5));
BorderPane root = new BorderPane(accordion);
root.setBottom(buttons);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For what it's worth, I probably wouldn't try to use an Accordion as a wizard, and would just use a BorderPane, with the current "wizard page" in the center and next and previous buttons in the bottom. You can add a breadcrumb trail too if you need.
I am trying to add an emoji to my chat program when my client types :)
I am trying to add this in the FXML controller. I have captured when the user types :) using the following code snippet :
if(chat.contains(":)")) {
...
}
My chat is printed into a textarea named taChat
taChat.appendText(chat + '\n');
Any help is appreciated!
A better approach would be to use TextFlow instead of using TextArea.
Advantages :
Individual Text are treated as children to the TextFlow. They can be added and accessed individually.
ImageView can be added directly to the TextFlow as a child.
A simple chat window with support for smiley :)
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
public class ChatWindowWithSmiley extends Application {
public void start(Stage primaryStage) {
TextFlow textFlow = new TextFlow();
textFlow.setPadding(new Insets(10));
textFlow.setLineSpacing(10);
TextField textField = new TextField();
Button button = new Button("Send");
button.setPrefWidth(70);
VBox container = new VBox();
container.getChildren().addAll(textFlow, new HBox(textField, button));
VBox.setVgrow(textFlow, Priority.ALWAYS);
// Textfield re-sizes according to VBox
textField.prefWidthProperty().bind(container.widthProperty().subtract(button.prefWidthProperty()));
// On Enter press
textField.setOnKeyPressed(e -> {
if(e.getCode() == KeyCode.ENTER) {
button.fire();
}
});
button.setOnAction(e -> {
Text text;
if(textFlow.getChildren().size()==0){
text = new Text(textField.getText());
} else {
// Add new line if not the first child
text = new Text("\n" + textField.getText());
}
if(textField.getText().contains(":)")) {
ImageView imageView = new ImageView("http://files.softicons.com/download/web-icons/network-and-security-icons-by-artistsvalley/png/16x16/Regular/Friend%20Smiley.png");
// Remove :) from text
text.setText(text.getText().replace(":)"," "));
textFlow.getChildren().addAll(text, imageView);
} else {
textFlow.getChildren().add(text);
}
textField.clear();
textField.requestFocus();
});
Scene scene = new Scene(container, 300, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Output
For unicode Emoji support, please visit How to support Emojis