JavaFX Making an scrollable list of custom components - java

I am working on a project. In this project, I must have a shop that has a huge list of cards. I am also very new to JavaFX.
I made a custom class that inherits pane. It has an image view and some labels to show the name and description of card.
Know my problem is that how should I add them to scene to have an scrollable list of this items? What Components should my Scene have. (I omitted imports in the code below)
CardView.java ---- Custom component that loads an fxml
public class CardView extends Pane {
CardController cardController;
Node view;
public CardView() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("../FXMLFiles/Card.fxml"));
fxmlLoader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> param) {
return cardController = new CardController();
}
});
try {
view = (Node) fxmlLoader.load();
} catch (IOException ex) {
}
getChildren().add(view);
cardController.setNameAndDsc("Card", "This is A card", heroImg);
}
}
CardController.java
public class CardController {
#FXML
private Label name_lbl;
#FXML
private Label dsc_lbl;
#FXML
private ImageView card_img;
public void setNameAndDsc(String name, String dsc, Image img) {
name_lbl.setText(name);
dsc_lbl.setText(dsc);
card_img.setImage(img);
}
public void setName_lbl(Label name_lbl) {
this.name_lbl = name_lbl;
}
public void setDsc_lbl(Label dsc_lbl) {
this.dsc_lbl = dsc_lbl;
}
public void setCard_img(ImageView card_img) {
this.card_img = card_img;
}
}
Card.fxml
Overall View of Card.fxml:
Actually I want to have a huge list of this card that can be scrolled. How should I do that? What Components should I use. I must also note that I have access to JFoenix.

Use a listview. It's a virtual control so it only creates nodes that are in the visual bounds.
You can apply css to make background transparent.

Related

Struggling to get JavaFx app working, objects stop existing?

So i'm working on a pet project, essentially a digitalisation of a do it yourself role playing adventure book.
I switched to using Scenebuilder because of the freedom it allows when crafting a GUI.
I'm having trouble binding the data to the screen. It seems the objects I am calling either stop existing or are not the ones i need.
I am using a SQLite database for my data, and that seems to work fine.
I am using Maven to import the things i need, this also seems to work fine however this requires me to use
public class DdAppLauncher {
public static void main(String[] args) {
DdApp2.main(args);
}
}
into ->
public class DdApp2 extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/mainWindow.fxml"));
stage.setTitle("Deathtrap Dungeon");
stage.setScene(new Scene (root, 800,600));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This brings up the titlescreen that contains a start button which is handled by the following controller.
public class MainController {
LocationsPool loc;
Location currentLocation;
#FXML
private ListView<String> inventory;
#FXML
private Text locationDescription;
#FXML
private Text descrA;
#FXML
private Text descrB;
#FXML
private Text descrC;
#FXML
private Text descrD;
#FXML
private Text descrE;
#FXML
private Text descrF;
#FXML
public void startButtonClicked(ActionEvent event){
try {
System.out.println("Starting game");
Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
// Swap screen
Parent root = FXMLLoader.load(getClass().getResource("/fxml/gameWindow.fxml"));
stage.setScene(new Scene(root,800,600));
} catch (IOException e) {
e.printStackTrace();
}
//Setup
loc = new LocationsPool();
currentLocation = loc.getLocation(1);
System.out.println(currentLocation.getDescription());
locationDescription = new Text(currentLocation.getDescription());
System.out.println(locationDescription.getText());
System.out.println(locationDescription);
}
#FXML
public void handleButtonA(){
System.out.println(currentLocation==null);
}
}
Output on console ->
starting game
The clamour... (works)
The clamour... (works)
On button press ->
True
So it seems the app "forgets" fields when it runs? or is it the controller that stop existing and is remade?
Furthermore when trying to bind data to fields with fx:id, it doesn't seem to link those fields to anything until i press that button.
Am i structuring this all wrong? What am i not getting?
The final product should have the description loaded and all the choices loaded for that location.
Then on a selection should load up a new location and new choices so the text needs to be updated.
Thanks in advance.
Kev

Placeholder for TreeView?

I have a TreeView which is empty at the start and I want to set a placeholder until it is empty. Like the one available for ListView (setPlaceholder())
My first thought was to wrap the TreeView into a BorderPane and just change the center based on the number of elements in the TreeView. The problem is though that I add elements to the TreeView through drag and drop and if I set a label to the center for substituting a placeholder, I won't be able to drag n drop my items in the TreeView anymore. Any suggestions would be much appreciated.
TreeView has no placeholder support - not entirely certain, why not, but could be that an empty tree (whatever that means: null root? root without children? root not showing?) is a rare species.
It's rather simple to implement, though, by following the implementation for a virtualized control that supports it, f.i. TableView. All we need is a custom TreeViewSkin that
manages (creates and adds to the tree's hierarchy, layouts as needed) the placeholder
listens to relevant state of the tree and updates the placeholder's visibilty as appropriate
An example, toggling the emptyness by toggling the tree's root between null/not null via a button:
public class TreeViewWithPlaceholder extends Application {
private static class TreeViewPlaceholderSkin<T> extends TreeViewSkin<T> {
private StackPane placeholderRegion;
private Label placeholderLabel;
public TreeViewPlaceholderSkin(TreeView<T> control) {
super(control);
installPlaceholderSupport();
}
private void installPlaceholderSupport() {
registerChangeListener(getSkinnable().rootProperty(), e -> updatePlaceholderSupport());
updatePlaceholderSupport();
}
/**
* Updating placeholder/flow visibilty depending on whether or not the tree
* is considered empty.
*
* Basically copied from TableViewSkinBase.
*/
private void updatePlaceholderSupport() {
if (isTreeEmpty()) {
if (placeholderRegion == null) {
placeholderRegion = new StackPane();
placeholderRegion.getStyleClass().setAll("placeholder");
getChildren().add(placeholderRegion);
placeholderLabel = new Label("No treeItems");
placeholderRegion.getChildren().setAll(placeholderLabel);
}
}
getVirtualFlow().setVisible(!isTreeEmpty());
if (placeholderRegion != null)
placeholderRegion.setVisible(isTreeEmpty());
}
#Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
if (placeholderRegion != null && placeholderRegion.isVisible()) {
placeholderRegion.resizeRelocate(x, y, w, h);
}
}
private boolean isTreeEmpty() {
return getSkinnable().getRoot() == null;
}
}
private Parent createContent() {
TreeView<String> tree = new TreeView<>() {
#Override
protected Skin<?> createDefaultSkin() {
return new TreeViewPlaceholderSkin<>(this);
}
};
Button toggle = new Button("toggleRoot");
toggle.setOnAction(e -> {
TreeItem<String> root = tree.getRoot();
tree.setRoot(root == null ? new TreeItem<>("root") : null);
});
BorderPane content = new BorderPane(tree);
content.setBottom(toggle);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
//stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TreeViewWithPlaceholder.class.getName());
}

How do I dynamically change the image in an ImageView (JavaFX)?

I'm trying to implement a validation check for the contents of a TextField, displaying the validity as an icon next to it. However it doesn't seem to change the image. Here is the code I have so far, I've stripped out anything not related to the problem I'm experiencing.
Here's the view class:
package mypackage.view;
import mypackage.model.Foo;
// JavaFX imports
public class MyView extends VBox {
private final Foo model;
private final MyPresenter presenter;
HBox tokenValidationbox;
TextField tokentxt;
ImageView isValidimg;
public MyView(Foo model) {
this.model = model;
initFieldData();
layoutForm();
this.presenter = new MyPresenter(model, this);
}
private void initFieldData() {
tokenValidationbox = new HBox();
tokentxt = new TextField();
isValidimg = new ImageView();
}
private void layoutForm() {
tokenValidationbox.getChildren().addAll(tokentxt, isValidimg);
this.getChildren().add(tokenValidationbox);
}
}
And this is the presenter class that contains the logic:
package mypackage.view;
import mypackage.model.Foo;
// JavaFX imports
public class MyPresenter {
private final Foo model;
private final MyView view;
public MyPresenter(Foo model, MyView view) {
this.model = model;
this.view = view;
attachEvents();
}
private void attachEvents() {
view.tokentxt.setOnAction((ActionEvent event) -> {
view.isValidimg.setImage(new Image(validationImage(view.tokentxt.getText())))
});
}
public String validationImage(String token) {
String img = "dialog-error.png";
if(isValid(token)) img = "emblem-default.png";
return getClass().getClassLoader().getResource(img).toExternalForm();
}
private static boolean isValid(String token) {
// snip
}
}
As I understand this should check whether the entered token is valid whenever something is changed in the text field, and then load the corresponding image to display, however the image is not showing up.
emblem-default.png and dialog-error.png are located in the project resources folder and can be loaded statically (i.e. if I put an Image constructor inside the ImageView when initializing it, it works just fine)
Add a ChangeListener to the text property instead. onAction is only triggered, when Enter is pressed or similar.
Furthermore I recommend not recreating the images every time:
private static final Image VALID_IMG = new Image(MyPresenter.class.getClassLoader().getResource("emblem-default.png").toExternalForm());
private static final Image INVALID_IMG = new Image(MyPresenter.class.getClassLoader().getResource("dialog-error.png").toExternalForm());
public Image validationImage(String token) {
return isValid(token) ? VALID_IMG : INVALID_IMG;
}
view.tokentxt.textProperty().addListener((observable, oldValue, newValue) -> {
view.isValidimg.setImage(validationImage(newValue));
});
You could also use a Binding for this:
view.isValidating.imageProperty().bind(Bindings.createObjectBinding(() -> validationImage(view.tokentxt.getText()), view.tokentxt.textProperty()));
Which would update the image even before the text is modified.

JavaFX CheckBoxTree in popup of drop-down Button

In order for the end-user to constrain a search to some columns of the main TableView, I needed a treeview with checkboxes.
I decided to embed this TreeView in a popup, showing on click on a custom button.
I have created the following class, inspired from the question:
Java FX8 TreeView in a table cell
public class CustomTreeMenuButton extends MenuButton {
private PopupControl popup = new PopupControl();
private TreeView<? extends Object> tree;
private CustomTreeMenuButton me = this;
public void setTree(TreeView<? extends Object> tree) {
this.tree = tree;
}
public CustomTreeMenuButton() {
super();
this.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (!popup.isShowing()) {
Bounds b = me.localToScreen(me.getBoundsInLocal());
double x = b.getMinX();
double y = b.getMaxY();
popup.setAutoHide(true);
// popup.setAutoFix(true);
popup.setAnchorX(x);
popup.setAnchorY(y);
popup.setSkin(new Skin<Skinnable>() {
#Override
public void dispose() {
}
#Override
public Node getNode() {
return tree;
}
#Override
public Skinnable getSkinnable() {
return null;
}
});
popup.show(me.getScene().getWindow());
}
}
});
}
}
The tree I am working with contains CheckBoxTreeItem objects, and while the popup is working, there is some weird blur on all checkboxes, whenever the focus is not on a checkbox. (See GIF below)
First, I was thinking it was maybe an antialiasing problem, but popup.getScene().getAntiAliasing().toString() returns DISABLED
Then, I saw that non integer anchor points could cause problems. However popup.setAutoFix(true) did nothing, nor did the following:
popup.setAnchorX(new Double(x).intValue());
popup.setAnchorY(new Double(y).intValue());
It might be worth noting that I am working with FXML.
How can I get sharp checkboxes regardless of their focus ?
I would suggest a built-in control, CustomMenuItem, rather than reinventing the wheel:
A MenuItem that allows for arbitrary nodes to be embedded within it,
by assigning a Node to the content property.
An example
// Create the tree
CheckBoxTreeItem<String> rootItem = new CheckBoxTreeItem<String>("All stuff");
rootItem.setExpanded(true);
final TreeView<String> tree = new TreeView<String>(rootItem);
tree.setEditable(true);
tree.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
for (int i = 0; i < 8; i++) {
final CheckBoxTreeItem<String> checkBoxTreeItem =
new CheckBoxTreeItem<String>("Stuff" + (i+1));
rootItem.getChildren().add(checkBoxTreeItem);
}
tree.setRoot(rootItem);
tree.setShowRoot(true);
// Create a custom menu item
CustomMenuItem customMenuItem = new CustomMenuItem(tree);
customMenuItem.setHideOnClick(false);
// Create the menu button
MenuButton mb = new MenuButton("Stuffs");
mb.getItems().add(customMenuItem);
And the output
Note: It is important to set the hideOnClickProperty to true, to avoid closing when the user clicks in the tree, which can be even done in the contructor, so you can shorten the initialization to:
CustomMenuItem customMenuItem = new CustomMenuItem(tree, false);
If you want to remove the hover glow, you can add the following CSS class:
.menu-item {
-fx-padding: 0;
}

How to run one of multiple distinct stages at application startup with javafx?

I'm starting with javafx and I'm having some trouble understanding how to correctly model the following situation:
Ideally I'd like to have a main() method that would somehow allow me to either open a LoginDialog or if there's already a user/password combination available on disk, to bypass login and directly show the MainDialog to the user.
My main issue is that when I run Application.launch() I'm expected to submit an Application instance, and when implementing one, I don't have any control over its Stage object creation, which creates a catch-22 for me here.
I could create a LoginScene and MainScene but then I'd have no control for things like the Stage's title, for instance.
What's the usual route to solve this kind of issues with javafx?
Thanks
Define a single Application subclass and put the logic to decide whether you need to show the login screen in the start() method (the proper place for startup logic is the aptly-named start() method, not the main method):
public class MyApplication extends Application {
private boolean loggedIn ;
#Override
public void start(Stage primaryStage) {
loggedIn = checkLoginFromDisk();
while (! loggedIn) {
FXMLLoader loginLoader = new FXMLLoader(getClass().getResource("path/to/login.fxml"));
Parent loginRoot = loginLoader.load();
LoginController loginController = loginLoader.getController();
Scene loginScene = new Scene(loginRoot);
primaryStage.setScene(loginScene);
primaryStage.setTitle("Login");
primaryStage.showAndWait();
// check login from controller and update loggedIn...
}
FXMLLoader mainLoader = new FXMLLoader(getClass().getResource("path/to/main.fxml"));
Parent mainRoot = mainLoader.load();
Scene mainScene = new Scene(mainRoot);
primaryStage.setScene(mainScene);
primaryStage.setTitle("My Application");
primaryStage.sizeToScene();
primaryStage.show();
}
private boolean checkLoginFromDisk() {
// ... etc
}
// for environments not supporting direct launch of JavaFX:
public static void main(String[] args) {
launch(args);
}
}
If you're not using FXML, you just define classes instead of FXML files + controllers for "login" and "main", but the structure stays the same:
public class LoginView {
private final GridPane /* for example */ view ;
public LoginView() {
// setup UI, etc...
}
public Pane getView() {
return view ;
}
public boolean checkLogin() {
// etc...
}
}
and
public class MainView {
private BorderPane /* for example */ view ;
public MainView() {
// set up UI etc...
}
public Pane getView() {
return view ;
}
}
and your start method then looks like
#Override
public void start(Stage primaryStage) {
loggedIn = checkLoginFromDisk();
while (! loggedIn) {
LoginView loginView = new LoginView();
Scene loginScene = new Scene(loginView.getView());
primaryStage.setScene(loginScene);
primaryStage.setTitle("Login");
primaryStage.showAndWait();
loggedIn = loginView.checkLogin();
}
MainView mainView = new MainView();
Scene mainScene = new Scene(mainView.getView());
primaryStage.setScene(mainScene);
primaryStage.setTitle("My Application");
primaryStage.sizeToScene();
primaryStage.show();
}
Obviously you can refactor this in many different ways (reuse the same login class or fxml instance, use a different stage for the main view, etc etc etc) as you need.
Note that there is no requirement to use the stage passed to the start() method. So if you wanted standalone classes to encapsulate the stage containing a login scene and a main scene, you could add the following classes:
public class LoginStage extends Stage {
private final LoginView loginView ;
public LoginStage() {
loginView = new LoginView();
setScene(new Scene(loginView.getView());
setTitle("Login");
}
public boolean checkLogin() {
return loginView.checkLogin();
}
}
and similarly make a MainStage class. (In the FXML-based version, the LoginStage holds a reference to the LoginController and just loads the FXML in the constructor instead of instantiating the LoginView class.) Then
public class MyApplication extends Application {
private boolean loggedIn ;
#Override
public void start(Stage ignored) {
loggedIn = checkLoginFromDisk();
while (! loggedIn) {
LoginStage login = new LoginStage();
loginStage.showAndWait();
loggedIn = loginStage.checkLogin();
}
new MainStage().show();
}
// ...
}
This seems to be remarkably similar to what I was looking for. It follows jns suggestion.
Not ideal but not terrible:
class LoginScene(stage: Stage) extends Scene(new VBox()) {
val vbox = this.getRoot.asInstanceOf[VBox]
...
}
class MainScene(stage: Stage) extends Scene(new VBox()) {
val vbox = this.getRoot.asInstanceOf[VBox]
...
}
class ApplicationStartup extends Application {
override def start(primaryStage: Stage): Unit = {
val scene = if (...) new LoginScene(primaryStage) else new MainScene(primaryStage)
primaryStage.setScene(scene)
primaryStage.show()
}
}
(code is presented in Scala)
Alternatively, as can be seen from the question's comments, one can just ignore the primaryStage and create our own ones at will, which it's just what I wanted from the outset:
class MainDialog extends Application {
override def start(primaryStage: Stage): Unit = {
val newStage = new Stage {
setTitle("abcdef")
setScene(new Scene(new Button("Hello World")))
}
newStage.show()
}
}

Categories

Resources