JavaFX PiChart, my hover values blink - javafx

Context :
Hi !
I'm trying to create a little popup which display the value of slice when mouse hover, on my PieChart (with JavaFX).
I successed on my LineChart, AreaChart etc.. Thanks this post : JavaFX LineChart Hover Values (thank you so much Jewelsea for your help).
Problem (1/2) :
But with the PieChart, I have a problem : The popup is blinking oO
My code :
With syntactic color : https://bpaste.net/show/12838ad6b2e2
import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ListRepere;
import com.alpha.client.view.nodes.stats.statsEngine.beans.OptionsChart;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ValueStat;
/**
*
* #author Zombkey
*/
public class PieChartNode implements ChartNode {
//My personnal attributes
private ListRepere categories;
private ArrayList<ValueStat> values;
//The PieChart
private PieChart chart;
//The data of Chart, will be fill by a thread
private ObservableList<PieChart.Data> pieChartData;
//The node which contain chart and label
private Group group;
//The Label
private final Label caption;
public PieChartNode(ListRepere categories, ArrayList<ValueStat> values, OptionsChart optionsChart) {
this.categories = categories;
this.values = values;
//New Group
group = new Group();
//I must use a StackPane to place Label hover Chart
StackPane pane = new StackPane();
group.getChildren().add(pane);
//Init' PieChart
pieChartData = FXCollections.observableArrayList();
chart = new PieChart(pieChartData);
chart.setStartAngle(180.0);
//Add chart to StackPane
pane.getChildren().add(chart);
//Init Popup(Label)
caption = new Label("");
caption.setVisible(false);
caption.getStyleClass().addAll("chart-line-symbol", "chart-series-line");
caption.setStyle("-fx-font-size: 12; -fx-font-weight: bold;");
caption.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
//Add Label to StackPane
pane.getChildren().add(caption);
}
#Override
public Node getNodeGraph() {
return (Node) group;
}
#Override
public Task initTaskFormat() {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
//i and sizeOfallElements are just use for ProgressBar
int i = 0;
int sizeOfallElements = values.size();
updateProgress(i, sizeOfallElements);
//For Each ValueStat (a Personnal pojo Class), I must create a slice
for (ValueStat v : values) {
//Create the PieChart.Data and add it to ObservableList
PieChart.Data dataTemp = new PieChart.Data(v.getCategorie().getStringName(), v.getDoubleValue());
pieChartData.add(dataTemp);
//HERE, the interessante code !
//At the same way that the LineChart, I add Event when mouse entered and mouse exited.
//When mouse entered (on the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("MOUSE_ENTERED : "+dataTemp.getName());
//I display Label
caption.setVisible(true);
//I move Label near the mouse cursor
caption.setTranslateX(e.getX());
caption.setTranslateY(e.getY());
//I hide the mouse cursor
dataTemp.getNode().setCursor(Cursor.NONE);
//I change text of Label
caption.setText(String.valueOf(dataTemp.getPieValue()) + "\n" + dataTemp.getName());
//I try to change the frame color of Label
caption.getStyleClass().add(dataTemp.getNode().getStyleClass().get(2));
}
});
//When mouse exited (the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_EXITED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("MOUSE_EXITED : "+dataTemp.getName());
//I Hide Label
caption.setVisible(false);
//I show the mouse cursor
dataTemp.getNode().setCursor(Cursor.DEFAULT);
}
});
//Update progress
updateProgress(i++, sizeOfallElements);
}
return null;
}
};
return task;
}
}
Problem (2/2) :
The problem is that the events (MOUSE_ENTERED and MOUSE_EXITED) are emitted, too often instead of once.
Ex :
I just put in, then put off, my mouse hover a slice.
Here the result on console :
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
Anyone know why the event bug ?
Thanks : )

It not the blinking effect caused by label?
When you shows the label, it means that you exited the node which is listened. This causes hiding the label. When label disappears, it fires the mouse entered event on the node, it shows the label etc.
Not tested, just an idea.
EDIT:
If I am right, try to avoid putting label under the mouse pointer:
caption.setTranslateX(e.getX()+10);
caption.setTranslateY(e.getY()+10);
For example (10 is a magic number, depends on insets etc.)

Thanks all for your help.
#maskacovnik to find the problem, #James_D to find a cool solution, and #ItachiUchiha to put my image on my post : D
Now, my new code.
import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ListRepere;
import com.alpha.client.view.nodes.stats.statsEngine.beans.OptionsChart;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ValueStat;
public class PieChartNode implements ChartNode {
//My personnal attributes
private ListRepere categories;
private ArrayList<ValueStat> values;
//The PieChart
private PieChart chart;
//The data of Chart, will be fill by a thread
private ObservableList<PieChart.Data> pieChartData;
//The node which contain chart and label
private Group group;
//The Label
private final Label caption;
public PieChartNode(ListRepere categories, ArrayList<ValueStat> values, OptionsChart optionsChart) {
this.categories = categories;
this.values = values;
//New Group
group = new Group();
//I must use a StackPane to place Label hover Chart
StackPane pane = new StackPane();
group.getChildren().add(pane);
//Init' PieChart
pieChartData = FXCollections.observableArrayList();
chart = new PieChart(pieChartData);
chart.setStartAngle(180.0);
//Add chart to StackPane
pane.getChildren().add(chart);
//Init Popup(Label)
caption = new Label("");
caption.setVisible(false);
caption.getStyleClass().addAll("chart-line-symbol", "chart-series-line");
caption.setStyle("-fx-font-size: 12; -fx-font-weight: bold;");
caption.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
//Add Label to StackPane
pane.getChildren().add(caption);
}
#Override
public Node getNodeGraph() {
return (Node) group;
}
#Override
public Task initTaskFormat() {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
//i and sizeOfallElements are just use for ProgressBar
int i = 0;
int sizeOfallElements = values.size();
updateProgress(i, sizeOfallElements);
//For Each ValueStat (a Personnal pojo Class), I must create a slice
for (ValueStat v : values) {
//Create the PieChart.Data and add it to ObservableList
PieChart.Data dataTemp = new PieChart.Data(v.getCategorie().getStringName(), v.getDoubleValue());
pieChartData.add(dataTemp);
//At the same way that the LineChart, I add Event when mouse entered and mouse exited.
//When mouse entered (on the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
//Set Label ignores the mouse
caption.setMouseTransparent(true);
//I move Label near the mouse cursor, with a offset !
caption.setTranslateX(e.getX());
caption.setTranslateY(e.getY()+20);
//I change text of Label
caption.setText(String.valueOf(dataTemp.getPieValue()) + "\n" + dataTemp.getName());
//Change the color of popup, to adapt it to slice
if(caption.getStyleClass().size() == 4){
caption.getStyleClass().remove(3);
}
caption.getStyleClass().add(dataTemp.getNode().getStyleClass().get(2));
//I display Label
caption.setVisible(true);
}
});
//Need to add a event when the mouse move hover the slice
//If I don't the popup stay blocked on edges of the slice.
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_MOVED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
//Keep Label near the mouse
caption.setTranslateX(e.getX());
caption.setTranslateY(e.getY()+20);
}
});
//When mouse exited (the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_EXITED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
//I Hide Label
caption.setVisible(false);
}
});
//Update progress
updateProgress(i++, sizeOfallElements);
}
return null;
}
};
return task;
}
}
Here the result :

I had the same problem but also wanted to make sure that the popup can extend beyond the chart, i.e. that it does not get cut off when the text does not fit in the chart. Here's a solution using a Tooltip instead of a Label:
public class ChartHoverUtil<T> {
public static void setupPieChartHovering(PieChart chart) {
new ChartHoverUtil<PieChart.Data>(
data -> String.format("Value = ", data.getPieValue()),
data -> data.getNode())
.setupHovering(chart.getData());
}
private final Tooltip tooltip = new Tooltip();
private final SimpleBooleanProperty adjustingTooltip = new SimpleBooleanProperty(false);
private final Function<T, String> textProvider;
private final Function<T, Node> nodeProvider;
private EventHandler<MouseEvent> moveHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (tooltip.isShowing()) {
setLabelPosition(e);
}
}
};
private EventHandler<MouseEvent> enterHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
adjustingTooltip.set(true);
Node chartNode = (Node) e.getSource();
tooltip.show(chartNode, e.getScreenX(), e.getScreenY());
setLabelPosition(e);
ObservableBooleanValue stillHovering = chartNode.hoverProperty().or(adjustingTooltip);
stillHovering.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean nowHovering) {
if (!nowHovering) {
stillHovering.removeListener(this);
tooltip.hide();
}
}
});
T chartData = (T) chartNode.getUserData();
String txt = textProvider.apply(chartData);
tooltip.setText(txt);
adjustingTooltip.set(false);
}
};
public ChartHoverUtil(Function<T, String> textProvider, Function<T, Node> getNode) {
this.textProvider = textProvider;
this.nodeProvider = getNode;
tooltip.addEventFilter(MouseEvent.MOUSE_MOVED, moveHandler);
}
public void setupHovering(Collection<T> data) {
for (T chartData : data) {
Node node = nodeProvider.apply(chartData);
node.setUserData(chartData);
setupNodeHovering(node);
}
}
private void setupNodeHovering(Node node) {
node.addEventFilter(MouseEvent.MOUSE_MOVED, moveHandler);
node.addEventHandler(MouseEvent.MOUSE_ENTERED, enterHandler);
// Do not use MOUSE_EXIT handler because it is triggered immediately when showing the tooltip
}
private void setLabelPosition(MouseEvent e) {
adjustingTooltip.set(true);
tooltip.setAnchorX(e.getScreenX());
tooltip.setAnchorY(e.getScreenY() + 20);
adjustingTooltip.set(false);
}
}

Related

How can I change the scene By pressing a specific key(b) on the the keyboard?

In my application, there are two scenes: mainScene and bossScene where mainScene is used when starting up the application.
I'm trying to implement the boss key functionality where by pressing the 'b' key on the the keyboard should change the scene to bossScene. And also by pressing the button in bossScene should switch back to mainScene.
I'm getting an error on InteliJ saying "Cannot resolve method setOnKeyPressed in List
My Code:
public void start(Stage stage) throws Exception {
stage.setTitle("BossKey Example");
// Scene and layout for the main view
VBox root = new VBox();
Scene mainScene = new Scene(root, 500, 300);
// Scene for the BOSS view
Scene bossScene = new Scene(new Label("Nothing suspicious here"), 500, 300);
List<TextField> fields = new ArrayList<TextField>();
for (int i = 0; i < 10; i++) {
fields.add(new TextField());
}
fields.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
}
});
/////// Added addEventFilter, still not working
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, new
EventHandler<KeyEvent() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
keyEvent.consume();
}
});
// Create components for main view
root.getChildren().addAll(fields);
root.getChildren().add(new Button("Hello!"));
stage.setScene(mainScene);
stage.show();
}
}
KeyCombination filters
You should use a key combination in an event filter, e.g., CTRL+B or SHORTCUT+B.
For details on how to apply key combinations, see:
javafx keyboard event shortcut key
Why a key combination is superior to filtering on the character "b":
If you filter on a "b" character, the feature won't work if caps lock is down.
If you filter on a "b" character, you will be unable to type "b" in the text field.
You might think you could write scene.setOnKeyPressed(...), however, that won't work as expected in many cases. A filter is required rather than a key press event handler because the key events may be consumed by focused fields like text fields if you use a handler, so a handler implementation might not activate in all desired cases.
Filtering on a key combination avoids the issues with trying to handle a character key press. The key combinations rely on key codes which represent the physical key pressed and don't rely on the state of other keys such as caps lock unless you explicitly add additional logic for that.
If you don't understand the difference between an event filter and an event handler and the capturing and bubbling phases of event dispatch, then study:
the oracle event handling tutorial.
KeyCombination filter implementation
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
Accelerator alternative
An accelerator could be used instead of an event filter. Information on applying an accelerator is also in an answer to the linked question, I won't detail this alternative further here.
Example Solution
Standalone executable example code:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
public class SceneSwap extends Application {
#Override
public void start(Stage stage) throws IOException {
final Scene mainScene = new Scene(
createLayout(
"Press CTRL+B to enter boss mode",
Color.PALEGREEN
)
);
final Scene bossScene = new Scene(
createLayout(
"Press CTRL+B to exit boss mode",
Color.PALEGOLDENROD
)
);
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
stage.setScene(mainScene);
stage.show();
}
private VBox createLayout(String text, Color color) {
VBox mainLayout = new VBox(10,
new Label(text),
new TextField()
);
mainLayout.setPadding(new Insets(10));
mainLayout.setStyle("-fx-background: " + toCssColor(color));
return mainLayout;
}
private String toCssColor(Color color) {
int r = (int) Math.round(color.getRed() * 255.0);
int g = (int) Math.round(color.getGreen() * 255.0);
int b = (int) Math.round(color.getBlue() * 255.0);
int o = (int) Math.round(color.getOpacity() * 255.0);
return String.format("#%02x%02x%02x%02x" , r, g, b, o);
}
public static void main(String[] args) {
launch();
}
}

JavaFX drag thin line on chart

I have a horizontal line drawn on a chart. When hovering over it the cursor changes to CursorType.S_RESIZE. That indicates the user can start to drag. As the line is very thin you have to place the cursor very accurate. For a better user experience I would like to add a margin above and below the line to enter the draggable zone easier.
Is there a way to make the line “thicker” so the setOnMouseMoved() event fires already when approaching?
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class DragLine extends Application {
public void start(Stage stage) {
ChartWithLine chartWithLine = new ChartWithLine(new NumberAxis(), new NumberAxis());
stage.setScene(new Scene(chartWithLine, 500, 400));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class ChartWithLine<X, Y> extends LineChart {
public ChartWithLine(Axis axis, Axis axis2) {
super(axis, axis2);
line = new Line();
line.setOnMouseMoved(event -> line.setCursor(Cursor.S_RESIZE));
getPlotChildren().add(line);
}
private Line line;
public void layoutPlotChildren() {
super.layoutPlotChildren();
double yPos = getYAxis().getDisplayPosition(55);
line.setStartX(0);
line.setEndX(getBoundsInLocal().getWidth());
line.setStartY(yPos);
line.setEndY(yPos);
}
}
Here is my work around. I am plotting a second, thicker line at the same position where I plot the thin line. The thicker line is set to transparent so it is not visible. The dragging functionality is set to the thicker line. When it is dragged, both lines are plotted to the dragged location.
This solves the issue that it is hard to grab a thin line with the mouse. But I do not really like it for two reasons. First there is a second line I do not need at all actually. And second if you hover over the middle of the thicker line where the thin visible line is located, the mouse changes back to not draggable cursor. I would have to implement dragability for the thinner line now too to avoid this. But this is pretty much overkill.
Again, is there some kind of way to set the line thicker without making it look thicker?
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class DragLine extends Application {
public void start(Stage stage) {
ChartWithLine chartWithLine = new ChartWithLine(new NumberAxis(), new NumberAxis());
stage.setScene(new Scene(chartWithLine, 500, 400));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
//ChartWithLine
class ChartWithLine<X, Y> extends LineChart {
public ChartWithLine(Axis axis, Axis axis2) {
super(axis, axis2);
draggableLine = new DraggableLine(this);
getPlotChildren().addAll(draggableLine.lineToShow, draggableLine.lineToDrag);
}
private DraggableLine draggableLine;
public void layoutPlotChildren() {
super.layoutPlotChildren();
updateLinePlot();
}
//updateLinePlot called when line was dragged
public void updateLinePlot() {
//mouse position after drag
double yPos = draggableLine.mousePosY;
System.out.println("Line dragged to: " + getYAxis().getValueForDisplay(yPos));
//plot lines accordingly to new mouse position
Line line = draggableLine.lineToDrag;
line.setStartX(0);
line.setEndX(getBoundsInLocal().getWidth());
line.setStartY(yPos);
line.setEndY(yPos);
line = draggableLine.lineToShow;
line.setStartX(0);
line.setEndX(getBoundsInLocal().getWidth());
line.setStartY(yPos);
line.setEndY(yPos);
}
}
//DraggableLine
class DraggableLine {
public DraggableLine(ChartWithLine chart) {
this.chart = chart;
//lineToShow is thin line plotted visible on chart
lineToShow = new Line();
//lineToDrag is plotted at same position on chart as thin visible line.
lineToDrag = new Line();
//set transparent to make it not visible
lineToDrag.setStyle("-fx-stroke: transparent;");
//set line to drag stroke width very broad so it is easy to grab
lineToDrag.setStrokeWidth(20.0);
lineToDrag.setOnMouseMoved(this::mouseOver);
lineToDrag.setOnMouseDragged(event -> onMouseDragged(event.getY()));
lineToDrag.setOnMousePressed(this::onMousePressed);
lineToDrag.setOnMouseReleased(event -> onMouseReleased());
}
private ChartWithLine chart;
public Line lineToShow;
public Line lineToDrag;
boolean isDragging = false;
public double mousePosY = 55;
//change cursor
protected void mouseOver(MouseEvent event) {
if (isDragZone(event)) {
lineToDrag.setCursor(Cursor.S_RESIZE);
} else {
lineToDrag.setCursor(Cursor.DEFAULT);
}
}
//mouse pressed over draggable zone
void onMousePressed(MouseEvent event) {
if (isDragZone(event))
isDragging = true;
}
//mouse released
void onMouseReleased() {
isDragging = false;
}
//change values when mouse is dragging
void onMouseDragged(double y) {
if (!isDragging) return;
mousePosY = y;
chart.updateLinePlot();
}
//check if mouse is in draggable zone
protected boolean isDragZone(MouseEvent event) {
return event.getY() > (lineToDrag.getStartY()) || event.getY() < (lineToDrag.getStartY());
}
}

HTMLEditor subscript and superscript text

I have been trying to show subscript and superscript text in HTMLEditor. there are two buttons for sub and sup mode. the user types the (sub/sup)text in a textfield and press the OK button which allows the textfield text to be rendered as sub or sup in HTMLEditor. The code is as follows:
import java.util.List;
import java.util.regex.Pattern;
import javafx.application.*;
import javafx.collections.FXCollections;
import javafx.event.*;
import javafx.geometry.Orientation;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.web.HTMLEditor;
import javafx.stage.Stage;
public class HTMLEditorCustomizationSample extends Application {
// limits the fonts a user can select from in the html editor.
private static final List<String> limitedFonts = FXCollections.observableArrayList("Arial", "Times New Roman", "Courier New", "Comic Sans MS");
String sup = " ⁺⁻⁼⁽⁾⁰¹²³⁴⁵⁶⁷⁸⁹ᴬᵃᴭᵆᵄᵅᶛᴮᵇᶜᶝᴰᵈᶞᴱᵉᴲᵊᵋᶟᵌᶠᴳᵍᶢˠʰᴴʱᴵⁱᶦᶤᶧᶥʲᴶᶨᶡᴷᵏˡᴸᶫᶪᶩᴹᵐᶬᴺⁿᶰᶮᶯᵑᴼᵒᵓᵔᵕᶱᴽᴾᵖᶲʳᴿʴʵʶˢᶳᶴᵀᵗᶵᵁᵘᶸᵙᶶᶣᵚᶭᶷᵛⱽᶹᶺʷᵂˣʸᶻᶼᶽᶾꝰᵜᵝᵞᵟᶿᵠᵡᵸჼˤⵯ";
String supchars = " +−=()0123456789AaÆᴂɐɑɒBbcɕDdðEeƎəɛɜɜfGgɡɣhHɦIiɪɨᵻɩjJʝɟKklLʟᶅɭMmɱNnɴɲɳŋOoɔᴖᴗɵȢPpɸrRɹɻʁsʂʃTtƫUuᴜᴝʉɥɯɰʊvVʋʌwWxyzʐʑʒꝯᴥβγδθφχнნʕⵡ";
String subchars=" +−=()0123456789aeəhijklmnoprstuvxβγρφχ";
String sub=" ₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₔₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ";
char[] csup = sup.toCharArray();
char[] characters = supchars.toCharArray();
char[] csub = sub.toCharArray();
char[] character = subchars.toCharArray();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
// create a new html editor and show it before we start modifying it.
final HTMLEditor htmlEditor = new HTMLEditor();
stage.setScene(new Scene(htmlEditor));
stage.show();
// hide controls we don't need.
hideImageNodesMatching(htmlEditor, Pattern.compile(".*(Cut|Copy|Paste).*"), 0);
Node seperator = htmlEditor.lookup(".separator");
seperator.setVisible(false);
seperator.setManaged(false);
// modify font selections.
int i = 0;
for (Node candidate : (htmlEditor.lookupAll("MenuButton"))) {
// fonts are selected by the second menu in the htmlEditor.
if (candidate instanceof MenuButton && i == 1) {
// limit the font selections to our predefined list.
MenuButton menuButton = (MenuButton) candidate;
List<MenuItem> removalList = FXCollections.observableArrayList();
final List<MenuItem> fontSelections = menuButton.getItems();
for (MenuItem item : fontSelections) {
if (!limitedFonts.contains(item.getText())) {
removalList.add(item);
}
}
fontSelections.removeAll(removalList);
// Select a font from out limited font selection.
// Selection done in Platform.runLater because if you try to do
// the selection immediately, it won't take place.
Platform.runLater(new Runnable() {
#Override
public void run() {
boolean fontSelected = false;
for (final MenuItem item : fontSelections) {
if ("Comic Sans MS".equals(item.getText())) {
if (item instanceof RadioMenuItem) {
((RadioMenuItem) item).setSelected(true);
fontSelected = true;
}
}
}
if (!fontSelected && fontSelections.size() > 0 && fontSelections.get(0) instanceof RadioMenuItem) {
((RadioMenuItem) fontSelections.get(0)).setSelected(true);
}
}
});
}
i++;
}
// add a custom button to the top toolbar.
Node node = htmlEditor.lookup(".top-toolbar");
if (node instanceof ToolBar) {
ToolBar bar = (ToolBar) node;
ToggleButton supButton = new ToggleButton("x²");
ToggleButton subButton = new ToggleButton("x₂");
TextField txt = new TextField();
Button okBtn = new Button("OK");
Button clrBtn = new Button("CLEAR");
ToggleGroup group = new ToggleGroup();
supButton.setToggleGroup(group);
subButton.setToggleGroup(group);
Separator v1=new Separator();
v1.setOrientation(Orientation.VERTICAL);
Separator v2=new Separator();
v2.setOrientation(Orientation.VERTICAL);
txt.setDisable(true);
okBtn.setDisable(true);;
clrBtn.setDisable(true);
bar.getItems().add(v1);
bar.getItems().add(supButton);
bar.getItems().add(subButton);
bar.getItems().add(v2);
bar.getItems().add(txt);
bar.getItems().add(okBtn);
bar.getItems().add(clrBtn);
okBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
System.out.println(htmlEditor.getHtmlText());
if (supButton.isSelected()) {
txt.setPromptText(" Enter the superscript text ");
String text = htmlEditor.getHtmlText().replaceAll("</p></body></html>", "");
text = text.replaceAll("<html dir=\"ltr\"><head></head><body contenteditable=\"true\"><p>", "");
System.out.println(text);
text="<p>"+text + "<sup>"+ txt.getText()+"</sup></p>";
System.out.println(text);
htmlEditor.setHtmlText(text);
}
else if (subButton.isSelected()) {
txt.setPromptText(" Enter the superscript text ");
String text = htmlEditor.getHtmlText().replaceAll("</p></body></html>", "");
text = text.replaceAll("<html dir=\"ltr\"><head></head><body contenteditable=\"true\"><p>", "");
System.out.println(text);
text=text + "<sub>"+ txt.getText()+"</sup></p>";
System.out.println(text);
htmlEditor.setHtmlText(text);
}
}
});
clrBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
txt.clear();
}
});
supButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
if (supButton.isSelected()) {
txt.setPromptText(" Enter the superscript text ");
txt.setDisable(false);
okBtn.setDisable(false);;
clrBtn.setDisable(false);
}
}
});
subButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
if (subButton.isSelected()) {
txt.setPromptText(" Enter the subscript text ");
txt.setDisable(false);
okBtn.setDisable(false);;
clrBtn.setDisable(false);
}
}
});
}
}
private String convertSupText(String dsup) {
char[] cdsup = dsup.toCharArray();
String data="";
for (int i = 0; i < cdsup.length; i++) {
for (int j = 0; j < characters.length; j++) {
if (cdsup[i] == characters[j]) {
data = data + csup[j];
}
}
}
return data;
}
private String convertSubText(String dsup) {
char[] cdsup = dsup.toCharArray();
String data="";
for (int i = 0; i < cdsup.length; i++) {
for (int j = 0; j < character.length; j++) {
if (cdsup[i] == character[j]) {
data = data + csub[j];
}
}
}
return data;
}
// hide buttons containing nodes whose image url matches a given name pattern.
public void hideImageNodesMatching(Node node, Pattern imageNamePattern, int depth) {
if (node instanceof ImageView) {
ImageView imageView = (ImageView) node;
String url = imageView.getImage().impl_getUrl();
if (url != null && imageNamePattern.matcher(url).matches()) {
Node button = imageView.getParent().getParent();
button.setVisible(false);
button.setManaged(false);
}
}
if (node instanceof Parent) {
for (Node child : ((Parent) node).getChildrenUnmodifiable()) {
hideImageNodesMatching(child, imageNamePattern, depth + 1);
}
}
}
}
The problem is that after adding the subscript or superscript text, the cursor still remains in subscript or superscript mode and every time the text is added it goes on a newline.
#Manoj I think your problem is that you don't know what the HTMLeditor is doing with any text you enter in the textfield (aka WebPage). Appearantly it is applying the your <sub> tag to the next text you enter (adding 1 and typing a normal 2 afterwards results in 12):
<html dir="ltr"><head></head><body contenteditable="true"><p><br><sup>1</sup></p></body></html>
<html dir="ltr"><head></head><body contenteditable="true"><p><br><sup>1<font size="2">1</font></sup></p></body></html>
I looked into the files (HTMLEditor>HTMLEditorSkin>WebPage>twkExecuteCommand) and in the end commands like bold/italic are executed in a dll (jfxwebkit). My knowledge is exceeded here. I see no solution which would not involve rewriting the whole HTMLEditor + native libraries.
(just included this in an answer bc comment length was exceeded)
thought so. I have done a work around using webview along with html editor. And it works fine for now. The code is as follows:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Separator;
import javafx.scene.control.Button;
import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.web.HTMLEditor;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
public class FXMLDocumentController implements Initializable {
#FXML
private HTMLEditor HE;
#FXML
private WebView WV;
WebEngine webEngine;
Button supButton;
Button subButton;
Tooltip sup;
Tooltip sub;
Alert info= new Alert(Alert.AlertType.INFORMATION);;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
webEngine = WV.getEngine();
supButton = new Button("x²");
subButton = new Button("x₂");
supButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
info.setTitle("SUCCESS");
info.setHeaderText("Information");
info.setContentText("Use <sup>Text to to superscripted</sup> to use superscript fuction.\n Press Preview button to preview the changes");
info.showAndWait();
}});
subButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
info.setTitle("SUCCESS");
info.setHeaderText("Information");
info.setContentText("Use <sub>Text to to subscripted</sub> to use subscript fuction.\n Press Preview button to preview the changes");
info.showAndWait();
}});
sup = new Tooltip();
sub = new Tooltip();
sup.setText(" Use <sup>Text to to superscripted</sup> to use superscript fuction.\n Press Preview button to preview the changes ");
sub.setText(" Use <sub>Text to to subscripted</sub> to use subscript fuction.\n Press Preview button to preview the changes ");
Node node = HE.lookup(".top-toolbar");
if (node instanceof ToolBar) {
ToolBar bar = (ToolBar) node;
Separator v2 = new Separator();
v2.setOrientation(Orientation.VERTICAL);
bar.getItems().add(supButton);
bar.getItems().add(subButton);
bar.getItems().add(v2);
}
supButton.setTooltip(sup);
subButton.setTooltip(sub);
}
#FXML
private void handleKeyTyped(ActionEvent event) {
String text = HE.getHtmlText();
text = text.replaceAll("<sup>", "<sup>");
text = text.replaceAll("</sup>", "</sup>");
text = text.replaceAll("<sub>", "<sub>");
text = text.replaceAll("</sub>", "</sub>");
webEngine.loadContent(text);
}
}

Display Popup with ProgressBar in JavaFX

How can I display my progress bar through pop up and automatically close if process is finished. Here is my code.
Task<ProgressForm> task = new Task<ProgressForm>() {
#Override
public ProgressForm call() throws InterruptedException{
ProgressForm pf = new ProgressForm();
for (int i = 1; i <= 10; i++) {
pf.activateProgressBar(this);
updateProgress(i, 10);
}
return pf;
}
};
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
ProgressForm pf = (ProgressForm)task.getValue();
pf.getDialogStage().close();
}
});
Thread th = new Thread(task);
th.run();
Progress form class:
private final Stage dialogStage;
private final ProgressBar pb = new ProgressBar();
private final ProgressIndicator pin = new ProgressIndicator();
public ProgressForm() {
dialogStage = new Stage();
dialogStage.initStyle(StageStyle.UTILITY);
dialogStage.setResizable(false);
dialogStage.initModality(Modality.APPLICATION_MODAL);
// PROGRESS BAR
final Label label = new Label();
label.setText("alerto");
pb.setProgress(-1F);
pin.setProgress(-1F);
final HBox hb = new HBox();
hb.setSpacing(5);
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(pb, pin);
Scene scene = new Scene(hb);
dialogStage.setScene(scene);
}
public void activateProgressBar(final Task task) throws InterruptedException {
pb.progressProperty().bind(task.progressProperty());
pin.progressProperty().bind(task.progressProperty());
dialogStage.show();
}
public Stage getDialogStage() {
return dialogStage;
}
The problem with this code is
if i use .show(), displaying pop up is smooth but NO PROGRESS BAR.
if i use .showAndWait(), displaying pop up requires manual exit for the pop up to close BUT Progress bar displays.
Any thoughts/ideas about this?
The two rules for multithreading in JavaFX are:
Code which modifies the UI (creates a Stage or changes properties
of nodes that are part of a scene graph) must be executed on the
JavaFX Application thread. Violating this rule will either throw
IllegalStateExceptions or result in unpredictable behavior.
Code which takes a long time to execute should be executed in a background thread (i.e. not the FX Application Thread). Violating this rule will cause the UI to become unresponsive.
Your code violates the first rule, because it calls the ProgressForm constructor in a background thread. You should set up the UI first, show the dialog, and then start the background thread.
Note that there is no need to repeatedly bind the progress properties of the progress bar and indicator to the progress property of the task. Once it is bound, it will remain bound until and unless you unbind it.
It's quite hard to fix your code as it stands, because your background task doesn't actually do anything that takes any time. Here's a version of what you're doing with just a pause:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class ProgressDialogExample extends Application {
#Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start");
startButton.setOnAction(e -> {
ProgressForm pForm = new ProgressForm();
// In real life this task would do something useful and return
// some meaningful result:
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws InterruptedException {
for (int i = 0; i < 10; i++) {
updateProgress(i, 10);
Thread.sleep(200);
}
updateProgress(10, 10);
return null ;
}
};
// binds progress of progress bars to progress of task:
pForm.activateProgressBar(task);
// in real life this method would get the result of the task
// and update the UI based on its value:
task.setOnSucceeded(event -> {
pForm.getDialogStage().close();
startButton.setDisable(false);
});
startButton.setDisable(true);
pForm.getDialogStage().show();
Thread thread = new Thread(task);
thread.start();
});
StackPane root = new StackPane(startButton);
Scene scene = new Scene(root, 350, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class ProgressForm {
private final Stage dialogStage;
private final ProgressBar pb = new ProgressBar();
private final ProgressIndicator pin = new ProgressIndicator();
public ProgressForm() {
dialogStage = new Stage();
dialogStage.initStyle(StageStyle.UTILITY);
dialogStage.setResizable(false);
dialogStage.initModality(Modality.APPLICATION_MODAL);
// PROGRESS BAR
final Label label = new Label();
label.setText("alerto");
pb.setProgress(-1F);
pin.setProgress(-1F);
final HBox hb = new HBox();
hb.setSpacing(5);
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(pb, pin);
Scene scene = new Scene(hb);
dialogStage.setScene(scene);
}
public void activateProgressBar(final Task<?> task) {
pb.progressProperty().bind(task.progressProperty());
pin.progressProperty().bind(task.progressProperty());
dialogStage.show();
}
public Stage getDialogStage() {
return dialogStage;
}
}
public static void main(String[] args) {
launch(args);
}
}
You can use controlsfx library to display this easily
private void progressDialogue(){
copyWorker = createWorker();
ProgressDialog dialog = new ProgressDialog(copyWorker);
dialog.initStyle(StageStyle.TRANSPARENT);
dialog.setGraphic(null);
//stage.initStyle(StageStyle.TRANSPARENT);
dialog.initStyle(StageStyle.TRANSPARENT);
//dialog.setContentText("Files are Uploading");
//dialog.setTitle("Files Uploading");
//dialog.setHeaderText("This is demo");
dialog.setHeaderText(null);
dialog.setGraphic(null);
dialog.initStyle(StageStyle.UTILITY);
new Thread(copyWorker).start();
dialog.showAndWait();
}
public Task createWorker() {
return new Task() {
#Override
protected Object call() throws Exception {
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
updateMessage("2000 milliseconds");
updateProgress(i + 1, 10);
}
return true;
}
};
}
now you need to call the method progressDialogue();
the code is from this video : https://www.youtube.com/watch?v=DK_1YGLI9ig

Java "Could Not Serialize the Data"

I'm trying to get my clipboard to receive some custom data in a drag and drop. The custom data is another java type. This other type does implement serializable, so I'm really not sure why this isn't working. Any ideas are appreciated!
imgView.setOnDragDetected(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
ClipboardContent content = new ClipboardContent();
content.put(dataFormat, RHSIconizedToken.this);
Dragboard db = imgView.startDragAndDrop(TransferMode.ANY);
db.setContent(content);
event.consume();
}
});
To retrieve this object later I'm using:
RHSIconizedToken replacementRHSiToken = (RHSIconizedToken) db.getContent(RHSIconizedToken.getDataFormat());
I'm getting the following error, but the RHSIconizedToken does implement Serializable
java.lang.IllegalArgumentException: Could not serialize the data
GetDataFormat returns the DataFormat Object that is used in the put argument in the first code example.
That's because your object is not serializable.
Indeed, it's not because it implements Serializable that it is Serializable.
Look deeper inside the exception, you might find something like this
Caused by: java.io.NotSerializableException: javafx.beans.property.SimpleObjectProperty
Maybe making some fields transient will help.
If your drag object isn't serializable, save it in a global variable during the drag. Here's a JavaFx (Java8 with lambdas) example with draggable tabs that go bewteen panes within the same JVM.
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DraggingTabPane extends Application {
private static final DataFormat TAB_TYPE = new DataFormat("nonserializableObject/tab");
private static Tab dndTab;// global for drag-n-drop of non-serializable type
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TabPane tabPane1 = createDndTabPane();
TabPane tabPane2 = createDndTabPane();
VBox root = new VBox(10);
root.getChildren().addAll(tabPane1, tabPane2);
final Random rng = new Random();
for (int i=1; i<=8; i++) {
final Tab tab = createDraggableTab("Tab "+i);
final StackPane pane = new StackPane();
int red = rng.nextInt(256);
int green = rng.nextInt(256);
int blue = rng.nextInt(256);
String style = String.format("-fx-background-color: rgb(%d, %d, %d);", red, green, blue);
pane.setStyle(style);
final Label label = new Label("This is tab "+i);
label.setStyle(String.format("-fx-text-fill: rgb(%d, %d, %d);", 256-red, 256-green, 256-blue));
pane.getChildren().add(label);
pane.setMinWidth(600);
pane.setMinHeight(250);
tab.setContent(pane);
if (i<=4) {
tabPane1.getTabs().add(tab);
} else {
tabPane2.getTabs().add(tab);
}
}
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
public TabPane createDndTabPane() {
final TabPane tabPane = new TabPane();
tabPane.setOnDragOver(event -> {
if (event.getDragboard().hasContent(TAB_TYPE)
&& dndTab.getTabPane() != tabPane) {// && different from source location
event.acceptTransferModes(TransferMode.MOVE);
event.consume();
}
});
tabPane.setOnDragDropped(event -> {
if (event.getDragboard().hasContent(TAB_TYPE)
&& dndTab.getTabPane() != tabPane) {// && different from source location
dndTab.getTabPane().getTabs().remove(dndTab);
tabPane.getTabs().add(dndTab);
event.setDropCompleted(true);
event.consume();
}
});
return tabPane;
}
private Tab createDraggableTab(String text) {
final Tab tab = new Tab();
final Label label = new Label(text);
tab.setGraphic(label);
label.setOnDragDetected(event -> {
Dragboard dragboard = label.startDragAndDrop(TransferMode.MOVE);
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.put(TAB_TYPE, 1);
dndTab = tab;
dragboard.setContent(clipboardContent);
event.consume();
});
return tab ;
}
}

Resources