I want to bind a content of TreeView selected items list and bumped into a strange behaviour of the selected items change value when removing elements. I wrote a some test application:
public class TreeViewSelectedItemsBindingTest extends Application {
public class Item extends TreeItem<Integer> {
public Item(Integer... value) {
Arrays.stream(value).forEach(v -> getChildren().add(new TreeItem<Integer>(v)));
}
}
#Override
public void start(Stage primaryStage) throws Exception {
TreeView<Integer> treeView = new TreeView<>();
treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
treeView.setRoot(new Item(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
treeView.getRoot().setExpanded(true);
treeView.setShowRoot(false);
ListView<TreeItem<Integer>> listView = new ListView<>();
Bindings.bindContent(listView.getItems(), treeView.getSelectionModel().getSelectedItems());
treeView.getSelectionModel().getSelectedItems()
.addListener((ListChangeListener<? super TreeItem<Integer>>) change -> {
System.out.println("Change: " + change);
System.out.println("TreeView size: " + treeView.getSelectionModel().getSelectedItems().size());
System.out.println("ListView size: " + listView.getItems().size());
System.out.println("-------------------");
});
HBox box = new HBox();
box.getChildren().addAll(treeView, listView);
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
static public void main(String[] args) {
launch(args);
}
}
This application bind TreeView selected items to ListView items. Select in the tree view all elements from 0 to 9 and then press SHIFT+element 5, for example, to change selection from 0 to 5 elements. You will got an exeption:
Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException: toIndex = 9
It is because ListChangeListener.Change reports from index relative to initial unmodified list, not relative a list changed previously:
-------------------
Change: { [TreeItem [ value: 6 ]] removed at 6, }
TreeView size: 9
ListView size: 9
-------------------
Change: { [TreeItem [ value: 7 ]] removed at 7, }
TreeView size: 8
ListView size: 8
-------------------
You can see "removed at 6" then "removed at 7", but size of underlying lists also changed so index should not be increased here, i.e. all "removed at" should be at 6. And because of this a Bindings.bindContent is failed.
It is a bug JDK-8180359. As a workaround the bonded list should be updated as whole:
treeView.getSelectionModel().getSelectedItems().addListener(
(ListChangeListener<? super TreeItem<Model>>) change -> {
while (change.next()) {
listView.setAll(change.getList().stream()
.filter(Objects::nonNull)
.map(i -> i.getValue())
.collect(toList()));
}
});
Related
So I'm using ComboBox to create a list of items of which the user can choose one. The list is hierarchical/indented in the following way:
Header 1
Item 1
Item 2
Header 1.1
Item 1
Item 2
Header 2
Item 1
Item 2
This looks good, but here comes the problem. When a user clicks on one of the indented items, the item is displayed WITH indentation. So the ComboBox displays, for example, the following:
Item 1
That looks really bad. I want it to be displayed like this (without the preceding indentation):
Item 1
Code for running the program:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
// Parent root = FXMLLoader.load(getClass().getResource("interface.fxml"));
ComboBox<String> genre1AddBook = new ComboBox<String>();
ObservableList<String> genresAddBook;
genresAddBook = FXCollections.observableArrayList("SKĂ–NLITTERATUR", "\tDeckare", "\tFantasy");
genre1AddBook.getItems().addAll(genresAddBook);
StackPane root = new StackPane();
root.getChildren().add(genre1AddBook);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How can I make the ComboBox display the indented items without the indentation and still display a hierarchical/indented structure?
Given the class you posted, you can use a buttonCell on the combo box that strips the whitespace from the string.
genre1AddBook.setButtonCell(new ListCell<>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty) ;
if (empty || item == null) {
setText("");
} else {
setText(item.stripLeading());
}
}
});
But this is a really bad design; I assume in your real application you would not actually use String as the data class for your ComboBox, if the values in the combo box have additional data associated with them (such as the parent or child nodes in the hierarchical structure). You should set a cell factory on the combo box that indents the cells in the dropdown according to their position in the hierarchy, and a button cell which doesn't indent the text.
I see a strange appearance of the text contained in a TextArea aftrer doing some changes of TextArea content and style.
With the simplified code shown below I reproducibly see this when I click the button 4 times:
But this is what I expected to see:
Note: If I then click into the TextArea I see the expected result.
What can can be done to get the expected result?
Note that I need to set textarea min/max width and height to get a nice appearance of the content.
Of course I could set it to a bigger value, but that would destroy the look that is required.
I tried setCache as proposed here but that did not work.
I have JavaFX-8 on Windows 8.1. I would also be interested what results are seen in newer versions.
EDIT
With JavaFX-13 the result is:
The text seems to be moved to the right instead of centered as specified in the css (and also to the bottom). I had ecpected that the text is postioned the same as on initial start of the application.
CSS:
.text-area-centered *.text {
-fx-text-alignment: center ;
}
.text-area-centered .scroll-pane {
-fx-hbar-policy: NEVER;
-fx-vbar-policy: NEVER;
}
Java:
public class Main extends Application {
private static final BackgroundFill blackBGF = new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY);
private static final BackgroundFill whiteBGF = new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY);
private static double textareaXY = 50;
private TextArea textarea = new TextArea();
private int clickNo = 1;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
VBox vb = new VBox();
root.setCenter(vb);
Button b = new Button("ClickMe");
b.addEventHandler(ActionEvent.ACTION, this::OnClickButton);
vb.getChildren().add(b);
vb.getChildren().add(textarea);
textarea.setEditable(false);
textarea.getStyleClass().add("text-area-centered");
textarea.setBackground(new Background(blackBGF));
textarea.setMinHeight(textareaXY);
textarea.setMaxHeight(textareaXY);
textarea.setMinWidth(textareaXY);
textarea.setMaxWidth(textareaXY);
textarea.setFont(new Font("Courier New",10));
textarea.setText("1 2 3\n4 5 6\n7 8 9");
primaryStage.show();
}
private void OnClickButton(ActionEvent event)
{
if(clickNo == 1)
{
textarea.setText("7");
textarea.setFont(new Font("Courier New Bold",24));
}
else if(clickNo == 2)
{
Region region = ( Region ) textarea.lookup( ".content" );
region.setBackground(new Background(blackBGF));
textarea.setStyle("-fx-text-inner-color: white;");
}
else if(clickNo == 3)
{
Region region = ( Region ) textarea.lookup( ".content" );
region.setBackground(new Background(whiteBGF));
textarea.setStyle("-fx-text-inner-color: black;");
}
else if(clickNo == 4)
{
textarea.setText("1 2 3\n4 5 6\n7 8 9");
textarea.setFont(new Font("Courier New",10));
}
clickNo++;
}
public static void main(String[] args) {
launch(args);
}
}
I'm trying to make a program that will display a random set of 4 cards, then when I click the button again it will clear the old set and display a new random set.
Right now my program will display 4 random images of cards when I click the button; however, when I try to click it again nothing happens. I'm assuming it has something to do with the EventHandler no longer being registered to the button after I clear the root children. However, I don't know how to go about fixing this. Any help is greatly appreciated! I haven't been able to find an answer to this yet, and have only been learning JavaFX for about a week. Thank you.
The code I have so far:
public class CardShuffle extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
Scanner input = new Scanner(System.in);
File cardsFolder = new File("C:\\Users\\timsp\\Pictures\\JPEG");
ArrayList<File> cardsFilePaths = new ArrayList<File> (Arrays.asList(cardsFolder.listFiles()));
Button deal = new Button("DEAL");
Pane hb = new HBox(10);
hb.setPadding(new Insets(5, 5, 5, 5));
root.getChildren().add(deal);
deal.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
root.getChildren().clear();
ArrayList<ImageView> cards = getRandomCards(cardsFilePaths);
for (int i = 0; i < 4; i++) {
cards.get(i).setFitWidth(150);
cards.get(i).setFitHeight(100);
hb.getChildren().add(cards.get(i));
}
root.getChildren().addAll(deal, hb);
}
});
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public ArrayList<ImageView> getRandomCards(ArrayList<File> cardsFilePaths) {
ArrayList<ImageView> cards = new ArrayList<ImageView>();
try {
for (int i = 0; i < 4; i++) {
Image card = new Image((new FileInputStream(cardsFilePaths.get((int) (Math.random() * 52)).getPath())));
ImageView temp = new ImageView();
temp.setImage(card);
cards.add(temp);
}
} catch (FileNotFoundException e) {
e.getMessage();
}
return cards;
}
}
Many problems here :
the first one, and the most important (because it hides your further error) is the root layout : you use a StackPane, the first thing you should do is to replace it by a VBox for example and rerun your program, it will be easier to see what really happens. (you will not have 4 cards, but 8, 12, 16 and so on).
the first one generates the second one. By doing this root.getChildren().addAll(deal, hb); you put the HBox layout above the button, and the click is first consumed by the HBox. Here is an example to see it more easily :
// Add the HBox as soon as the initialization
root.getChildren().add(deal);
hb.setOnMouseClicked(e -> System.out.println("HBox clicked"));
deal.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
root.getChildren().clear();
ArrayList<ImageView> cards = getRandomCards(cardsFilePaths);
for(int i = 0; i < 4; i++) {
cards.get(i).setFitWidth(150);
cards.get(i).setFitHeight(100);
hb.getChildren().add(cards.get(i));
}
hb.setStyle("-fx-background-color:CORNFLOWERBLUE;-fx-opacity:0.8;");
root.getChildren().addAll(deal, hb);
}
});
And the last one, you don't really want to remove all root's children, what you want is to replace your cards by another 4 ones. Thus it is not necessary to remove the button, only the HBox can be manipulated as shown by the following example :
// Add the HBox as soon as the initialization
root.getChildren().addAll(hb, deal);
deal.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// root.getChildren().clear();
// Replace the previous line by the following :
hb.getChildren().clear();
ArrayList<ImageView> cards = getRandomCards(cardsFilePaths);
for(int i = 0; i < 4; i++) {
cards.get(i).setFitWidth(150);
cards.get(i).setFitHeight(100);
hb.getChildren().add(cards.get(i));
}
// The following is useless now.
// root.getChildren().addAll(hb, deal);
}
});
I have problem with showing data using JavaFX lib.
Description of my problem:
I need to show lists of String's and every list have different count of elements.
All that i invented is create listview for every list.
But i have trouble with showing:
For example: I have listview with String's - and following simple code for it:
ListView<String> lst = new ListView<>();
ObservableList<String> observableList = FXCollections.observableArrayList("Hello", "Hello -2");
lst.setItems(observableList);
lst.setPrefHeight(100);
lst.setOrientation(Orientation.HORIZONTAL);
Scene scene = new Scene(new VBox(lst));
primaryStage.setScene(scene);
primaryStage.show();
And when i run it - i see something like:
But i want that elements in listview fills in space in view, i do not want this stripy space in right side.
In my task i have different lists with different count of elements.
If you can help me or get me idea - how it should be done - it will be great!
Thanx!
I think this is the solution that are you looking for:
public class TestController {
#FXML
private ListView<String> listView;
#FXML
public void initialize() {
ObservableList<String> observableList = FXCollections.observableArrayList("Hello", "Hello -2", "More", "Item");
listView.setItems(observableList);
listView.setPrefHeight(100);
Platform.runLater(() -> System.out.println(listView.getWidth()));
listView.setOrientation(Orientation.HORIZONTAL);
listView.setCellFactory(param -> new CustomListCell());
}
private class CustomListCell extends ListCell<String> {
CustomListCell() {
// If you want to use as a separate class you can use the getListView() instead of listView.
prefWidthProperty().bind(listView.widthProperty()
.divide(listView.getItems().size()) // set the width equally for each cell
.subtract(1)); // subtracted 1 to prevent displaying of a scrollBar, but you can play with
// this if you have many values in the listView
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item);
}
}
}
}
Why does a
tableColumn.setCellValueFactory(new PropertyValueFactory<Object1, Integer>("Number"));
method call the getNumber method multiple times when I only add one row to a tableView?
The number field is in object2 that is used in object1. So here Object1 one provides a getNumber method that gets a number stored in an arraylist in object2.
Here is my getNumber method:
public double getNumber() {
setNumber++;
return sets.get(setNumber).getNumber();
}
The value is fetched when the TableView shows it. When you modify your value when it is retrieved, you are just doing that and you just count the fetches.
Imagine you have a TableView with 10 visible rows and 20 entries in the list. When you scroll down and up, the value is fetched for the rows which newly appear in the visible area.
If you try this:
public class SO extends Application {
public static class Item {
final int v;
Item(int v) { this.v = v; }
public int getV() {
System.out.println(v);
return v;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
Scene scene = new Scene(pane, 300, 100);
primaryStage.setScene(scene);
ObservableList<Item> list = FXCollections.observableArrayList();
for (int i=0; i<20; i++) {
Item item = new Item(i);
list.add(item);
}
TableView<Item> tv = new TableView<>(list);
TableColumn<Item, String> col = new TableColumn<>("Column");
col.setCellValueFactory(new PropertyValueFactory<Item,String>("v"));
tv.getColumns().add(col);
pane.getChildren().add(tv);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
When scrolling the TableView to the end, it will print increasing numbers in the console. When scrolling back, it prints decreasing numbers as it displays values from the beginning of the list. Part of the output:
5
6
7
8
9
9
2
2
1
1
0