Mouse Click event on GridPane JavaFX-8 - java

The solution is probably staring at me in the face. But here's my problem.
Take a look at this method:
public void showUserData() {
int numUsers = usersModel.numberOfUsers(); // number of users(rows) in the database
ColumnConstraints column1 = new ColumnConstraints();
column1.setPercentWidth(40);
ColumnConstraints column2 = new ColumnConstraints();
column2.setPercentWidth(60);
int counter = 0;
for(int i = 0; i <= numUsers - 1; i++) {
subGrid = new GridPane();
subGrid.getColumnConstraints().addAll(column1, column2);
userImage = new ImageView();
subGrid.setStyle("-fx-background-color: #dddddd;");
subGrid.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("mouse click detected! " + mouseEvent.getSource());
subGrid.setStyle("-fx-background-color: blue;");
}
});
vbox = new VBox();
vbox.getChildren().add(new Label("Name: " + getUserData().get(counter)));
vbox.getChildren().add(new Label("Username: " + getUserData().get(counter + 1)));
vbox.getChildren().add(new Label("Position: " + getUserData().get(counter + 2)));
subGrid.add(vbox, 1, 0);
user = new Image("file:///" + getUserImage().get(i));
userImage.setFitWidth(150);
userImage.setFitHeight(150);
userImage.setSmooth(true);
userImage.setPreserveRatio(true);
userImage.setImage(user);
subGrid.add(userImage, 0, 0);
if(i % 2 == 0 && i == 0) {
mainGrid.add(subGrid, 0, 0);
} else if (i % 2 == 0 && i != 0){
mainGrid.add(subGrid, 0, i - 1);
} else {
mainGrid.add(subGrid, 1, i - 1);
}
scroll.setContent(mainGrid);
counter = counter + 3;
}
}
I know its a mess but hear me out. I have a ScrollPane, and inside is a GridPane that spans the entire area of the ScrollPane. In every cell of the GridPane, there is another smaller GridPane. In each of these smaller GridPanes, the left column is an ImageView and the right is a VBox with labels in it.
The long and the short of this method is that:
a. It takes the number of rows in the database.
b. It creates a subGrid for every row in the database (corresponding to the number of users in the table).
c. It creates the ImageViews and VBoxes for each of the subGrids and adds them in.
d. It then takes the subGrids and adds it to the mainGrid.
This process works. But the listener only works on the latest subGrid that was added. i.e. second subGrid lights up, first does not, third subGrid lights up, second does not, etc.
I'm not sure if TableView can work with this. That's why I tried it with GridPane first. Why does my listener not work?
I ama beginner at this. I know the method is a mess. I know I need to refactor some things. But as long, as I can get this to work, I will happily clean it up afterwards. Any help appreciated, thank you.

Related

How can I fill a combobox with the selection from another combobox? JavaFX

I've started creating a GUI for a March Madness bracket generator by displaying all 64 teams for round1 as Labels and now I'm trying to create a ComboBox dropdown menu for each match.
I've created a ComboBox for 2 matches and now I want to create a new ComboBox that pulls its options from the other two ComboBox's before it. So in the example diagram below, the new ComboBox should have the options Duke and VCU for the user to choose from.
(2 combo boxes) (new combo box)
Duke------
Duke ---
ND St. ---
X
VCU -----
VCU ---
UCF -----
How can I do so?
public class ControlPanel extends Application
{
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("March Madness 2019 Generator");
BorderPane componentLayout = new BorderPane();
componentLayout.setPadding(new Insets(20,0,20,20));
final FlowPane choicePane = new FlowPane();
choicePane.setHgap(100);
Label choiceLbl = new Label("Match1");
ArrayList<Team> round1 = new ArrayList<Team>();
round1.add(new Team("Duke", 0.670, 1)); //0
round1.add(new Team("North Dakota St", 0.495, 16));
round1.add(new Team("VCU", 0.609, 8));
round1.add(new Team("UCF", 0.606, 9));
//The choicebox is populated from an observableArrayList
ChoiceBox r2Match1 = new ChoiceBox(FXCollections.observableArrayList( match(round1, 0, 1) ));
//Add the label and choicebox to the flowpane
choicePane.getChildren().add(choiceLbl);
choicePane.getChildren().add(r2Match1);
//put the flowpane in the top area of the BorderPane
componentLayout.setTop(choicePane);
//Add the BorderPane to the Scene
Scene appScene = new Scene(componentLayout,500,500);
//Add the Scene to the Stage
primaryStage.setScene(appScene);
primaryStage.show();
}
private ArrayList<Team> match(ArrayList<Team> roundPullFrom, int team1, int team2) {
ArrayList<Team> temp = new ArrayList<Team>();
temp.add(roundPullFrom.get(team1));
temp.add(roundPullFrom.get(team2));
return temp;
}
}
Combine ComboBoxes pairwise using the approach posted in my previous answer until you're left with a single ComboBox.
The following code layouts the nodes in something that resembles a tree structure too, but you could easily decouple the layout by keeping every round in a data structure instead of overwriting the values of a single array. (Since you'll want to access the data, you should store the combos in a proper data structure anyways.)
private static ComboBox<String> createCombo(double x, double y, double width) {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.setLayoutX(x);
comboBox.setLayoutY(y);
comboBox.setMaxWidth(Region.USE_PREF_SIZE);
comboBox.setMinWidth(Region.USE_PREF_SIZE);
comboBox.setPrefWidth(width);
return comboBox;
}
private static Label createLabel(String text, double maxWidth) {
Label label = new Label(text);
label.setMaxWidth(maxWidth);
return label;
}
#Override
public void start(Stage primaryStage) {
String[] teams = new String[64];
for (int i = 0; i < teams.length; i++) {
teams[i] = Integer.toString(i);
}
final double offsetY = 30;
final double offsetX = 100;
final double width = 90;
Pane root = new Pane();
// array storing the comboboxes
// combos for previous round are at the lowest indices
ComboBox<String>[] combos = new ComboBox[teams.length / 2];
// create initial team labels & comboboxes
for (int i = 0, offsetTeams = 0; i < combos.length; i++, offsetTeams += 2) {
Label label = createLabel(teams[offsetTeams], width);
double y = offsetTeams * offsetY;
label.setLayoutY(y);
root.getChildren().add(label);
label = createLabel(teams[offsetTeams+1], width);
label.setLayoutY(y+offsetY);
ComboBox<String> comboBox = createCombo(offsetX, y + offsetY / 2, width);
comboBox.getItems().addAll(teams[offsetTeams], teams[offsetTeams+1]);
combos[i] = comboBox;
root.getChildren().addAll(label, comboBox);
}
double x = 2 * offsetX;
int count = combos.length / 2; // combos still left for the next round
for (; count > 0; count /= 2, x += offsetX) { // for each round
// create comboboxes combining the combos from previous round pairwise
for (int i = 0, ci = 0; i < count; i++, ci+=2) {
// get combos pairwise
ComboBox<String> c1 = combos[ci];
ComboBox<String> c2 = combos[ci+1];
ComboBox<String> combo = createCombo(x, (c1.getLayoutY() + c2.getLayoutY()) / 2, width) ;
// combine data from previous round
ChangeListener<String> listener = (o, oldValue, newValue) -> {
final List<String> items = combo.getItems();
int index = items.indexOf(oldValue);
if (index >= 0) {
if (newValue == null) {
items.remove(index);
} else {
items.set(index, newValue);
}
} else if (newValue != null) {
items.add(newValue);
}
};
c1.valueProperty().addListener(listener);
c2.valueProperty().addListener(listener);
root.getChildren().add(combo);
combos[i] = combo;
}
}
primaryStage.setScene(new Scene(new ScrollPane(root), 600, 400));
primaryStage.show();
}
The structure of your problem is a tree. So you might want to have your solution support that structure. Either you use a Binary Tree data structure to resemble the tournament or you create such a structure by e.g. having classes like:
class Team {
String name;
}
class Match {
Team teamA;
Team teamB;
String where;
Date when;
public Team selectWinner() {
...
}
}
class Tournament {
List<Team> teams;
List<Match> getMatches(int round,List<Team> teams) {
List<Match> matches=new ArrayList<Match>)();
if (round==1) {
for (teamIndex=1;teamIndex<=teams.size();teamIndex+=2) {
Match match=new Match(teams[teamIndex-1],teams(teamIndex)];
matches.add(match);
}
} else {
List<Team> winners=new ArrayList<Team>();
for (Match match:getMatches(round-1)) {
winners.add(match.selectWinner());
}
return getMatches(1,winners);
}
}
}
From this structure you can then derive the necessary gui components to make the selection dynamic and let the GUI components take their values from the Tournament, Match and Team classes.

tooltip hover just works sometimes

I am trying to implement a Tooltip in a loop, for displaying some words after hovering over a rectangle. The problem is that, the words just show up once in a while (sometimes even without a delay, sometimes not even after waiting for a minute). When I put the tooltip statement outside of the loop the words always show up (with a short delay), but I need the statement inside. Any idea what causes the problem?
for (int i = 0; i < finalList.size(); i++) {
for (int j = 0; j < names.getName().size(); j++) {
Label number = new Label(" " + finalList.get(i).get(j).getNamesFrequencySection());
Rectangle rec = new Rectangle();
rec.setY(j * 20 + 20);
rec.setX(i * 80 + 100);
rec.setWidth(80);
rec.setHeight(20);
if (finalList.get(i).get(j).getNamesFrequencySection() == 0) {
rec.setFill(Color.rgb(255, 255, 255));
}
else {
rec.setFill(Color.rgb(250 - (3 * finalList.get(i).get(j).getNamesFrequencySection()), 0, 0));
}
number.setLayoutY(j * 20 + 20);
number.setLayoutX(i * 80 + 130);
root.getChildren().add(rec);
root.getChildren().add(number);
Tooltip.install(rec, new Tooltip(finalList.get(i).get(j).getMostCommon().replaceAll("[^a-zA-Z ]","")));
}
}
NamesFrequency is an Integer and getMostCommon() is the String of the words.

Scrollable gridpane in javafx

im trying to add multiply VBox to a gridpane (called refPane in the following codesnippet) which is inside a scrollpane.
int columnIndex = 0;
int rowIndex = 0;
int boxWidth = windowWidth/ITEMS_PER_ROW;
int boxHeight = windowHeight/ITEMS_PER_COLUMN;
for(int i=0; i<items.size(); i++){
VBox vBox = new VBox();
vBox.setPrefWidth(boxWidth);
vBox.setPrefHeight(boxHeight);
Label label1 = new Label();
label1.setText("ImgPlaceholder");
label1.setPrefWidth(boxWidth);
label1.setPrefHeight(boxHeight / 100 * 70);
vBox.getChildren().add(label1);
Label label2 = new Label();
label2.setText("Description");
label2.setPrefWidth(boxWidth);
label2.setPrefHeight(boxHeight / 100 * 30);
label2.setPadding(new Insets(0,0,0, 10));
vBox.getChildren().add(label2);
refPane.add(vBox, columnIndex, rowIndex);
if(columnIndex != 0 && columnIndex % GAMES_PER_ROW == 0){
rowIndex++;
columnIndex = 0;
}else {
columnIndex++;
}
It adds no more then ITEMS_PER_ROW Vboxes in one row and continues in the next row. Also there should be no more rows then ITEMS_PER_COLUM visible.
The problem is, if I add more then ITEMS_PER_ROW * ITEMS_PER_COLUMN to the grid, instead ob beeing scrollable, the vboxes just get smaller in size.
Any Ideas? Thanks in advance.
Chances are javafx is prioritizing shrinking the VBox's over expanding your grid pane. Try setting the minHeight of each VBox, to be equal to its prefHeight to keep them from shrinking vertically.

JavaFX dynamic gridpane row height adjustment

I am currently working on a project, and I cannot find just any solution. I want a Gridpane row height to be dynamically counted from the width of one specific column inside that row. The whole Gridpane must be resizable and the rest of the available space should take another row below so the aspect ratio of that one cell is preserved according to the element inside that cell. Any ideas boys?
click for a pic
public class GridPaneControl extends GridPane {
private int index;
private Label startLabel;
private Label endLabel;
private HBox inputCellsContainer;
private HBox outputCellsContainer;
private Button inputOutputNextButton;
private Button inputOutputPreviousButton;
public GridPaneControl() {
setGridLinesVisible(true);
initialization();
ColumnConstraints cc0 = new ColumnConstraints();
cc0.setMaxWidth(Double.MAX_VALUE);
cc0.setHgrow(Priority.NEVER);
ColumnConstraints cc1 = new ColumnConstraints();
cc1.setMaxWidth(Double.MAX_VALUE);
ColumnConstraints cc2 = new ColumnConstraints();
cc2.setMaxWidth(Double.MAX_VALUE);
cc2.setHgrow(Priority.NEVER);
RowConstraints rc0 = new RowConstraints();
rc0.setVgrow(Priority.ALWAYS);
rc0.setFillHeight(false);
rc0.setMaxHeight(Double.MAX_VALUE);
RowConstraints rc1 = new RowConstraints();
rc1.setVgrow(Priority.ALWAYS);
rc1.setFillHeight(false);
rc1.setMaxHeight(Double.MAX_VALUE);
RowConstraints rc2 = new RowConstraints();
rc2.setVgrow(Priority.NEVER);
RowConstraints rc3 = new RowConstraints();
rc3.setVgrow(Priority.ALWAYS);
getColumnConstraints().addAll(cc0, cc1, cc2);
getRowConstraints().addAll(rc0, rc1, rc2, rc3);
}
private void initialization() {
inputCellsContainer = new HBox(0);
outputCellsContainer = new HBox(0);
GridPane.setValignment(inputCellsContainer, VPos.BOTTOM);
GridPane.setValignment(outputCellsContainer, VPos.TOP);
inputOutputPreviousButton = new Button("<<");
inputOutputPreviousButton.maxWidth(Double.MAX_VALUE);
GridPane.setHgrow(inputOutputPreviousButton, Priority.NEVER);
GridPane.setVgrow(inputOutputPreviousButton, Priority.NEVER);
GridPane.setMargin(inputOutputPreviousButton, new Insets(5));
inputOutputNextButton = new Button(">>");
inputOutputNextButton.maxWidth(Double.MAX_VALUE);
GridPane.setHgrow(inputOutputNextButton, Priority.NEVER);
GridPane.setVgrow(inputOutputNextButton, Priority.NEVER);
GridPane.setMargin(inputOutputNextButton, new Insets(5));
for (int i = 0; i < 32; i++) {
InputOutputCell cellIn = new InputOutputCell(String.format("%02X", i), Color.AQUA, 0);
InputOutputCell cellOut = new InputOutputCell(String.format("%02X", i), Color.BEIGE, 1);
HBox.setHgrow(cellIn, Priority.ALWAYS);
HBox.setHgrow(cellOut, Priority.ALWAYS);
inputCellsContainer.getChildren().add(cellIn);
outputCellsContainer.getChildren().add(cellOut);
}
GridPane.setHgrow(inputCellsContainer, Priority.ALWAYS);
GridPane.setHgrow(outputCellsContainer, Priority.ALWAYS);
GridPane.setVgrow(inputCellsContainer, Priority.ALWAYS);
GridPane.setVgrow(outputCellsContainer, Priority.ALWAYS);
startLabel = new Label("0");
endLabel = new Label("31");
GridPane.setHalignment(startLabel, HPos.LEFT);
GridPane.setHalignment(endLabel, HPos.RIGHT);
this.add(inputOutputPreviousButton, 0, 0, 1, 2);
this.add(inputCellsContainer, 1, 0);
this.add(outputCellsContainer, 1, 1);
this.add(inputOutputNextButton, 2, 0, 1, 2);
this.add(startLabel, 1, 2);
this.add(endLabel, 1, 2);
}
public int getIndex() {
return index;
}
private class InputOutputCell extends StackPane {
#FXML
Text text;
#FXML
Rectangle rectangle;
public InputOutputCell(String text, Color color, int type) {
setMinSize(0, 0);
this.text = new Text(text);
rectangle = new Rectangle();
if (type == 0) {
rectangle.widthProperty().bind(inputCellsContainer.widthProperty().divide(32));
rectangle.heightProperty().bind(inputCellsContainer.widthProperty().divide(32));
} else {
rectangle.widthProperty().bind(outputCellsContainer.widthProperty().divide(32));
rectangle.heightProperty().bind(outputCellsContainer.widthProperty().divide(32));
}
rectangle.maxWidth(Double.MAX_VALUE);
rectangle.maxHeight(Double.MAX_VALUE);
rectangle.setArcHeight(10);
rectangle.setArcWidth(10);
rectangle.setFill(color);
rectangle.setStroke(Color.BLACK);
getChildren().addAll(rectangle, this.text);
}
public void setText(String text) {
this.text.setText(text);
}
public String getText() {
return this.text.getText();
}
}
}
I want cells 1,0 and 1,1 to be resizable and clearly by increasing their width the height of row 0 and 1 should not be increasing equally. If there should be any height left I want the row 3 to take it because row 2 should not grow to height at all.
You did a fair bit of explanation (which is good) but it is complex enough to make me read 10 times, and I think I understood 70% of it. The most puzzling part is this line:
the rest of the available space should take another row below so the aspect ratio of that one cell is preserved according to the element inside that cell.
Not sure which "cell" you are referrring to, and what you mean by keeping aspect ratio.
Now for the actual answer, I think the most obvious one that I can see is that you have given rows 0 and 1 ALWAYS priority for VGrow.
You have 3 rows that has ALWAYS VGrow, and what the GridPane will do is to give all children to whatever space that they preferred to have, then distribute all the "leftover" spaces to rows with ALWAYS. That is why the 3 gaps in your image has the same height.

Add plus/minus buttons to table cell LibGDX

I am looking for a way to add plus and minus buttons to table cells in LibGDX.
I'm trying to do something like this image:
I noticed that the uiskin files I'm using contain plus and minus buttons (right next to the checkbox) which will do just fine (uiskin.png).
Any tips on how I can add those to my table and make it so they increase/decrease the integer value in that table cell?
I've added a rudimentary sample code with an indication of what I want to do:
#Override
public void create() {
Stage stage = new Stage();
Skin skin = new Skin(Gdx.files.internal("skins/uiskin.json"));
Table table = new Table(skin);
table.setPosition(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2);
for(int i = 0; i < 10; i++){
table.add(Integer.toString(i)); //Somehow add plus/minus buttons left
//and right that increase/decrease the integer value
table.row();
}
stage.addActor(table);
Gdx.input.setInputProcessor(stage);
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
This is just some example code, but if it works here I will certainly be able to figure it out.
Thanks
First, you need an array to store your number values. It's not good practice to use your View (table cells) to store your Model (number values), and besides, Label cells store a String, not an integer, so it would be inconvenient.
private int[] tableData = new int[10];
Now you need a button that can be associated with a row in your data. Here's one way Create a subclass of ImageButton that can take a row number and mode (subtract or add).
public class IncrementButton extends ImageButton {
public final int rowNumber;
public final boolean decrement;
public IncrementButton (Skin skin, String styleName, int rowNumber, boolean decrement){
super(skin, styleName);
this.rowNumber = rowNumber;
this.decrement = decrement;
}
}
The plus and minus images in the uiskin atlas you're using are called "tree-minus" and "tree-plus". You need an ImageButton style for each of these that uses one of these as the imageUp property. (imageUp is used as a default if you don't define images for other states such as imageDown.) You can add these to uiskin.json:
com.badlogic.gdx.scenes.scene2d.ui.ImageButton$ImageButtonStyle: {
plus: { down: default-round-down, up: default-round, imageUp: tree-plus },
minus: { down: default-round-down, up: default-round, imageUp: tree-minus }
},
Now you can create a ChangeListener for these buttons that will modify the numbers in the data and update the table accordingly. And then set it all up:
Stage stage = new Stage();
Skin skin = new Skin(Gdx.files.internal("skins/uiskin.json"));
final Table table = new Table(skin);
final Label[] labels = new Label[tableData.length]; //keep references to the labels for updating them.
final ChangeListener incrementListner = new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
IncrementButton incrementButton = (IncrementButton)actor;
int row = incrementButton.rowNumber;
tableData[row] += incrementButton.decrement ? -1 : 1;
labels[row].setText(Integer.toString(tableData[row]));
}
};
table.setPosition(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2);
for(int i = 0; i < tableData.length; i++){
IncrementButton decrementButton = new IncrementButton(skin, "minus", i, true);
decrementButton.addListener(incrementListner);
IncrementButton incrementButton = new IncrementButton(skin, "plus", i, false);
incrementButton.addListener(incrementListner);
table.add(decrementButton);
labels[i] = table.add(Integer.toString(i)).getActor();//Add number label and keep reference to it in labels array for the change listener to look up
table.add(incrementButton);
table.row();
}
stage.addActor(table);
Gdx.input.setInputProcessor(stage);
If you want to set min and max values for the data, then you need some arrays for the plus buttons and minus buttons (similar to the labels array) so the change listener can look up and disable/enable the buttons accordingly when a limit is hit.

Categories

Resources