Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Beschäftige mich gerade mit JavaFX und hab hierzu gleich zu Anfang eine Verstänisfrage.
Folgendes habe ich mir gedacht:
1. Habe eine PrimaryStage erstellt und lege auf diesen die 1. Scene (welche Menüs, Buttons usw. schon enthält)
2. Nun wollte ich zur Laufzeit eine Scene erstellen die je nach Einstellung einen anderen Aufbau hat (Anzahl Labels usw.) Dies sollte dann auf die 1. Scene gelegt werden.
Ich wollte eine VBox erstellen, die Label und Checkboxen enthält. Nun soll beim Start je Einstellung 2-10 VBoxen erstellt werden.
Gehe ich recht in der Annahme das es so gemacht wird, oder wie bekomme ich das bei der Laufzeit geändert?
Das kommt drauf an was du unter "je nach Einstellung" verstehst. Angenommen es gibt 3 Einstellungsmöglichkeiten und für jede der 3 Einstellungsmöglichkeiten soll es eine andere Ansicht geben. Dann würde ich diese Ansichten nicht dynamisch zur Laufzeit sondern per FXML Datei erstellen.
Wenn es 22 Möglichkeiten gibt dann könnte man schon an die dynamische Erstellung denken.
Wenn ich 3 als Anzahl nutze, dann sollt der Name dreimal unter einander und danaben die Radio Buttons.
Am Ende will ich abfragen welcher Nutzer welchen Radio Button geklickt hat.
Habe mal das ganze bildlich veranschaulicht.
Auf das Mainwindow soll in den roten Rahmen die scene2 gebracht werden. Je nach gewählter Anzahl
Mainwindow:
Scene2:
Da gibts nicht viel zu erklären: der wesentliche Code ist in showRows(). Der entfernt aus einer Box erstmal alles und fügt dann n Elemente hinzu. Die Elemente habe ich als RadioScene "gefaked". Statt den RadioScene-Objekten sollte man den Spaß auch aus einer FXML laden können. Anzumerken wäre noch, dass ich mit Sicherheit nicht die Referenz bin, was JavaFX betrifft - da halte Dich besser an @Robat
Ich hab mal ein kleines Beispiel zusammengeschustert so wie man es machen *könnte* .. das ganze entspricht hoffentlich dem, was du brauchst:
(1) Es gibt keine Anzeige, bei der man über ein Textfeld input angeben kann, wie viele Zeilen angezeigt werden sollen. Über einen Button wird man dann zur nächsten Ansicht mit der ListView geleitet
Java:
public class MainController {
/** Textfeld über welches man die Anzahl der Zeilen angibt **/
@FXML private TextField input;
@FXML
public void onButtonPressed(ActionEvent event) {
if(!input.getText().isEmpty()) {
int value = Integer.parseInt(input.getText());
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/javafxtomexample/second.fxml"));
Stage stage = (Stage)((Node)event.getSource()).getScene().getWindow();
try {
// eigene ControllerFactory um die eingegebene Zeilenanzahl in den Controller
// zu übergeben.
loader.setControllerFactory(e -> new SecondController(value));
stage.setScene(new Scene(loader.load()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(2) In der ListView werden Model Objekte dargestellt. Die Model-Klasse besitzt 2 Attribute. Einen Namen und ein Array für die RadioButton Namen. Properties dienen in JavaFX dazu eine "automatische" Synchronisation zwischen View und Model-Klasse herzustellen. Dazu "bindet" man zB die nameProperty des Models an eine TextProperty eine Labels. Ändert sich der Text im Label ändert sich auch automatisch der Name im Model.
Java:
public class Model {
private StringProperty name;
private StringProperty[] radioButtonNames;
public Model(String name, String[] radioButtonNames) {
this.name = new SimpleStringProperty(name);
this.radioButtonNames = Arrays.stream(radioButtonNames).map(SimpleStringProperty::new).toArray(SimpleStringProperty[]::new);
}
public StringProperty nameProperty() {
return name;
}
public int getSize() {
return radioButtonNames.length;
}
public String getName() {
return name.get();
}
public StringProperty radioButtonNameProperty(int index) {
return radioButtonNames[index];
}
}
(3) DIe ListView wird dann mit "Dummy-Data" befüllt. Bei dir wären das dann sicherlich sinnvolle Daten Außerdem wird eine Custom Cell Factory gesetzt (siehe Punkt 4). Das dient dazu für jede Zeile das Label + die 4 RadioButtons anzuzeigen
Java:
public class SecondController implements Initializable {
private int rows;
private ObservableList<Model> data;
@FXML private ListView<Model> listView;
public SecondController(int rows) {
this.rows = rows;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
data = FXCollections.observableArrayList();
// dummy data hinzufügen
// je nach dem wie viele Zeilen angezeigt werden sollen
for (int i = 0; i < rows; i++) {
data.add(new Model("Label#" + (i + 1),
new String[]{"Antwort " + (i + 1) + ".1",
"Antwort " + (i + 1) + ".2",
"Antwort " + (i + 1) + ".3",
"Antwort " + (i + 1) + ".4"}));
}
listView.setItems(data);
listView.setCellFactory(listView -> new ModelListViewCell());
}
}
(4) Der spannende Teil ist eigentlich die eigens definierte ListCell, die jeweils ein Model darstellt. Dort wird dem Label der passende Text zugeordnet und die RadioButtons erstellt. Außerdem hab ich einfach mal unterstellt, dass in jeder Zeile immer nur ein RadioButton gleichzeitig aktiviert sein darf. Dazu habe ich alle 4 RadioButtons in eine ToggleGroup gepackt. Diese sorgt dafür, dass alle anderen RadioButtons wieder deselektiert werden, sobald du einen RadioButton anklickst
Java:
public class ModelListViewCell extends ListCell<Model> {
@FXML
private HBox root;
@FXML
private Label labelName;
private FXMLLoader loader;
@Override
protected void updateItem(Model item, boolean empty) {
super.updateItem(item, empty);
// wichtig, damit leere Zellen auch wirklich leer sind
if(empty || item == null) {
setText(null);
setGraphic(null);
} else {
if(loader == null) {
// hier würdest du deine Scene2.fxml laden und befüllen
loader = new FXMLLoader(getClass().getResource("/javafxtomexample/listview_item.fxml"));
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
// hier siehst du das oben angesprochene Binding
labelName.textProperty().bind(item.nameProperty());
ToggleGroup toggleGroup = new ToggleGroup();
for(int i = 0; i < item.getSize(); i++) {
RadioButton button = new RadioButton();
button.textProperty().bind(item.radioButtonNameProperty(i));
button.setToggleGroup(toggleGroup);
HBox.setMargin(button, new Insets(5, 5, 5, 5));
root.getChildren().add(button);
}
// hier wird einfach ein Listener hinzugefügt der ausgelöst wird, sobald sich die Auswahl der RadioButtons ändert
toggleGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) -> {
String val = ((RadioButton)toggleGroup.getSelectedToggle()).getText();
System.out.println(item.getName() + " switched to " + val);
});
setText(null);
setGraphic(root);
}
}
}
Ich danke dir vielmals für den Code. Bin noch dabei das ganze zu verstehen.
Kennst du zufällig gute Lektüre und/oder Videos wo man noch paar Infos mehr bekommen kann? Will ja nicht bei jedem kleinen Problemchen im Forum nachfragen müssen.
Angefangen hab ich mit dem Tutorial von Oracle ( https://docs.oracle.com/javafx/2/get_started/jfxpub-get_started.htm )
Ansonsten kann ich dir nur das empfehlen, was du eh gerade schon machst: Probier dich aus und stell Fragen, wenn du was nicht verstehst. Dazu ist so ein Forum ja da.
Der obige Quelltext ist ja doch ein wenig kompliziert für mich zu verstehen, versuche aber durchzuschauen.
Andere Frage zum GUI Aufbau im Bezug zu JavaFX:
Kann ich eine Stage nehmen, darauf eine Scene legen die nur ein Menü und Statusleiste hat.
Anschliesend lege ich eine Scene für den linken Bereich und eine Scene für den rechten Bereich über die MainScene?
Nicht ganz. Du musst dir für JavaFX GUIs prinzipiell erstmal 3 Begriffe merken:
- Stage: Darunter kannst du dir das eigentliche Fenster vorstellen (wie das JFrame bei Swing - falls du damit gearbeitet hast.) Im Beispiel der realen Welt wäre die Stage bei einem Theaterstück deine leere Bühne ohne Hintergrund, Darsteller, ..
- Scene: Eine Scene stellt den aktuellen Fensterinhalt dar. Die Scene wäre also alles was zum Zeitpunkt X auf der Bühne passiert / zu sehen ist (Hintergrund, Darsteller, ..)
- Nodes: Das sind Objekte in einer Scene - im Bühnenbeispiel wäre das also ein einzelner Darsteller zB.
Du kannst zur Laufzeit nun beliebig zwischen Scenes wechseln. Eine Anwendung kann also problemlos mehrere Scenes haben .. es kann aber immer nur eine angezeigt werden. So ist es ja auch bei einer Bühne. Ein Theaterstück hat immer mehrere Szenen .. aber du siehst immer nur eine davon gleichzeitig.
Also ist eine *.fxml immer nur eine Scene, oder wie muss ich dies verstehen?
Das mit den 3 Begriffen hab ich verstanden, nur hab ich eben nicht gewusst das nur eine Scene dargestellt werden kann. Hatte da einen Verständnisproblem.
Eine FXML Datei ist erstmal nur die Beschreibung deiner grafischen Objekte. Wenn du diesen über den FXMLLoader lädst werden im Hintergrund die Nodes erstellt. Eine Scene wird erst dan draus, wenn du mittels new Scene() eine erstellst und dort das Root-Element (in dem meisten Fällen zB ein Layout wie HBox, AnchorPane, .. ) übergibst.
Es ist aber durchaus Möglich eine FXML Datei in eine andere einzubinden. Daher wäre es falsch zu sagen eine FXML-Datei *ist* eine Scene
Also könnte ich eine Main.fxml erstellen die nur ein Menü enthält. Dann eine LinkeSeiteLayout.fxml einbinden und eine Rechteseitelayout.fxml ?
Wenn ja, dann ist das was ich vorhin gemeinte hatte. Hab mich da nur falsch ausgedrückt.
Habe vergessen zu sagen das ich die FXML Dateien mit dem SceneBuilder erstelle. Kann ich dann da auch einfach im Code das ändern? Oder spinnt dann der SceneBuilder?
Wir würdest du das machen wenn nur die linke Seite sich dynamisch ändern kann?
Bevor wir beide hier noch ewig lang rumraten. Versuch mal genau zu beschreiben was du unter "dynamisch" Verändern verstehst. Am Besten mal an einem konkreten Beispiel. Was genau ändert sich - wie stellst du dir das vor
Okay. Bei dem Beispiel macht es aber mEn keinen Sinn FXML Dateien zu schachteln. Das kannst du wunderbar in eine Datei packen.
Das ganze sind dann auch keine verschiedenen Scenes. Du hast eine Scene die dein Label, Textfeld, Button und eben die X Anzahl an Reihen darstellt.
Jedes mal wenn du auf den Button klickst werden die Reihen aktualisiert.
Du hast auf jeden Fall 2 Anregungen für das Problem bekommen. Entweder machst du es wie @mihe7 gezeigt hat mit einer VBox (dort musst du die Combobox eben durch deinen Button/Textfeld ersetzen) oder wie ich gezeigt habe mit einer ListView (das ganze dann eben ohne neue Scene). Welche Variante du wählst ist eigentlich egal.
Alles in die Main oder in einer FXML? Bin immer ausgegangen das man alles was GUI ist in einer FXML auslagert damit es besser wartbar ist. Haben irgendwann mal gelernt das sich dann ein "Designer" ums aussehen kümmern kann und ich nur die Logik entwickeln muss.
Ich meine alles in eine FXML Datei. Für dein Vorhaben bräuchtest du (grob skizziert)
- eine Main.java Klasse, die deine Applikation startet + die FXML Datei lädt
- eine main.fxml Datei die deine UI-Komponenten deklariert
- einen MainController.java welche für die Logik zuständig ist (Auf Button Klick reagieren)
Und gleich hab ich die nächste Frage. Wie im gif zu sehen möchte ich auf Klick zum nächsten Fenster und wieder zurück wechseln.
Würde jetzt 2 Scenen erstellen (je 1 für die Fenster). Bisher verstehe ich folgendens: Bei jedem Klick lade ich die neue FXML? Oder mache ich alles in einer FXML? Wie verhält es sich mit dem Menü? Jedes mal neu erstellen?
Bisher verstehe ich folgendens: Bei jedem Klick lade ich die neue FXML? Oder mache ich alles in einer FXML? Wie verhält es sich mit dem Menü? Jedes mal neu erstellen?
Für den Anfang spricht erstmal nichts dagegen die Ansichten jedes mal neu zu laden (über den FXMLLoader) .. später - wenn du dann etwas sicherer bist - kann man natürlich anfangen die geladenen Ansichten zu cachen und sie nur einmalig zu laden.