Is WebView "embeddable" in TextFlow? - java

It says on the OpenJDK samples for TextFlow that it can have arbitrary Nodes as children, e.g. Button. I was wondering if WebView can be a possible child? If so, then I must be doing something wrong. When I add it to the TextFlow, the WebView just takes up space, but displays nothing. The content I've tried range from simple HTML pages, up to videos from services like YouTube.
Since this control is fairly new, I was unable to find an existing use case online.
In case you're wondering why I want this. I have a ListView that displays rich content. If TextFlow is capable of displaying any Node, then it would save me from creating multiple extension classes for the different types I want to display on the ListView (e.g. Video, formatted text, HTML - these I already did, and they work fine), and I can manage content easily by just managing the TextFlow's children.
Thanks.

Yes, you can. Note that the web view will be layout out using its baselineOffset, which is not going to be appropriate for use in a TextFlow (I think what happens is it's bottom edge will be aligned with the bottom edge of the text, and it gets clipped to the height of the text row.) You can get around this by wrapping it in a Group:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class WebViewInTextFlow extends Application {
#Override
public void start(Stage primaryStage) {
TextFlow flow = new TextFlow();
WebView webView = new WebView();
webView.getEngine().getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> System.out.println(newState));
webView.getEngine().load("http://stackoverflow.com/questions/31353921/is-webview-embeddable-in-textflow/31357609#31357609");
flow.getChildren().addAll(new Text("Some text"), new Group(webView), new Text("Some more text"));
primaryStage.setScene(new Scene(flow, 800, 800));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

JavaFX TabPane does not forward click events when tab reordering is enabled [duplicate]

In this minimal example we can drag the Tabs to reorder them:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
Scene scene = new Scene(tabPane);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
We can start a drag when the mouse is over the tabs name, but not when it is over the button (the tabs graphic). The button in this case is blocking the drag event from being seen by the tab.
I cannot simply set isMouseTransparant as that prevents the button from registering its own mouse clicks.
How can I allow the user to drag a tab through the tabs graphic while still being able to interact with the graphic via clicking?
Example mode based solution
I suggest implementing a reorder mode.
When the reorder mode is switched on, the nodes in the graphics of the tab headers are all disabled and the user can click anywhere in the tab headers to drag and reorder the associated tabs. During this time, the user cannot interact with the disabled graphic nodes.
When the reorder mode is switched off, the nodes in the graphics of the tab headers are all enabled. The user cannot drag the tabs to reorder them, but they can interact with the nodes in the graphics to perform their functions.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.Objects;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
ToggleButton reorderModeToggle = new ToggleButton("Reorder Mode");
reorderModeToggle.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
tabPane.getTabs().stream()
.map(Tab::getGraphic)
.filter(Objects::nonNull)
.forEach(graphic -> graphic.setDisable(isSelected));
tabPane.setTabDragPolicy(
isSelected
? TabPane.TabDragPolicy.REORDER
: TabPane.TabDragPolicy.FIXED
);
});
VBox layout = new VBox(10, reorderModeToggle, tabPane);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If new tabs can be added dynamically by the user, then you could additionally appropriately initialize their disable state based on the reorder mode (or you could do that in a list change listener for the tabs in the TabPane). I didn't provide logic for this additional optional extra step.
Discussion of why this approach is provided
I realize that is not what you originally asked for, but it might be a reasonable solution to your problem.
Trying to make it so that the graphics control work and the drag functions of the tab are also available at the same time to appear (to me) to be quite problematic, both in terms of how to implement this and the behavior that the user should expect to occur. For example, a button will be fired when the mouse is pressed, but a drag event for a tab header will also begin when a mouse is pressed, so, if you press a button in the header, should you also begin the drag event? It is quite unclear.
The input handling for buttons is handled by an InputMap class inside a ButtonBehavior reference by the ButtonSkin. Both of which are non-public APIs, so trying to override the default behavior is difficult.
There are methods in the private InputMap class to help decide whether the behavior of the button should consume the event it takes actions on (a mouse press or key press) or not (by default it will). So this could be done by installing your own skin replicating (a lot) of the functionality in the private API with some modifications to provide the custom functionality you want (but I do not recommend this at all).
I had thought that using event filters you would be able to provide the original functionality you requested, and you may be able to do that, but from my investigations, that would not be at all easy.
Alternate Discussion
The alternate solution suggested by sorifiend in comments might also work for you:
... option is to not use a button. Try with a label instead (Labels don't capture any events by default), or you could use custom graphics to render the tab header to look however you want, and then it will behave exactly as you want without a button.
Alternately, instead of putting interactive graphics in the headers, you could place all your interactive nodes body of each tab, keeping only text or static images in the headers, allowing them to always be re-orderable.

How to drag a Tab through its graphic?

In this minimal example we can drag the Tabs to reorder them:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
Scene scene = new Scene(tabPane);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
We can start a drag when the mouse is over the tabs name, but not when it is over the button (the tabs graphic). The button in this case is blocking the drag event from being seen by the tab.
I cannot simply set isMouseTransparant as that prevents the button from registering its own mouse clicks.
How can I allow the user to drag a tab through the tabs graphic while still being able to interact with the graphic via clicking?
Example mode based solution
I suggest implementing a reorder mode.
When the reorder mode is switched on, the nodes in the graphics of the tab headers are all disabled and the user can click anywhere in the tab headers to drag and reorder the associated tabs. During this time, the user cannot interact with the disabled graphic nodes.
When the reorder mode is switched off, the nodes in the graphics of the tab headers are all enabled. The user cannot drag the tabs to reorder them, but they can interact with the nodes in the graphics to perform their functions.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.Objects;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
ToggleButton reorderModeToggle = new ToggleButton("Reorder Mode");
reorderModeToggle.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
tabPane.getTabs().stream()
.map(Tab::getGraphic)
.filter(Objects::nonNull)
.forEach(graphic -> graphic.setDisable(isSelected));
tabPane.setTabDragPolicy(
isSelected
? TabPane.TabDragPolicy.REORDER
: TabPane.TabDragPolicy.FIXED
);
});
VBox layout = new VBox(10, reorderModeToggle, tabPane);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If new tabs can be added dynamically by the user, then you could additionally appropriately initialize their disable state based on the reorder mode (or you could do that in a list change listener for the tabs in the TabPane). I didn't provide logic for this additional optional extra step.
Discussion of why this approach is provided
I realize that is not what you originally asked for, but it might be a reasonable solution to your problem.
Trying to make it so that the graphics control work and the drag functions of the tab are also available at the same time to appear (to me) to be quite problematic, both in terms of how to implement this and the behavior that the user should expect to occur. For example, a button will be fired when the mouse is pressed, but a drag event for a tab header will also begin when a mouse is pressed, so, if you press a button in the header, should you also begin the drag event? It is quite unclear.
The input handling for buttons is handled by an InputMap class inside a ButtonBehavior reference by the ButtonSkin. Both of which are non-public APIs, so trying to override the default behavior is difficult.
There are methods in the private InputMap class to help decide whether the behavior of the button should consume the event it takes actions on (a mouse press or key press) or not (by default it will). So this could be done by installing your own skin replicating (a lot) of the functionality in the private API with some modifications to provide the custom functionality you want (but I do not recommend this at all).
I had thought that using event filters you would be able to provide the original functionality you requested, and you may be able to do that, but from my investigations, that would not be at all easy.
Alternate Discussion
The alternate solution suggested by sorifiend in comments might also work for you:
... option is to not use a button. Try with a label instead (Labels don't capture any events by default), or you could use custom graphics to render the tab header to look however you want, and then it will behave exactly as you want without a button.
Alternately, instead of putting interactive graphics in the headers, you could place all your interactive nodes body of each tab, keeping only text or static images in the headers, allowing them to always be re-orderable.

Why JavaFX application and scene builder is showing garbled text?

Here's how my scene builder looks like:
and here's the GUI:
The standalone scene builder:
I just run the following source code from Java SDK demos:
package sample;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Button btn = new Button();
btn.setText("Say 'Hello World'!");
StackPane root_ctn = new StackPane();
root_ctn.getChildren().add(btn);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
System.out.println("Hello World!");
}
});
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root_ctn, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The only place the text looks good is in the console.
View idea.log
I did not yet find out the solution, but I found an interesting pattern:
On your Gluon Scene Builder screenshot, there is written Pgy Rtqlgev, where it should be New Project, and Qrgp Rtqlgev where it should be Open Project. Note that every letter is substituted by the after next letter in the alphabet!
The same applies to Say 'Hello World'!, which is "translated" to Lc{ 'Jgrrq Yqtrf'!. Note that the letter y is replaced by a {, which comes two positions after y in the ASCII table. Interestingly, the characters ' and ! stay the same..
The space each letter takes is still the space of the correct letter, as you can see in the following graphic with the correct text on the green background:
Update:
Is it possible that the font "Segoe UI" is missing or flawed on your system? Can you use that font for example in Word?
Update: I found two other questions of users facing the same problem. In both cases the problem seems to be related to the Segoe UI font:
Scene Builder Editor displaying weird characters
JavaFX Scene builder shows wired characters
I have also encountered this problem and after reading many forums I think I have a possible explanation and solution. The problem seems to be related to Mac users and Segoe UI;
I am guessing that because the font is used in Microsoft products, Macs are unable to render the font, even downloaded versions have not worked.
The simplest fix, which has worked for me so far, is to include
style="-fx-font-family: serif"
in the root node or add it in the controller or add
.root{
-fx-font-family: serif
}
to your CSS. This works for any font in your system.
Installing Segoe UI was a huge red herring for me. Instead, I changed the version of javafx defined in build.gradle to 17.0.1 and upgraded JavaFX to 16

Pane Shape Modification

Ok so to make a long story short I'm trying to create a sort of chat/message system and need a small bit of assistance. I'm trying to create an arrow on my container as the one shown in the image below. The image is taken off of ControlsFX and from their PopOver window. I CANNOT use their popover widget because it behaves a little wonky with what I'm using it for.
I went ahead and created my own little chat window popup that positions itself over the parent object that I define but I would really like it to have an arrow pointing to the object. The arrow will also ALWAYS face down and should be in the lower left-hand corner of the popup.
It should also be noted that the popup is NOT a window its a simple VBox that populates with lines of text. I could of course wrap that in a Pane if its needed. Can anyone come up with the proper way to create this arrow? I have my VBox background as a gradient as well so the arrow can't just be like plopped on the bottom through getChildren().add with the "same color" because then the gradient would be off. It has to be (somehow) a part of the container.
===========================================================================
EDIT:
Alright so I spent the greater part of today learning SVG Pathing which isn't too awful complicated but it's slightly tedious. The path I ended up going with is:
"M30 0 h100 a6,6 0 0,1 6,6 v50 a6,6 0 0,1 -6,6 h-88 L38 68 L34 62 h-4 a6,6 0
0,1 -6,-6 v-50 a6,6 0 0,1 6,-6 z"
Now the only problem with this is the arrow tail height grows with the size of the pane. For instance if I have a LOT of text in the box the pane grows in height (of course) and the arrow gets longer too. This behavior isn't a total deal breaker but it's not really what I had intended. I expected the capital Ls in the pathing to make sure that the points of the arrow stay put no matter what but it didn't work. Any thoughts on this?
There are a number of ways to achieve the effect which you wish.
One way is to use a CSS -fx-shape attribute specification on a region. See the JavaFX CSS reference for info on this setting and how to use it.
The sample below uses inline styles, but for a more maintainable and substantial application use an external CSS stylesheet.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Bubble extends Application {
private static final String SQUARE_BUBBLE =
"M24 1h-24v16.981h4v5.019l7-5.019h13z";
// source for svg path string: https://iconmonstr.com/speech-bubble-7/
private static final String ROUND_BUBBLE =
"M12 1c-6.628 0-12 4.573-12 10.213 0 2.39.932 4.591 2.427 6.164l-2.427 5.623 7.563-2.26c9.495 2.598 16.437-3.251 16.437-9.527 0-5.64-5.372-10.213-12-10.213z";
#Override
public void start(Stage stage) {
Label label = new Label("hello, world");
label.setStyle("-fx-font-size: 16px;");
StackPane bubble = new StackPane(label);
bubble.setPadding(new Insets(20));
bubble.setStyle(
"-fx-background-color: lightblue; " +
"-fx-border-color: navy; -fx-border-width: 2px; " +
"-fx-shape: \"" + ROUND_BUBBLE + "\";"
);
bubble.setEffect(new DropShadow(10, 5, 5, Color.MIDNIGHTBLUE));
StackPane layout = new StackPane(bubble);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I realize that the bubble shape demoed in this application isn't exactly the shape which you want but I don't understand from your description what the shape you want is. There are numerous bubble shapes already created at iconmonstr.com, you can just use its interface to search for speech bubbles and select the shape you want, or you can define your own svg path for your custom shape if you have the tools (e.g. Inkscape) and skill for that. To extract the svg path from an existing svg file, open up the svg file, hope that it is encoded in a compact path string format, and if it is, just copy and paste the path part from the svg into your JavaFX css file.
Another way to do this would be to programmatically construct a path which you would layer with the content by placing both in a StackPane.

Set background image the same size as the window/screen in Java app

I would like to set a background image the same size as my window/screen.
I would prefer to do this in my CSS file, but I have NOT FOUND a way to accomplish this.
Must I do this in a javafx class file?
Thanks for every help ;)
You will have to determine the screen size in java code as demonstrated in JavaFX window sizing, there is no way to determine it in CSS.
For an Image, in your java code you can use something as
ImageView imageView = new ImageView(image);
imageView.setFitWidth(Screen.getPrimary().getVisualBounds().getWidth());
imageView.setFitHeight(Screen.getPrimary().getVisualBounds().getHeight());
If you want to set the background image to a scene then:
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.*;
public class ScreenSizeImage extends Application {
#Override public void start(final Stage stage) {
// uncomment if you want the stage full screen.
//stage.setFullScreen(true);
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
stage.setX(bounds.getMinX());
stage.setY(bounds.getMinY());
stage.setWidth(bounds.getWidth());
stage.setHeight(bounds.getHeight());
StackPane root = new StackPane();
root.setStyle(
"-fx-background-image: url(" +
"'http://icons.iconarchive.com/icons/iconka/meow/256/cat-box-icon.png'" +
"); " +
"-fx-background-size: cover;"
);
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Of course, rather than the inline setStyle call you are best off using a separate CSS stylesheet like below:
.root{
-fx-background-image: url("background_image.jpg");
-fx-background-size: cover;
}
Good question...
Firstly, I think you mean "the size of your app window" in your question. Obviously, you can't have a background image that is larger than your screen if you app window is smaller.
Secondly, here's a snippet from the JavaFX CSS Reference Guide:
http://download.java.net/jdk9/jfxdocs/javafx/scene/doc-files/cssref.html
"...the background and border mechanisms are patterned after the CSS 3 draft backgrounds and borders module. See [4] for a detailed description."
...
[4] CSS Backgrounds and Borders Module Level 3: http://www.w3.org/TR/css3-background/
You can use the JavaFX vendor specific CSS property -fx-background-size with the stretch value as long as your aspect ratio is correct/your dimensions are proportional.
You might also get it to work by trying a combination of auto for one dimension and 100% for another... and some combination of contain... depending on if you can live with a cropped image.
-fx-background-size<bg-size> [ , <bg-size> ]*
<bg-size> = [ <size> | auto ]{1,2} | cover | contain | stretch
A series of values separated by commas. Each bg-size item in the series applies to the corresponding image in the background-image series.
You can get more detail at http://www.w3.org/TR/css3-background/#the-background-image

Categories