How to hide TabPane content on Tab clicked in JavaFX - java

Here is a code:
package tabpane;
import javafx.application.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
public class HideShowTabContentOnClicked extends Application {
public static void main(String[] args) {
launch(args);
}
private BorderPane createContent() {
BorderPane borderPane = new BorderPane();
TabPane tabPane = new TabPane();
tabPane.setSide(Side.LEFT);
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-background-color: lightblue");
stackPane.setMinWidth(200);
Tab firstTab = new Tab("First");
firstTab.setClosable(false);
firstTab.setContent(stackPane);
firstTab.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
firstTab.setContent(null);
} else {
firstTab.setContent(stackPane);
}
});
Tab secondTab = new Tab("Second");
StackPane stackPane2 = new StackPane();
stackPane2.setStyle("-fx-background-color: yellow");
secondTab.setContent(stackPane2);
secondTab.setClosable(false);
secondTab.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
secondTab.setContent(null);
} else {
secondTab.setContent(stackPane2);
}
});
StackPane center = new StackPane();
center.setStyle("-fx-background-color: cyan");
borderPane.setCenter(center);
tabPane.getTabs().addAll(firstTab, secondTab);
borderPane.setLeft(tabPane);
return borderPane;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setMaximized(true);
stage.show();
}
}
Here I tried to solve a problem by using selectedProperty() by setting content to null, but it doesn't working, I want to make Tab like toggle button so that when I click on it showed and hide TabPanes content.
Before
And when clicked
As an example I want to implement TabPane like Intellij IDEA Tool Buttons (like "Project", "Structure" Tool Buttons etc).

If you are going to keep your content into StackPane, you can bind stackPane.visibleProperty() with toggleButton.selectedProperty():
stackPane.visibleProperty()
.bind(Bindings.when(toggleButton.selectedProperty())
.then(false)
.otherwise(true)
);
in this exampl: toggleButton.isSelected() --> !stackPane.isVisible() and !toggleButton.isSelected() --> stackPane.isVisible(),
or listen ToggleButton's events:
// toggleButton.setOnAction(e ->{ //new .setOnAction() -> Override previous
toggleButton.addEventHandler(ActionEvent.ACTION, e ->{ //can add any quantity for your needs
if(toggleButton.isSelected())
stackPane.setVisible(false);
else stackPane.setVisible(true);
});
But the problem is instead of toggle button I want to use Tab, so that it behaves like toggle button. i.e. when click "First Tab" in my example code if content visible it should be invisible and vice versa. I mean only tabs should be shown
I found solution.Tab does not have click-handler... but
Tab tab = new Tab();
tab.setContent(stackPane);
Label lable = new Label("Label"); //create Label
tab.setGraphic(lable); //set Lable as Graphic to Tab
lable.setOnMouseClicked(event ->{ //setOnMouseClicked, for example
if(stackPane.isVisible()){
stackPane.setVisible(false);
}else{
stackPane.setVisible(true);
}
});
, you can use Label(for example) as Tab-text and add setOnMouseClicked()-handler to Label. You can use any Node with Handler/ActionListener -> It's up to you.
For example, you can use CheckBox to show/hide StackPane, and Tab text (you can combine FXML and Java-code to produce graphics):
Tab tab = new Tab("Tab2"); //Tab with Text
tab.setContent(stackPane);
CheckBox checkBox = new CheckBox(); //create CheckBox
tab.setGraphic(checkBox); //set CheckBox as Graphic to Tab
stackPane.visibleProperty()
.bind(Bindings.when(checkBox.selectedProperty())
.then(false)
.otherwise(true)
);
or
#FXML
private Tab tab;
// ...
tab.setGraphic(checkBox);
// ...

I have came up with this solution:
AtomicReference<Tab> currentTab = new AtomicReference<>(tabPane.getSelectionModel().getSelectedItem());
AtomicReference<Tab> lastTab = new AtomicReference<>(null);
tabPane.setOnMouseReleased(event -> {
// Check if current node is actually tab
Node n = event.getPickResult().getIntersectedNode();
while (n != null && !(n.getStyleClass().contains("headers-region"))) {
n = n.getParent();
}
if (n == null)
return;
lastTab.set(currentTab.get());
currentTab.set(tabPane.getSelectionModel().getSelectedItem());
if (currentTab.get() == lastTab.get()) {
// Hide
tabPane.setPrefSize(28, 28);
//tabPane.getSelectionModel().clearSelection(); // notify selection model
currentTab.set(null);
} else {
// Show
tabPane.setPrefSize(-1,-1);
currentTab.set(tabPane.getSelectionModel().getSelectedItem());
}
});
First of all, I have added mouse event to the tabPane. Inside this mouse event, check if node under cursor is actually Tab node. If it is, do some logic to identify what user is trying to do: hide or show. Hiding is a bit tricky, so I ended up with setting preferred size of TabPane to 28 px wide.
I have also tried to notify selection model with an empty newValue:
tabPane.getSelectionModel().clearSelection();
But this it is not working properly. Calling select(-1) should call clearSelection(), but behavior is different somehow.
When I select another tab after calling clearSelection(), selection model handler called with oldValue == null, that possibly does not update internal index and tab does not swithes to selected one.

Related

JavaFX: Why do I have to press the button several times before the background changes?

In my programm I want to switch dark mode on and off via a MenuItem. And it's is working, but I've a little problem and I don't understand what causes this problem.
The problem is, that I've to select the CheckMenuItem serveral times before the selectedProperty is doing something.
If you run my mvce, you should see it (I think you have to click it 3 times).
But after the selectedProperty was active for the first time, it works without any problems until you restart the application.
MVCE
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.MenuButton;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class DarkModeMVCE extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane pane = new BorderPane();
Scene scene = new Scene(pane, 500, 500);
MenuButton menuButton = new MenuButton("Menu");
CheckMenuItem checkMenuItem = new CheckMenuItem("Darkmode");
checkMenuItem.setSelected(false);
checkMenuItem.setOnAction(aE -> checkMenuItem.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
if (isSelected) {
pane.setStyle("-fx-background-color: black");
}
else {
pane.setStyle("-fx-background-color: white");
}
}));
menuButton.getItems().add(checkMenuItem);
pane.setCenter(menuButton);
primaryStage.setScene(scene);
primaryStage.setTitle("MVCE");
primaryStage.show();
}
}
Can anyone please explain why this is happening?
Is this a bug or an error in the code?
setOnAction() listener listen to any action applies on the MenuItem so when you click the menuItem it execute the code inside the listener
which at this point assign a new listener to the property of selection on the menuItem which gonna listen if the menuItem is gonna be selected or not next time any action is applied so the first action is not counted the next you do is deselect the menuItem whchi in your case do nothing and re-select it again make the desired action so no need to make two listener to the same property you can just make a listener to the change of the selection property
checkMenuItem.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
if (isSelected) {
pane.setStyle("-fx-background-color: black");
}
else {
pane.setStyle("-fx-background-color: white");
}
});
This is the simple way to achieve this :-)
checkMenuItem.setOnAction(event -> {
if (checkMenuItem.isSelected()) {
pane.setStyle("-fx-background-color: black");
} else {
pane.setStyle("-fx-background-color: white");
}
});

JavaFX mouse drag events not firing

I tried almost everything, but the mouse drag events are not firing, like explained here:
https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/input/MouseDragEvent.html
Here is a minimal example, so you can try it out (I am using Java 11 with JavaFX 11.0.2):
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class Main extends Application {
private double mouseClickPositionX, mouseClickPositionY, currentRelativePositionX, currentRelativePositionY;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Hello World");
BorderPane mainBorderPane = new BorderPane();
BorderPane centerBorderPane = new BorderPane();
FlowPane flowPane = new FlowPane();
GridPane gridPane = new GridPane();
Button button1 = new Button("button1");
gridPane.add(button1, 0, 0);
flowPane.getChildren().add(gridPane);
centerBorderPane.setCenter(flowPane);
HBox hbox = new HBox();
TilePane tilePane = new TilePane();
Button button2 = new Button("button2");
tilePane.getChildren().add(button2);
hbox.getChildren().add(tilePane);
mainBorderPane.setCenter(centerBorderPane);
centerBorderPane.setBottom(hbox);
// button2 event handlers
button2.setOnMousePressed(event -> {
mouseClickPositionX = event.getSceneX();
mouseClickPositionY = event.getSceneY();
currentRelativePositionX = button2.getTranslateX();
currentRelativePositionY = button2.getTranslateY();
button2.setMouseTransparent(true);
});
button2.setOnMouseDragged(event -> {
button2.setTranslateX(currentRelativePositionX + (event.getSceneX() - mouseClickPositionX));
button2.setTranslateY(currentRelativePositionY + (event.getSceneY() - mouseClickPositionY));
});
button2.setOnDragDetected(event -> {
button2.startFullDrag();
});
button2.setOnMouseReleased((event) -> {
button2.setMouseTransparent(false);
});
// button1 event handlers
button1.setOnMouseDragReleased((event) -> {
System.out.println("it works in button1");
});
// gridPane event handlers
gridPane.setOnMouseDragReleased((event) -> {
System.out.println("it works in gridPane");
});
primaryStage.setScene(new Scene(mainBorderPane, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I want to get the reference of button2 either in button1 or in gridPane via setOnMouseDragReleased. There are many nested panes etc. because I wanted to maintain the original project layout structure. I did this because I am not sure if this also can be a reason for the non functioning.
Thanks in advance.
I've ended up manually triggering the events from centerBorderPane to gridPane, using node.fireEvent(event). Also implemented a helper function, which returns the right child node:
private Optional<Node> findNode(Pane pane, double x, double y) {
return pane.getChildren().stream().filter(n -> {
Point2D point = n.sceneToLocal(x, y);
return n.contains(point.getX(), point.getY());
}).findAny();
}
Don't forget to consume the events, so you won't get into an infinite loop.
MOUSE_DRAG_RELEASED fires when a drag ends on this node. For example
centerBorderPane.setOnMouseDragReleased((event) -> {
System.out.println("centerBorderPane drag released");
});
should fire when you drag button2 and the drag ends on centerBorderPane.
To fire an event when the mouse is dragged over button1 use button1.setOnMouseDragged
If you want to propagate a mouse event from parent to its children see this

JavaFX - Creating non-selectable item in ListView

I'm using JFXListView and JFXListCell from the library called jfoenix and the purpose and function are same as the regular ListView.
The list contains some Label, Button and AnchorPane. At the very top and bottom of the list, I want to add non-selectable item. The item should not be selectable on mouse click, should not be able to focus and should not be able to scroll.
I though of using updateItem() function and setting the item disable:
#FXML
JFXListView listView;
ObservableList<AnchorPane> list = FXCollections.observableArrayList();
private void initializeListView(){
AnchorPane headerBottomPane = new AnchorPane();
headerBottomPane.setId("headerBottomPane");
....//some property of AnchorPane
list.add(headerBottomPane); //Add header AnchorPane
while(true){
AnchorPane listContainer = new AnchorPane();
Label title = new Label();
Label subtitle = new Label();
Button button = new Button();
Button button2 = new Button();
//Some code here...
listContainer.getChildren().addAll(label, subtitle, button, button2);
list.add(listContainer);
//some code here...
}
list.add(headerBottomPane); //Add bottom AnchorPane
listView.setCellFactory(new CallBack<JFXListView, JFXListCell>(){
#Override
public JFXListCell call(JFXListView param){
return new JFXListCell<AnchorPane>(){
#Override
protected void updateItem(AnchorPane anchorPane, boolean empty){
super.updateItem(anchorPane, empty);
if(anchorPane != null){
if(anchorPane.getId.equals("headerBottomPane")){
setDisable(true);
}
setItem(anchorPane);
}else{
setItem(null);
}
}
};
}
});
}
I am able to disable the top and last item of the list, the item is no longer able to select using mouseClick.
But the problem is, it is focusable when I use the Keyboard arrow up and arrow down another strange thing is when I use the mouse wheel to scroll the list, some of the item are becoming non-selectable too.
I would think of just using a VBox, and putting your top unselectable item first, then the ListView with all the selectable items, then the bottom unselectable item...
I think you have to put your listView.setCellFactory() function at the top of the code where you adding those items, try to initialize it before you add the item.
and inside your updateItem() try to use setMouseTransparent() and setFocusTravesable().
#Override
protected void updateItem(AnchorPane anchorPane, boolean empty){
super.updateItem(anchorPane, empty);
if(anchorPane != null){
if(anchorPane.getId.equals("headerBottomPane")){
setItem(anchorPane); //moved at the top
setMouseTransparent(true); //added this line
setFocusTraversable(false); //added this line
setDisable(true);
}else{
setItem(null);
}
}
I haven't test it but I hope it work.

how to actually hide Tab from TabPane with JavaFX

previously I was working on Java Swing and now I'm trying to work with JavaFX. My Java Swing code last time:
//These line of code is to call method that declared in ContentPage.java
contentPage.adminFeatureEnabled(adminEnabled);
contentPage.managerFeatureEnabled(managerEnabled);
and in my ContentPage.java
//By default, all feature (or tab) are enabled.
//This method is to remove register account if the user login into the system is manager and staff
public void adminFeatureEnabled(boolean a) {
if (!a) {
tabPane.removeTabAt(tabPane.indexOfComponent(registerAccount));
}
}
//This method is to remove register account and purchase order if the user who log into the system is staff
public void managerFeatureEnabled(boolean a) {
if(!a) {
tabPane.removeTabAt(tabPane.indexOfComponent(purchaseOrder));
}
}
and in my code:
if (role.equals("admin")){
contentPage.contentFrame.setTitle("Menu - Admin!");
contentPage.disUser.setEditable(true);
contentPage.chgRoles.setEnabled(true);
} else if(role.equals("manager")){
contentPage.contentFrame.setTitle("Menu - Manager!");
contentPage.chgRoles.setSelectedItem("manager");
adminEnabled = false;
}else if (role.equals("staff")){
contentPage.contentFrame.setTitle("Menu - Staff!");
contentPage.chgRoles.setSelectedItem("staff");
adminEnabled = false;
managerEnabled = false;
}
The code above will perform like this:
when the user login with admin account, all the feature (Tab) enabled
when the user login as manager, some feature (tab) will be hide
My current problem now:
I wanted the same feature as above in JavaFX but I don't know how as none of the method works as I wanted.
anyone can help me with this?
Simply modify the tabs list:
The following example adds/removes Tabs, when the CheckBoxes are (un)selected.
#Override
public void start(Stage primaryStage) {
Tab tab1 = new Tab("Tab 1", new Label("1"));
Tab tab2 = new Tab("Tab 2", new Label("2"));
TabPane tabPane = new TabPane();
tabPane.setPrefSize(400, 400);
CheckBox cb1 = new CheckBox("1");
CheckBox cb2 = new CheckBox("2");
cb1.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
tabPane.getTabs().add(0, tab1);
} else {
tabPane.getTabs().remove(tab1);
}
});
cb2.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
tabPane.getTabs().add(tab2);
} else {
tabPane.getTabs().remove(tab2);
}
});
Scene scene = new Scene(new VBox(new HBox(cb1, cb2), tabPane));
primaryStage.setScene(scene);
primaryStage.show();
}
It's been so long since the question asked, but this maybe helpful for someone.
You can try something like this.
you have a tabPane with three tabs tabOne, tabTwo and tabThree.
position index of tabs
tabOne - 0
tabTwo - 1
tabThree - 2
to hide tabTwo, you can use remove function and again reappear you can use set function.
to remove tab
tabPane.getTabs().remove(tabTwo);
set again with the relevant index to display at the correct location.
tabPane.getTabs().set(1, tabTwo);

JavaFX: Getting IDs of Dynamic created Button

I'm currently made an Form with JavaFX.
Always i press a Button, i call the "addAnswer()"-Method.
In that I create a RadioButton, a Label and a delete-Button, which i bundle in a HBox. All that HBoxes i pack in a vBox.
The Problem now is the delete-Button. I want to delte just THAT HBox in which the clicked Button is.
Here is my code:
public void addAnswer() {
this.rB = new RadioButton();
checkAnswer.getToggles().add(rB);
hBox = new HBox();
tF = new TextField();
delAnswer = new Button("Löschen");
delAnswer.setId(Integer.toString(counter));
hBox.getChildren().addAll(rB, tF, delAnswer);
hBox.setId(Integer.toString(counter));
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBox.getId())));
System.out.println(delAnswer.getId());
vBox.getChildren().addAll(hBox);
counter++;
}
public void delAnswer(int e){
vBox.getChildren().remove(delAnswer.getId());
}
i tried this one above but i realized, that all the delAnswers-Buttons have the same ID: the number of how often i pressed the add-Button.
Is there any solution where i can just select that one i pressed with that dynamic way? Cause i don't kow how often somebody will press or delete something.
Thanks
hbox is a field and this is why always the HBox last added is used. (hBox is evaluated, when lambda body is executed, not at the time of the lambda creation). This would be different, if you used a (effectively) final local variable:
final HBox hBoxLocal = hBox;
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBoxLocal.getId())));
However I'd like to present a different solution which would allow you to use the same EventHandler<ActionEvent> for all delete Buttons:
You can get the Node that triggered the event using getSource. From this Node you can get the parent, which is the HBox. You can remove this from the VBox using the remove(Object) method
delAnswer.setOnAction(e -> {
// get button
Node source = (Node) e.getSource();
// remove parent of button from VBox
vBox.getChildren().remove(source.getParent());
});
I think your problem is that you give the same event to all your button,Begin by creating a list that stores your buttons and then increments the value of the ID after affecting it to an item :
List<Button> buttons = new ArrayList<>();
/*
Create Button and call IDEvt method to create new event
for each button
*/
private void IDEvt(Button btn){
btn.setId(String.valueOf(IDRank));
btn.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(btn.getId());
}
});
IDRank++;
}

Categories

Resources