I want to create tabs panel with icons similar to the Firefox configuration panel with JavaFX:
Is there any example which I can use to see how to implement this?
Tabs, like many other elements in JavaFX, have a method called setGraphic(Node value), in which you can put any JavaFX node. Example:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class TabPaneTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Tabs");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);
TabPane tabPane = new TabPane();
BorderPane borderPane = new BorderPane();
for (int i = 0; i < 5; i++) {
Tab tab = new Tab();
tab.setGraphic(new Circle(0, 0, 10));
HBox hbox = new HBox();
hbox.getChildren().add(new Label("Tab" + i));
hbox.setAlignment(Pos.CENTER);
tab.setContent(hbox);
tabPane.getTabs().add(tab);
}
// bind to take available space
borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());
borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Result:
I know its an old thread, but i didnt find a direct answer anywhere. So i thought of posting it some that it will be helpfull for some searching for it.
This is what i did to get a tab like firefox preferences screen.
Add the image to the tab with setGraphics and add the following code to the application css file. My image size was 48x48. So i went for height as 70.
.tab-label {
-fx-content-display: top;
}
.tab-pane {
-fx-tab-min-height: 70;
-fx-tab-max-height: 70;
}
How to add image directly from image url:
Tab tab = new Tab();
tab.setGraphic(buildImage("patch/to/image");
// Helper method to create image from image patch
private static ImageView buildImage(String imgPatch) {
Image i = new Image(imgPatch);
ImageView imageView = new ImageView();
//You can set width and height
imageView.setFitHeight(16);
imageView.setFitWidth(16);
imageView.setImage(i);
return imageView;
}
Related
Is it possible with Popup opacity mask top and bottom JavaFX? I have TextField autocomplete with Popup. So the idea is to put an opacity mask.
Below is another way you can give a try, for getting the opacity masked effect. Though it is not exactly the same implementation, I took some ideas from the link you provided :).
I created a small utility where you can pass the Popup instance. The utility builds the mask panes and include to the root node of the Popup.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class PopupOpacityMaskDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
root.setStyle("-fx-background-color:grey;");
root.setOnMouseClicked(e -> {
ListView<String> content = new ListView<>();
content.getItems().addAll(IntStream.range(100, 200).mapToObj(i -> i + "").collect(Collectors.toList()));
content.setPrefSize(200, 250);
Popup popup = new Popup();
popup.setAutoHide(true);
popup.getContent().add(content);
popup.setX(e.getScreenX());
popup.setY(e.getScreenY());
popup.show(root.getScene().getWindow());
MaskUtil.applyMask(popup);
});
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("Demo");
primaryStage.show();
}
static class MaskUtil{
static void applyMask(Popup popup) {
double fadeSize = 70;
Pane pane = (Pane)popup.getScene().getRoot();
// Build the mask panes
Pane topMask = buildMaskPane(pane, fadeSize, false);
Pane bottomMask = buildMaskPane(pane, fadeSize, true);
// Just ensuring to remove any masks (if you are reusing the Popup)
pane.getChildren().removeAll(pane.lookupAll(".mask"));
pane.getChildren().addAll(topMask, bottomMask);
// Update the bottom mask position by listening to height of pane
pane.heightProperty().addListener((obs, old, h) -> bottomMask.setLayoutY(h.doubleValue() - fadeSize));
if (pane.getHeight() > 0) {
bottomMask.setLayoutY(pane.getHeight() - fadeSize);
}
}
private static Pane buildMaskPane(Pane pane, double fadeSize, boolean isBottom) {
Pane mask = new Pane();
mask.setMouseTransparent(true); // Turn this to 'false' if you don't want to interact over mask
mask.setPrefHeight(fadeSize);
mask.prefWidthProperty().bind(pane.widthProperty());
mask.maxHeightProperty().bind(mask.prefHeightProperty());
mask.minHeightProperty().bind(mask.prefHeightProperty());
mask.getStyleClass().add("mask");
mask.setStyle(String.format("-fx-background-color:linear-gradient(to %s, #555555, transparent)", isBottom ? "top" : "bottom"));
return mask;
}
}
}
Is there a way to display an integer on the slider thumb in javafx? Just curious because I am trying to make a clean UI and cannot find anything on displaying an integer on the slider thumb.
One way to address the requirement is by accessing the thumb node and include a Text/Label node. Please check the below demo for what i mean.
You can adjust the thumb padding and the text size for fine tuning.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class SliderTextDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Slider slider = new Slider(1, 10, 3);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMajorTickUnit(1f);
slider.setBlockIncrement(1f);
slider.setSnapToTicks(true);
Text text = new Text();
slider.skinProperty().addListener((obs,old,skin)->{
if(skin!=null){
StackPane thumb = (StackPane)slider.lookup(".thumb");
thumb.setPadding(new Insets(10));
thumb.getChildren().add(text);
}
});
slider.valueProperty().addListener((obs,old,val)->text.setText(val.intValue()+""));
slider.setValue(2);
VBox root = new VBox(slider);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
root.setSpacing(20);
Scene scene = new Scene(root,600,200);
primaryStage.setScene(scene);
primaryStage.setTitle("Slider Text Demo");
primaryStage.show();
}
}
UPDATE:
If you don't want to rely on accessing the skin,you can indeed implement/initialize the Slider as below. That way you can create a custom Slider and can reuse in multiple places.
Slider slider = new Slider(1, 10, 3) {
Text text;
#Override
protected void layoutChildren() {
super.layoutChildren();
if (text == null) {
text = new Text(((int) getValue()) + "");
valueProperty().addListener((obs, old, val) -> text.setText(val.intValue() + ""));
StackPane thumb = (StackPane) lookup(".thumb");
thumb.setPadding(new Insets(10));
thumb.getChildren().add(text);
}
}
};
I want to create a tree-based algorithm visualization in JavaFx, and there are many sub scripts and super scripts in the notations. I want to add these notations to shapes like circles.
I have tried using WebView object for doing that, but it just covers up the entire screen.
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Shape Text");
Group circles = new Group();
Circle circle = new Circle(50, Color.web("white", 0.7));
circle.setCenterX(500.0f);
circle.setCenterY(200.0f);
circle.setStrokeType(StrokeType.OUTSIDE);
circle.setStroke(Color.web("white", 0.16));
circle.setStrokeWidth(4);
circles.getChildren().add(circle);
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
webEngine.loadContent("<h1>B<sub>0</sub></h1>");
StackPane stack = new StackPane();
stack.getChildren().addAll(circles, webView);
Scene scene = new Scene(stack, 1000, 800, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
The above code replaces the entire view with HTML text. I also tried javafx.scene.text.Text class, but it does not support the HTML content.
Thank you in advance!
There are three things you might want to do:
Size the WebView to the HTML content (or the inner display region of the shape).
Make the background of the WebView pages transparent.
Center the HTML content in the WebView, with the WebView centered in the Shape.
Code below demonstrates some of these tricks:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.StrokeType;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import java.lang.reflect.Field;
public class ShapedHTML extends Application {
#Override
public void start(Stage stage) throws Exception{
stage.setTitle("Shape Text");
Group circles = new Group();
Circle circle = new Circle(50, Color.web("white", 0.7));
circle.setCenterX(500.0f);
circle.setCenterY(200.0f);
circle.setStrokeType(StrokeType.OUTSIDE);
circle.setStroke(Color.web("white", 0.16));
circle.setStrokeWidth(4);
circles.getChildren().add(circle);
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
webView.maxWidthProperty().bind(circle.radiusProperty().multiply(2));
webView.maxHeightProperty().bind(circle.radiusProperty().multiply(2));
webEngine.documentProperty().addListener(observable -> {
try {
// Use reflection to retrieve the WebEngine's private 'page' field.
Field f = webEngine.getClass().getDeclaredField("page");
f.setAccessible(true);
com.sun.webkit.WebPage page = (com.sun.webkit.WebPage) f.get(webEngine);
page.setBackgroundColor((new java.awt.Color(0, 0, 0, 0)).getRGB());
} catch (Exception e) {
System.out.println("Difficulty to make WebView background transparent");
e.printStackTrace();
}
});
webEngine.loadContent("<h1 id='root' style='background : rgba(0,0,0,0); margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);'>B<sub>0</sub></h1>");
StackPane stack = new StackPane();
stack.getChildren().addAll(circles, webView);
Scene scene = new Scene(stack, 1000, 800, Color.BLACK);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Note on com.sun class usage
The above code uses com.sun classes (which is usually not recommended as it is not publicly supported API). But, it worked for me (on Java 8) and I don't know a better way to accomplish the transparency of the WebView background.
If you are using later versions of Java (e.g. Java 11+), then you will need to provide some VM arguments to allow usage of the relevant com.sun classes to work. See, for instance, the stack overflow question Cannot access JavaFX class "WebPage" in IntelliJ-IDEA for resolving accessibility issues for com.sun.webkit.WebPage in Java 11+. An answer to that question suggests using the following VM arguments (which I haven't tried):
--add-exports javafx.web/com.sun.webkit=projectname
where the last argument is your module name as declared in the module-info.java of your project.
Try setting the maxSize of the WebView.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class JavaFXTestingGround extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
Circle circle = new Circle(100, Color.web("white", 0.7));
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
webEngine.loadContent("<h1>B<sub>0</sub></h1>");
webView.setMaxSize(50, 50);
StackPane stack = new StackPane();
stack.getChildren().addAll(circle, webView);
Scene scene = new Scene(stack, 1000, 800, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
This is my code
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
public void start(Stage primaryStage) throws Exception {
HBox OuterHBox = new HBox();
Image image1 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView1 = new ImageView(image1);
imageView1.setFitWidth(200);
imageView1.setFitHeight(200);
imageView1.setPreserveRatio(true);
imageView1.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView1.fitHeightProperty().bind(OuterHBox.heightProperty());
Image image2 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView2 = new ImageView(image2);
imageView2.setFitWidth(200);
imageView2.setFitHeight(200);
imageView2.setPreserveRatio(true);
imageView2.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView2.fitHeightProperty().bind(OuterHBox.heightProperty());
OuterHBox.getChildren().addAll(imageView1, imageView2);
Scene scene = new Scene(OuterHBox, 600, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static void loadFontStuff() {
Font.loadFont(Main.class.getResource("TRON.TTF").toExternalForm(), 10);
System.out.println(Main.class.getResource("TRON.TTF"));
}
}
Output
What I want is when I make the window smaller by reducing it's width or reducing it's height, both the images inside should resize and be visible.
When I reduce the height, I see both images resizing to fit the screen.
But when I reduce the width, both images don't resize. Second image disappears out of view.
[
When I reach the first image, it resizes itself.
You can achieve this by setting width to image when outerbox's width get changed.
You can add a width change listener to the outerbox and distribute the updated width to the images equally. You need to remove fitWidthProperty because each image will use complete outerbox width.
Please update your code like this:
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
public void start(Stage primaryStage) throws Exception {
HBox OuterHBox = new HBox();
Image image1 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView1 = new ImageView(image1);
imageView1.setFitWidth(200);
imageView1.setFitHeight(200);
imageView1.setPreserveRatio(true);
// imageView1.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView1.fitHeightProperty().bind(OuterHBox.heightProperty());
Image image2 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView2 = new ImageView(image2);
imageView2.setFitWidth(200);
imageView2.setFitHeight(200);
imageView2.setPreserveRatio(true);
// imageView1.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView2.fitHeightProperty().bind(OuterHBox.heightProperty());
// Updated Code============
OuterHBox.widthProperty().addListener((observable, oldValue, newValue) -> {
imageView1.setFitWidth(newValue.doubleValue() / 2);
imageView2.setFitWidth(newValue.doubleValue() / 2);
});
OuterHBox.getChildren().addAll(imageView1, imageView2);
Scene scene = new Scene(OuterHBox, 600, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static void loadFontStuff() {
Font.loadFont(Main.class.getResource("TRON.TTF").toExternalForm(), 10);
System.out.println(Main.class.getResource("TRON.TTF"));
}
}
The problem with your approach is with the combination of setPreserveRatio(true); and the binding. When you trying to increase the image height, because it has to keep its dimension ratio ( cause of setPreserveRatio(true); ), the ImageView will scale the width as well, forcing the HBox to increase its size and when it finally reaches more than the actual stage width it will make a part of the images to be hidden. You could set the setPreserveRatio to false unfortunately in that case the first Image will always try to get all the available space of the HBox and you will not be able to see the second Image.
In my opinion, I believe it's for the best to manually set the fitWidth and fitHeight of each ImageView by calculating the available space inside your pane. Here is an example
import java.util.ArrayList;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
private ArrayList<ImageView> allImages = null;
public void start(Stage primaryStage) {
HBox mainPane = new HBox();
try {
allImages = createImages(10, "icon.png");
} catch (Exception e1) {
e1.printStackTrace();
}
if (allImages == null || allImages.isEmpty()) {
Platform.exit();
}
mainPane.getChildren().addAll(allImages);
mainPane.widthProperty().addListener(e -> {
double fitWidth = mainPane.widthProperty().get() / allImages.size();
for (ImageView iv : allImages) {
iv.setFitWidth(fitWidth);
}
});
mainPane.heightProperty().addListener(e -> {
double fitHeight = mainPane.heightProperty().get();
for (ImageView iv : allImages) {
iv.setFitHeight(fitHeight);
}
});
Scene scene = new Scene(mainPane, 600, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
private ArrayList<ImageView> createImages(int count, String string) throws Exception {
ArrayList<ImageView> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
Image image = new Image(getClass().getResource("icon.png").toURI().toURL().toString());
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
list.add(imageView);
}
return list;
}
public static void main(String[] args) {
launch(args);
}
}
I am loading ten Images and display them in single row. Each time the user resize the Stage it will trigger an event which mainPane (your HBox) handles. Then finds out how many Images there are and how much space we have to fill, all you have to do then is to set the appropriate size for each ImageView and your are done. The Example above is working fine if you set the setPreserveRatio to false too.
I am trying to add close option in tabpane like a browser have it. Could you please tell me how to add closing feature in this tabpane? I tried this line but didn't solve my problem tabPane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
I am using javafx jfoenix library for UI.
package tabsDemo;
import java.awt.Color;
import java.awt.Dimension;
import java.math.BigInteger;
import java.security.SecureRandom;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTabPane;
import com.jfoenix.controls.JFXTextField;
public class TabsDemo extends Application {
private String msg = "Tab 0";
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Tabs");
Group root = new Group();
Scene scene = new Scene(root, 800, 600);
JFXButton b1 = new JFXButton();
b1.setId("back");
b1.setGraphic(new ImageView(new Image("/tabsDemo/back.png")));
b1.setMinWidth(20);
b1.setMinHeight(20);
b1.setMaxWidth(20);
b1.setMaxHeight(20);
JFXButton b2 = new JFXButton();
b2.setId("farword");
b2.setGraphic(new ImageView(new Image("/tabsDemo/forward.png")));
b2.setMinWidth(20);
b2.setMinHeight(20);
b2.setMaxWidth(20);
b2.setMaxHeight(20);
JFXButton b3 = new JFXButton();
b3.setId("refresh");
b3.setGraphic(new ImageView(new Image("/tabsDemo/refresh.png")));
b3.setMinWidth(20);
b3.setMinHeight(20);
b3.setMaxWidth(20);
b3.setMaxHeight(20);
JFXTextField t1 = new JFXTextField();
t1.setMinWidth(100);
// t1.setPrefWidth(900);
// t1.setMaxWidth(1000);
t1.setMinHeight(30);
t1.setMaxHeight(30);
GridPane gridPane = new GridPane();
gridPane.setHgap(4);
gridPane.add(b1, 0, 0);
gridPane.add(b2, 1, 0);
gridPane.add(b3, 2, 0);
gridPane.add(t1, 3, 0);
gridPane.setHgrow(t1, Priority.ALWAYS);
BorderPane borderPane = new BorderPane();
borderPane.setTop(gridPane);
JFXTabPane tabPane = new JFXTabPane();
Tab tab1 = new Tab();
tab1.setText("Tab1");
tab1.setContent(borderPane);
VBox vbox = new VBox();
vbox.getChildren().addAll(new JFXButton("B1"), new JFXButton("B2"), new JFXButton("B3"), new JFXButton("B4"));
Tab tab2 = new Tab();
tab2.setText("Tab2");
tab2.setContent(vbox);
tabPane.getTabs().addAll(tab1, tab2);
tabPane.setPrefSize(800, 600);
// I add here the closing option for tab but it's not working
tabPane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
SingleSelectionModel<Tab> selectionModel = tabPane.getSelectionModel();
selectionModel.select(1);
JFXButton button = new JFXButton("Add New Tab");
button.setOnMouseClicked((o) -> {
Tab temp = new Tab();
int count = tabPane.getTabs().size();
temp.setText(msg + count);
temp.setContent(new Label("Tab 0" + count));
tabPane.getTabs().add(temp);
});
borderPane.setRight(button);
tabPane.setMaxSize(800, 600);
/*
* HBox hbox = new HBox(); hbox.getChildren().addAll(button, tabPane);
* hbox.setSpacing(50); hbox.setAlignment(Pos.CENTER);
* hbox.setStyle("-fx-padding:20");
*/
BorderPane rootBorderpane = new BorderPane();
rootBorderpane.setCenter(tabPane);
root.getChildren().addAll(rootBorderpane);
scene.getStylesheets().add(TabsDemo.class.getResource("jfoenix-components.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setTitle("JFX Tabs Demo");
}
private SecureRandom random = new SecureRandom();
public String nextSessionId() {
return new BigInteger(50, random).toString(16);
}
}
Thank you!
Your JFXTabPane is a custom component from the JFoenix library, so there's no guarantee that all standard JavaFX settings, such as TabClosingPolicy in your case, are fully implemented.
I checked their GitHub repository and looks like there's an unresolved issue regarding this missing feature. Someone forked the repository, apparently resolved the problem and submitted a pull request, but it's still has an open status. In other words, the changes are not included in the current build, so the bug still exists.
My suggestion is to stick with the standard JavaFX components, which should provide everything you need to create simple applications, including the closeable tabs, which are are working well as presented in the following example.
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("JFX Tabs Demo");
TabPane tabPane = new TabPane();
tabPane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
tabPane.getTabs().add(new Tab("Test"));
Scene scene = new Scene(tabPane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}