I think my question just boils down to this: Suppose that I have a view at the top left of the main layout, taking up some amount of space. I want to programmatically place a table of radio buttons. The table should have its top aligned with the bottom of that other view. I want the table to fill the width of the main layout. And I want its height to be as maximal as possible, on the device's screen, within those constraints.
Then with the dimensions of the table constrained like that, I want to add radio buttons into the first column until it is full, and then start filling the second column, and so on, until there are no remaining radio buttons to place. It seems like the main challenges are setting the constraints of the table and then knowing how many radio buttons can be added in a column before breaking and starting on the next column.
How do I do that?
In case more details are helpful, I'm trying to make an app which allows the user to select a few checkboxes, each of which determines a set of "quiz words". Then a word is selected from the set at random, but all of the possible answers are put into radio buttons, and the buttons put into a table.
The radio buttons should be put into rows and columns. I need to figure out how to stop entering buttons into a column when the column becomes "full". Presumably that needs to be handled by layout constraints and parameters, but that's the part I don't really understand how to do.
In order to try to keep this example minimal, I've tried to delete a lot of the details and just describe what's happening there in comments. If any part of it is helpful I can always put the code back.
package com.example.japanesequiz;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
String[] ah_monographs = {"あah", "かka", "さsa", "たta", "なna",
"はha", "まma", "やya", "らra", "わwa"};
String[] ii_monographs = {"いii", "きki", "しshi", "ちti", "にni",
"ひhi", "みmi", "", "りri", "ゐwi"};
String[][] mono_strings = {ah_monographs, ii_monographs};
Map<String, String[]> nameToRow = new HashMap<>();
SharedPreferences sps;
ConstraintLayout main_layout;
Button hirButton;
TextView questionText;
TableLayout radioButtonTable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sps = getSharedPreferences("Syllograms", MODE_PRIVATE);
main_layout = findViewById(R.id.layout);
hirButton = findViewById(R.id.hirButton);
questionText = findViewById(R.id.questionText);
setTheCheckboxes();
setTheRadioTable();
hirButton.setOnClickListener(new View.OnClickListener() {
// a bunch of stuff to make it so that, when the button is clicked,
// the program scans the checkboxes, to determine a collection of quiz
// words and their answers. One quiz word is selected at random, but
// all possible answers are put into radio buttons, and the radio
// buttons are put into the "radio table".
});
}
public void setTheCheckboxes() {
// A bunch stuff to set up the checkboxes, which are laid out in the upper-left corner of the screen.
}
public void setTheRadioTable() {
radioButtonTable = new TableLayout(this);
for (int i = 0; i < ah_monographs.length; i++) {
TableRow tableRow = new TableRow(this);
tableRow.setLayoutParams(new TableRow.LayoutParams(
TableRow.LayoutParams.MATCH_PARENT,
TableRow.LayoutParams.WRAP_CONTENT));
radioButtonTable.addView(tableRow);
}
ConstraintSet csRadioTable = new ConstraintSet();
csRadioTable.connect(radioButtonTable.getId(), ConstraintSet.TOP,
questionText.getId(), ConstraintSet.BOTTOM);
csRadioTable.connect(radioButtonTable.getId(), ConstraintSet.BOTTOM,
hirButton.getId(), ConstraintSet.TOP);
csRadioTable.connect(radioButtonTable.getId(), ConstraintSet.LEFT,
main_layout.getId(), ConstraintSet.LEFT);
csRadioTable.connect(radioButtonTable.getId(), ConstraintSet.RIGHT,
main_layout.getId(), ConstraintSet.RIGHT);
csRadioTable.constrainHeight(radioButtonTable.getId(), ConstraintSet.WRAP_CONTENT);
csRadioTable.constrainWidth(radioButtonTable.getId(), ConstraintSet.WRAP_CONTENT);
csRadioTable.applyTo(main_layout);
}
}
Related
I am dynamically creating GridLayout with 9 buttons. However the horizontal orientation that I set for this GridLayout does not work when I change emulator to horizontal position. Here is my code -
import android.os.Bundle;
import android.app.Activity;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
public class MainActivity extends Activity {
GridLayout gl;
Button[] buttons;
int item;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gl = new GridLayout(MainActivity.this);
gl.setLayoutParams(new LayoutParams
(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
gl.setOrientation(GridLayout.HORIZONTAL);
gl.setColumnCount(3);
gl.setRowCount(3);
buttons = new Button[9];
for(int i=0;i<9;i++)
{
buttons[i] = new Button(MainActivity.this);
LayoutParams params = new TableRow.LayoutParams(WRAP_CONTENT, WRAP_CONTENT,1f);
buttons[i].setLayoutParams(params);
buttons[i].setHeight(WRAP_CONTENT);
buttons[i].setWidth(WRAP_CONTENT);
buttons[i].setGravity(Gravity.CENTER);
buttons[i].setId(i+1);
int s = buttons[i].getId();
buttons[i].setText(Integer.toString(s));
buttons[i].setTextSize(25);
buttons[i].setWidth(352);
buttons[i].setHeight(552);
buttons[i].setPadding(50,50,50,50);
gl.addView(buttons[i]);
}
setContentView(gl);
}
}
Since I am not using activity_main.xml to create GridLayout I cannot set orientation in XML. Hence setting it in MainActivity.java at
gl.setOrientation(GridLayout.HORIZONTAL);
Also for each button dynamically created I cannot set layout_gravity to fill. Hence doing like this -
buttons[i].setGravity(Gravity.CENTER);
I want to bind a CheckMenuItem's selectedProperty to another observable value, like cmi.selectedProperty().bind(myObs). However, this is not possible, since the framework sets the selection property when the check menu item is clicked (see line 1394 of ContextMenuContent.java).
Is there a way to intercept the click—so that I can do my own custom processing—and still bind the selection property to another observable?
I suppose I'm thinking of the click as a request to update some state. The user clicks the menu item, then the program attempts to change some state accordingly, and the selection changes if the state successfully updated. Under 'normal' conditions, the check should toggle upon every click; however, if something bad happens, I'd prefer that the check doesn't toggle and instead reflects the true state of the program.
One way to do this (without getting into writing a skin for the menu item) is to roll your own menu item with a graphic. You can just use a region for the graphic and steal the CSS from the standard modena stylesheet. Then bind the visible property of the graphic to the observable value, and toggle the observable value in the menu item's action handler:
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class VetoableMenuItemWithCheck extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar() ;
Menu choices = new Menu("Choices");
// observable boolean value to which we're going to bind:
BooleanProperty selected = new SimpleBooleanProperty();
// graphic for displaying checkmark
Region checkmark = new Region();
checkmark.getStyleClass().add("check-mark");
// bind visibility of graphic to observable value:
checkmark.visibleProperty().bind(selected);
MenuItem option = new MenuItem("Option", checkmark);
choices.getItems().add(option);
Random rng = new Random();
// when menu item action occurs, randomly fail (with error alert),
// or update boolean property (which will result in toggling check mark):
option.setOnAction(e -> {
if (rng.nextDouble() < 0.25) {
Alert alert = new Alert(AlertType.ERROR, "I'm sorry Dave, I'm afraid I can't do that", ButtonType.OK);
alert.showAndWait();
} else {
selected.set(! selected.get());
}
});
menuBar.getMenus().add(choices);
root.setTop(menuBar);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add("check-menu.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and
check-menu.css:
.check-mark {
-fx-background-color: -fx-mark-color;
-fx-shape: "M0,5H2L4,8L8,0H10L5,10H3Z";
-fx-scale-shape: false;
-fx-padding: 0em 0.11777em 0em 0em;
}
There may be a simpler approach, but this seems not too bad.
A version for a vetoable radio menu item could follow the same basic idea, but with
ObjectProperty<MenuItem> selectedItem = new SimpleObjectProperty<>();
and then for each menu item do
checkmark.visibleProperty().bind(selectedItem.isEqualTo(option));
option.setOnAction(e -> {
if (successful()) {
selectedItem.set(option);
}
});
To get an idea of what I want
When the textfield is clicked, the dropdown appears with suggestions that are filtered out as the user types in the text field. The height of the box should also adjust real-time to either contain all of the items, or a maximum of 10 items.
I managed to get this somewhat working using a ComboBox, but it felt a bit rough around the edges and it didn't seem possible to do what I wanted (The dropdown doesn't resize unless you close it and re-open it).
New idea, have a text field and then show a VBox of buttons as the dropdown. The only problem is that I don't know how to position the dropdown so that it doest stay in the noral flow so it can overlay any exisiting elements below the text field. Any ideas?
Please consider this Example, you can take the idea and apply it to your project.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class SearchFormJavaFX extends Application{
#Override
public void start(Stage ps) throws Exception {
String[] options = {"How do I get a passport",
"How do I delete my Facebook Account",
"How can I change my password",
"How do I write some code in my question :D"};
// note that you don't need to stick to these types of containers, it's just an example
StackPane root = new StackPane();
GridPane container = new GridPane();
HBox searchBox = new HBox();
////////////////////////////////////////////////////
TextField text = new TextField();
// add a listener to listen to the changes in the text field
text.textProperty().addListener((observable, oldValue, newValue) -> {
if(container.getChildren().size()>1){ // if already contains a drop-down menu -> remove it
container.getChildren().remove(1);
}
container.add(populateDropDownMenu(newValue, options),0,1); // then add the populated drop-down menu to the second row in the grid pane
});
// those buttons just for example
// note that you can add action listeners to them ..etc
Button close = new Button("X");
Button search = new Button("Search");
searchBox.getChildren().addAll(text,close,search);
/////////////////////////////////////////////////
// add the search box to first row
container.add(searchBox, 0, 0);
// the colors in all containers only for example
container.setBackground(new Background(new BackgroundFill(Color.GRAY, null,null)));
////////////////////////////////////////////////
root.getChildren().add(container);
Scene scene = new Scene(root, 225,300);
ps.setScene(scene);
ps.show();
}
// this method searches for a given text in an array of Strings (i.e. the options)
// then returns a VBox containing all matches
public static VBox populateDropDownMenu(String text, String[] options){
VBox dropDownMenu = new VBox();
dropDownMenu.setBackground(new Background(new BackgroundFill(Color.GREEN, null,null))); // colors just for example
dropDownMenu.setAlignment(Pos.CENTER); // all these are optional and up to you
for(String option : options){ // loop through every String in the array
// if the given text is not empty and doesn't consists of spaces only, as well as it's a part of one (or more) of the options
if(!text.replace(" ", "").isEmpty() && option.toUpperCase().contains(text.toUpperCase())){
Label label = new Label(option); // create a label and set the text
// you can add listener to the label here if you want
// your user to be able to click on the options in the drop-down menu
dropDownMenu.getChildren().add(label); // add the label to the VBox
}
}
return dropDownMenu; // at the end return the VBox (i.e. drop-down menu)
}
public static void main(String[] args) {
launch();
}
}
What you're trying to do has already been implemented, and is included in ControlsFx. It's open source, and I think it would suit you need. It looks some what like this
You can even add custom nodes to it, so that cross can be done too.
public void pushEmails(TextField Receptient) {
ArrayList<CustomTextField> list = new ArrayList<>();
for (int i = 0; i < Sendemails.size(); i++) {
CustomTextField logo=new CustomTextField(Sendemails.get(i));
ImageView logoView=new ImageView(new Image("/Images/Gmail.png"));
logo.setRight(logoView);
list.add(logo);
}
TextFields.bindAutoCompletion(Receptient, list);
}
In my javafx application I want to create a scene where I can show the usernames of all the users in my database.
Precisely, I want to show a list of labels where every label get a username.
(the number of labels depend on the number of users).
Note: I can do this in java with a list and a foreach loop, but this is the first time that I work with javafx and I want to know how to create a loop of graphic component.
Thanks.
Here are a couple of alternatives, one sample is just looping and adding new labels to the children of a layout pane, the other is using the in-built ListView component. There are other alternatives of course. Which you choose to use will depend upon the functionality you need to achieve.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class UserDisplay extends Application {
#Override public void start(Stage stage) {
String[] users = { "Huey", "Dewey", "Louie" };
VBox layout = new VBox(10);
// ALTERNATIVE 1: add labels in a loop.
for (String user: users) {
Label userLabel = new Label(user);
layout.getChildren().add(userLabel);
}
// ALTERNATIVE 2: use the built-in ListView component.
ListView<String> listView = new ListView<>(
FXCollections.observableArrayList(users)
);
layout.getChildren().add(listView);
layout.setPadding(new Insets(10));
layout.setPrefSize(100,200);
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have an ObservableMap<String, ObservableSet<String>>.
I would like to create a UI which has a comboBox and a ListView. The comboBox is populated by the keys of the map. Selecting one key from the map would then populate the ListView with the contents of the Set that is mapped to by that key.
In the past I have handled making a ListView for an ObservableSet by creating a second data structure, an ObservableList, and adding a ChangeListener to the set that updates the ObservableList so that it mirrors the set.
However in this case I don't have just one set, but a map of many sets. See my previous question which is similar but simpler: JavaFX: Populate TableView with an ObservableMap that has a custom class for its values
Here is some sample runnable code. It provides most of the functionality I want. However, the ListView doesn't respond to changes in the underlying Map of Sets. In this example, if you select "Vehicles" from the ComboBox and then click the Change Vehicles button, no changes are reflected in the ListView. However if you then select "Colors" and then back to "Vehicles", the ListView is repopulated and you now see the change.
So how would one get the ListView to automatically update itself when the underlying Map of Sets changes? My first guess is that you need to add a Listener to each Set that maintains a mirror of the contents of each Set in an ObservableList. But since this is a Map of Sets, the number of Sets can change and so the number of mirroring Lists will need to change. So I would need to have a collection of ObservableLists, I suppose...? And every time a new element is added to the Map, a new Listener and new ObservableList will need to be constructed.
import java.util.Map;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MapSetView extends Application {
ObservableMap<String, ObservableSet<String>> map = FXCollections.observableHashMap();
ComboBox<String> keysCombo = new ComboBox<>();
ListView<String> valuesList = new ListView<>();
#Override
public void start(Stage stage) throws Exception {
map.put("Vehicle", FXCollections.observableSet("plane", "train", "automobile"));
map.put("Color", FXCollections.observableSet("black","blue","red"));
keysCombo.getItems().clear();
for(Map.Entry<String, ObservableSet<String>> varEntry : map.entrySet()) {
keysCombo.getItems().add(varEntry.getKey());
}
keysCombo.setOnAction( (ActionEvent e) -> {
String selectedName = keysCombo.getSelectionModel().getSelectedItem();
valuesList.setItems(FXCollections.observableArrayList(map.get(selectedName)));
});
Button changeVehicles = new Button("Change Vehicles");
changeVehicles.setOnAction( (ActionEvent e) -> {
map.get("Vehicle").add("boat");
});
// display UI
VBox vBox = new VBox(8);
vBox.getChildren().addAll(keysCombo, valuesList, changeVehicles);
Scene scene = new Scene(vBox, 400, 400);
stage.setScene(scene);
stage.show();
}
}