I would like to add items to a table dynamically.
I do it like this:
public class TestApp extends Application {
public static void main(final String[] args) {
launch(args);
}
private final AtomicLong counter = new AtomicLong();
#Override
public void start(final Stage primaryStage) {
final VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
final TableView<String> tableView = new TableView<>();
final TableColumn<String, String> column = new TableColumn<>("Text");
column.setCellValueFactory(f -> new SimpleStringProperty(f.getValue()));
tableView.getColumns().add(column);
// Add some sample items to our TableView
for (int i = 0; i < 100; i++) {
tableView.getItems().add("Item #" + counter.incrementAndGet());
}
final Button button = new Button("Add items");
final long oldElement = counter.get();
button.setOnAction(e -> {
// Add more elements
for (int i = 0; i < 10; i++) {
tableView.getItems().add("Item #" + counter.incrementAndGet());
}
tableView.scrollTo("Item #" + oldElement);
});
root.getChildren().add(button);
root.getChildren().add(tableView);
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
(Example taken from here)
What I would like to change now, is, that when the table is already scrolled down completely, and I add more items, the table automatically scrolls down.
The 'scroll-state' seems to be maintained, which is why I still see the very bottom of the table (and the new elements).
How can I add items to the table but keeping the scroll at the current position / current row?
The 'scrollTo' works, but moves the last element from the bottom of the view to the top, which is also a litte irritating.
Related
I have a JavaFX application using TreeTableViews using JFX8.
Is there a way to stop TreeTableViews from intercepting mouse-clicks in the region of the Disclosure Node arrow even when the row is a leaf (ie has no children) and thereby has no Disclosure Node expand arrow? The current behaviour seems to be to intercept these mouse-clicks in the far-left of the Row, though I think users would expect the row to be selected as there is no disclosure node.
See below example...
public class TestTreeTableViewApplication extends Application {
#Override
public void start(Stage primaryStage) {
AnchorPane anchorPane = new AnchorPane();
anchorPane.setPrefWidth(600);
anchorPane.setPrefHeight(600);
TreeTableView<String> treeTableView = new TreeTableView<>();
TreeTableColumn<String, String> primaryColumn = new TreeTableColumn<>("Col1");
primaryColumn.setPrefWidth(200);
treeTableView.getColumns().add(primaryColumn);
//Just to show some data in the column for test purposes...
primaryColumn.setCellValueFactory(call -> new ReadOnlyStringWrapper(call.getValue().getValue()));
treeTableView.setRoot(new TreeItem<>("Root"));
for (int i = 0; i < 15; i++) {
TreeItem<String> newChild = new TreeItem<>("1st Tier Test " + i);
treeTableView.getRoot().getChildren().add(newChild);
for (int j = 0; j < 3; j++) {
TreeItem<String> newChild2 = new TreeItem<>("2nd Tier Test " + i);
newChild.getChildren().add(newChild2);
}
}
anchorPane.getChildren().add(treeTableView);
Scene scene = new Scene(anchorPane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
My Problem
I'm trying to resize a column and, it works as inteended at first. But as soon as the width of the column is the same as the minimum width, it doesn't work anymore.
It's like stuck. As user, you can't make it bigger unless you resize the whole window.
I know roughly what the problem is. I think it is because there is no space to make a cell bigger.
What I tried
What I know is that if I removed this line, it would work.
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
But I need this line for my programm, because the number of columns is different from file to file. I also need this line because every column should be the same size after loading the file (number of columns / width from tableview = column width).
MCVE
public class GuiLayout extends Application {
private Scene mainScene;
private Stage mainStage;
private VBox mainBox;
private TableView<String> tableView;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
initTable();
mainStage = new Stage();
mainScene = new Scene(mainVBox(), 1000, 600);
ObservableList observableList = FXCollections.observableArrayList("1", "1234", "Avenue 1", "17", "Second", "Married", "M", "admin", "admin#admin.com", "Just", "Test", "no");
tableView.setItems(observableList);
tableView.prefWidthProperty().bind(mainBox.widthProperty());
tableView.prefHeightProperty().bind(mainBox.heightProperty());
mainStage.setScene(mainScene);
mainStage.show();
}
private GridPane mainGrid() {
GridPane gridPane = new GridPane();
gridPane.setVgap(10);
gridPane.setHgap(10);
gridPane.add(tableView, 0, 2);
return gridPane;
}
private VBox mainVBox() {
mainBox = new VBox();
mainBox.prefWidthProperty().bind(mainStage.widthProperty().multiply(0.8));
mainBox.setPadding(new Insets(10, 10, 10 ,10));
mainBox.getChildren().add(mainGrid());
return mainBox;
}
private TableView initTable() {
tableView = new TableView<>();
TableColumn<String, String> numberCol
= new TableColumn<>("Nr.");
TableColumn<String, String> plzCol
= new TableColumn<>("PLZ");
TableColumn<String, String> adressCol
= new TableColumn<>("Adress");
TableColumn<String, String> houseNumber
= new TableColumn<>("House number");
TableColumn<String, String> floorCol
= new TableColumn<>("Floor");
TableColumn<String, String> familyStatusCol
= new TableColumn<>("Family Status");
TableColumn<String, String> genderCol
= new TableColumn<>("Gender");
TableColumn<String, String> userNameCol //
= new TableColumn<>("User Name");
TableColumn<String, String> emailCol//
= new TableColumn<>("Email");
TableColumn<String, String> firstNameCol //
= new TableColumn<>("First Name");
TableColumn<String, String> lastNameCol //
= new TableColumn<>("Last Name");
// Active Column
TableColumn<String, Boolean> activeCol//
= new TableColumn<>("Active");
tableView.getColumns().addAll(numberCol, plzCol, adressCol, houseNumber, floorCol, familyStatusCol, genderCol, userNameCol, emailCol, firstNameCol, lastNameCol, activeCol);
for (int i = 0; tableView.getColumns().size() > i; i++){
tableView.getColumns().get(i).setMinWidth(100);
}
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
return tableView;
}
}
In the end, I expect that you can resize the column (make it bigger) when every columns has the minimum width.
Thank you for your help.
Edit:
I know that I need a customized Callback but I don't really know how I should realized it.
I tried things like setting the Property to tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY); and set the size of the column hardcoded.
for (int o = 0; o < tableView.getColumns().size(); o++) {
tableView.getColumns().get(o).prefWidthProperty().bind(tableView.widthProperty().divide(tableView.getColumns().size()));
Can anyone help me with the Callback? I'm not asking you if you can make the Callback for me (but if you want go for it) more I'm asking if you can give me a hint or something how I could realise it.
I am working on an application in JavaFX where I need multiple scenes to switch between. But it seems like I can't have the same item (Example: a toolbar) In multiple scenes, it just shows the item in one of the scenes. Maybe it isn't possible to have the same item in different scenes, so my question is how do I do it then? Do I need multiple stages and if that is the case how do I change between stages? I am not using FXML for this project, we have to code it.. My current code:
public class Main extends Application {
private Label time;
private int minute;
private int hour;
private int second;
public static void main(String[] args) {
launch(args);
}
// CLOCK RUNNING
public void initialize() {
Timeline clock = new Timeline(new KeyFrame(Duration.ZERO, e -> {
Calendar cal = Calendar.getInstance();
second = cal.get(Calendar.SECOND);
minute = cal.get(Calendar.MINUTE);
hour = cal.get(Calendar.HOUR);
//System.out.println(hour + ":" + (minute) + ":" + second);
time.setText(hour + ":" + (minute) + ":" + second);
}),
new KeyFrame(Duration.seconds(1))
);
clock.setCycleCount(Animation.INDEFINITE);
clock.play();
}
#Override
public void start(Stage primaryStage) throws Exception {
//Specify The Size of Scenes, and the scenes.
BorderPane root1 = new BorderPane();
BorderPane root2 = new BorderPane();
Scene scene1 = new Scene(root1, 1100, 900);
Scene scene2 = new Scene(root2,1100,900);
// Get CSS File
scene1.getStylesheets().add("Helmuth.css");
time = new Label("Time:");
initialize();
//ToolBar i want this to be shown in both scenes //
Button homebt = new Button("Home");
Button tabelbt = new Button("Tabel");
ToolBar toolBar = new ToolBar();
toolBar.getItems().add(homebt);
toolBar.getItems().add(tabelbt);
toolBar.getItems().add(time);
Label label1 = new Label("Welcome to the first scene!");
Button button1 = new Button("Go to scene 2");
button1.setOnAction(e -> primaryStage.setScene(scene2));
VBox layout1 = new VBox();
layout1.getChildren().addAll(button1,toolBar);
Button button2 = new Button("Go Back");
button2.setOnAction(e -> primaryStage.setScene(scene1));
VBox mainbox = new VBox();
mainbox.setAlignment(Pos.TOP_CENTER);
mainbox.getChildren().addAll(button2, toolBar);
// Start scene 1
root2.setCenter(mainbox);
root1.setCenter(layout1);
primaryStage.setScene(scene1);
primaryStage.setTitle("Helmuth");
boolean b = false;
primaryStage.setResizable(b);
primaryStage.show();
}
}
Why do you want to switch between different scenes. A solution to your problem might be to just exchange the root node of the scene.
Demonstration of answer:(answered May 29 at 3:10 am)
**10/7/2016** you can find the code on GitHub
Actual Question before answered:(asked May 22 at 19:53)
The title might be not too great but what I want to do is something like this in JavaFX:
Examples
YouTube:
StackOverFlow(which has and autocomplete):
Question:
I don't require to write me the code for that. Instead I want to know how I can achieve that using JavaFX and some ideas.
For the tags you can use a custom styled HBox containing a Text (the tag name) node an a Button (the deletion button (X)). By playing around with the background and the border you can achieve the desired look of the tags.
The onAction handler of the button should remove the tag from it's parent...
For the whole tag bar you can use another HBox. Use the appropriate border for the correct look. In addition to the tags add a TextField with no background as last element and set the Hgrow property of that TextField to Priotity.ALWAYS to cover the rest of the available space.
The onAction handler of this TextField adds new tags and clears the content of the TextField.
You could e.g. use ControlsFX's autocompletion features with the TextField or implement it on your own for a custom look...
public class TagBar extends HBox {
private final ObservableList<String> tags;
private final TextField inputTextField;
public ObservableList<String> getTags() {
return tags;
}
public TagBar() {
getStyleClass().setAll("tag-bar");
getStylesheets().add(getClass().getResource("style.css").toExternalForm());
tags = FXCollections.observableArrayList();
inputTextField = new TextField();
inputTextField.setOnAction(evt -> {
String text = inputTextField.getText();
if (!text.isEmpty() && !tags.contains(text)) {
tags.add(text);
inputTextField.clear();
}
});
inputTextField.prefHeightProperty().bind(this.heightProperty());
HBox.setHgrow(inputTextField, Priority.ALWAYS);
inputTextField.setBackground(null);
tags.addListener((ListChangeListener.Change<? extends String> change) -> {
while (change.next()) {
if (change.wasPermutated()) {
ArrayList<Node> newSublist = new ArrayList<>(change.getTo() - change.getFrom());
for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
newSublist.add(null);
}
for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
newSublist.set(change.getPermutation(i), getChildren().get(i));
}
getChildren().subList(change.getFrom(), change.getTo()).clear();
getChildren().addAll(change.getFrom(), newSublist);
} else {
if (change.wasRemoved()) {
getChildren().subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear();
}
if (change.wasAdded()) {
getChildren().addAll(change.getFrom(), change.getAddedSubList().stream().map(Tag::new).collect(Collectors.toList()));
}
}
}
});
getChildren().add(inputTextField);
}
private class Tag extends HBox {
public Tag(String tag) {
getStyleClass().setAll("tag");
Button removeButton = new Button("X");
removeButton.setOnAction((evt) -> tags.remove(tag));
Text text = new Text(tag);
HBox.setMargin(text, new Insets(0, 0, 0, 5));
getChildren().addAll(text, removeButton);
}
}
}
style.css
.tag-bar {
-fx-border-color: blue;
-fx-spacing: 3;
-fx-padding: 3;
-fx-max-height: 30;
}
.tag-bar .tag {
-fx-background-color: lightblue;
-fx-alignment: center;
}
.tag-bar .tag .button {
-fx-background-color: transparent;
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Sort");
StackPane.setAlignment(btn, Pos.BOTTOM_CENTER);
TagBar tagBar = new TagBar();
btn.setOnAction((ActionEvent event) -> {
FXCollections.sort(tagBar.getTags());
});
Button btn2 = new Button("add \"42\"");
btn2.setOnAction(evt -> {
if (!tagBar.getTags().contains("42")) {
tagBar.getTags().add("42");
}
});
VBox root = new VBox();
root.getChildren().addAll(tagBar, btn, btn2);
root.setPrefSize(300, 400);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
Simple implementation of this code!
import ....
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane root = new BorderPane();
HBox tagsPane = new HBox(10);
tagsPane.setStyle("-fx-border-color: #F1F1F1;" +
" -fx-border-width: 1px;" +
" -fx-border-radius: 10;" +
" -fx-border-insets: 5");
root.setBottom(tagsPane);
TextField textField = new TextField();
textField.setPromptText("Tag name - ENTER to add");
textField.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER) {
tagButton(tagsPane, textField.getText());
textField.clear();
}
});
root.setTop(textField);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 450, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
//little image as 15x15 for example
Image toUse = new Image("sample/delete.png");
//box is the pane where this buttons will be placed
public void tagButton(HBox box,String tag){
ImageView closeImg = new ImageView(toUse);
Button result = new Button(tag,closeImg);
result.setPrefHeight(20);
result.setContentDisplay(ContentDisplay.RIGHT);
result.setOnAction(event -> box.getChildren().remove(result));
box.getChildren().add(result);
}
}
Also if u need different events for click on tag and click on "X" you can implement tagButton like this :
public void tagButton(HBox box,String tag){
ImageView closeImg = new ImageView(toUse);
HBox button = new HBox();
button.setStyle("-fx-padding:4;" +
" -fx-border-width: 2;" +
" -fx-border-color: black;" +
" -fx-border-radius: 4;" +
" -fx-background-color: f1f1f1;" +
" -fx-border-insets: 5;");
button.setPrefHeight(20);
button.getChildren().addAll(new Label(tag),closeImg);
closeImg.setOnMouseClicked(event ->
box.getChildren().remove(button)
);
button.setOnMouseClicked(event -> {
//doSomethig
});
box.getChildren().add(button);
}
This is my version too
The whole Main class
its somehow long that's why.
But to sum up. You need a
1: FlowPane for the container, and you do not have to worry about wrapping,it will wrap itself, both vertical or horizontal.
2: Label of course for your Text, which has a GraphicProperty
3: Path - well you could use Button, and add a Shape or Image to it, but that will be a lot of Nodes, so i used Path and i drew a X red button.
The rest is styling to your preferred color
EDIT
something like this?
you can style it to get that output
setFont(Font.font("Serif Regular", FontWeight.SEMI_BOLD,12));
use this line on the TextField
Here is a basic example of a tag bar (I wrote some code, because I think it's easier to follow). For the additional AutoComplete function you could use e.g. ControlsFx, as fabian already mentioned.
public class CloseTag extends HBox implements Comparable<CloseTag> {
private Label label;
private Label closeIcon;
public CloseTag(String text) {
setStyle("-fx-padding:8;");
Text icon = GlyphsDude.createIcon(FontAwesomeIcon.TIMES_CIRCLE);
closeIcon = new Label(null, icon);
label = new Label(text, new StackPane(closeIcon));
label.setContentDisplay(ContentDisplay.RIGHT);
getChildren().add(label);
}
public void setOnCloseAction(EventHandler<? super MouseEvent> action) {
closeIcon.setOnMouseClicked(action);
}
public String getText() {
return label.getText();
}
#Override
public int compareTo(CloseTag other) {
return getText().compareTo(other.getText());
}
}
public class TagPane extends FlowPane {
private TextField textField;
public TagPane() {
setStyle("-fx-padding:8;" + "-fx-hgap:10;");
setOnMouseClicked(evt -> onMouseClickedd(evt));
textField = new TextField();
textField.setOnKeyPressed(evt -> onKeyPressed(evt, textField));
}
private void onMouseClickedd(MouseEvent mouseEvent) {
if (mouseEvent.getTarget() != this || textField.getParent() != null ) {
return;
}
getChildren().add(textField);
textField.requestFocus();
}
private void onKeyPressed(KeyEvent evt, TextField textField) {
if (evt.getCode() == KeyCode.ENTER || evt.getCode() == KeyCode.TAB) {
createTag(textField.getText());
textField.clear();
}
}
private void createTag(String text) {
CloseTag tag = new CloseTag(text);
tag.setOnCloseAction(evt -> removeTag(tag));
getChildren().remove(textField);
getChildren().add(tag);
}
private void removeTag(CloseTag tag) {
getChildren().remove(tag);
}
}
I am building my fxml programmatically.
Now I'm adding a titledpanes to my accordion and in those titledpane I have setContent to a gridpane that is defined in the loop.
The result screen shows all titledpanes i've added. It shows the title of the titledpanes I set. But it only shows the content of the last titledpane I added.
I don't understand why, as the reference to the titledpane still exists in the arraylist I made and in my understanding the content(Node) I have set is saved there. And it looks like the row and columnconstraints are working for all instances, which I think means that the grid has been found for every TitledPane but every but the last one is without content.
Public class FXMLController Implements Initializable {
#FXML private VBox vboxDrills;
//NonFXML
private ArrayList<TitledPane> lsttpDrillList;
private Accordion accDrill;
public void initLayout(ArrayList<Drill> drills){
accDrill = new Accordion();
lsttpDrillList = new ArrayList<>();
TitledPane tpDrill; //AP = AccordionPane
GridPane gpTopDrill;
Accordion accDrillStep;
GridPane gpBottomDrill;
//gpTopDrill
ColumnConstraints cc1gpTopDrill = new ColumnConstraints();
cc1gpTopDrill.setHgrow(Priority.SOMETIMES);
cc1gpTopDrill.setMaxWidth(Double.NEGATIVE_INFINITY);
cc1gpTopDrill.setMinWidth(10.0);
cc1gpTopDrill.setPrefWidth(200.0);
ColumnConstraints cc2gpTopDrill = new ColumnConstraints();
cc2gpTopDrill.setHgrow(Priority.SOMETIMES);
cc2gpTopDrill.setMaxWidth(Double.NEGATIVE_INFINITY);
cc2gpTopDrill.setMinWidth(10.0);
cc2gpTopDrill.setPrefWidth(400.0);
RowConstraints rc1gpTopDrill = new RowConstraints();
rc1gpTopDrill.setMaxHeight(Double.NEGATIVE_INFINITY);
rc1gpTopDrill.setMinHeight(Double.NEGATIVE_INFINITY);
rc1gpTopDrill.setPrefHeight(35.0);
rc1gpTopDrill.setVgrow(Priority.SOMETIMES);
Label lblDrillID = new Label();
lblDrillID.minHeight(Double.NEGATIVE_INFINITY);
lblDrillID.minWidth(Double.NEGATIVE_INFINITY);
lblDrillID.setText("Drill ID:");
Label lblActualID = new Label();
lblActualID.minHeight(Double.NEGATIVE_INFINITY);
lblActualID.minWidth(Double.NEGATIVE_INFINITY);
Label lblDrillTitle = new Label();
lblDrillTitle.setText("Drill Title:");
TextField txtDrillTitleEdit = new TextField();
//end gpTopDrill
for(int i=0; i<drills.size(); i++){
//new instance and title
Drill drill = drills.get(i);
tpDrill = new TitledPane();
if(!drill.getTitle().isEmpty()){
tpDrill.setText(drill.getTitle());
}else{
tpDrill.setText("Drill" + drill.getID());
}
tpDrill.setId("drillPane" + (i));
//gpTopDrill
lblActualID.setText(Integer.toString(drill.getID()));
txtDrillTitleEdit.setId("drillTitleEdit" + (i));
txtDrillTitleEdit.setText(drill.getTitle());
GridPane.setConstraints(lblDrillID, 0, 0);
GridPane.setConstraints(lblActualID, 1, 0);
GridPane.setConstraints(lblDrillTitle, 0, 1);
GridPane.setConstraints(txtDrillTitleEdit, 1, 1);
gpTopDrill = new GridPane();
gpTopDrill.getColumnConstraints().addAll(cc1gpTopDrill, cc2gpTopDrill);
gpTopDrill.getRowConstraints().addAll(rc1gpTopDrill, rc1gpTopDrill);
gpTopDrill.getChildren().addAll(lblDrillID, lblActualID, lblDrillTitle, txtDrillTitleEdit);
//end gpTopDrill
//accDrillStep
//end accDrillStep
//gpBottomDrill
//end gpBottomDrill
//put VBox in drillpane
tpDrill.setContent(gpTopDrill);
lsttpDrillList.add(tpDrill);
}
accDrill.getPanes().setAll(lsttpDrillList);
accDrill.layoutXProperty().set(8.0);
accDrill.layoutYProperty().set(7.0);
accDrill.setExpandedPane(lsttpDrillList.get(0));
vboxDrills.getChildren().add(0, accDrill);
}
...
}
Example