0

I like to set up the preferred width/height of some components in my JavaFX UI, as some of the dimensions that it calculates by default are not to my liking. For example, I have some tables where currently all columns have the same width, but some should be larger than others. Also, I have some split panes that currently all have a 50-50 division which I'd like to change, and so on.

However I don't like to hard-code the values in the fxml files, because the "best" layout depends on the screen resolution, and also different users might have a different idea what is "best", it may even depend on their data how big or small they want a component to be.

My idea was to let the user layout the UI to his liking, and save his layout, then re-load it on startup. However from my research it seems there is no out of the box support to persist a UI layout, so it seems I have to manually define fx:id's for all relevant components and save their widths/heights to e.g. a file, and read that on startup and set the values likewise.

But I wonder, is there really not a better way to do this? Is it maybe be possible to iterate over all nodes in a scene and collect their dimensions, without having to know what speficially is contained in a scene? And also, when the scene is composed of multiple different fxml's? If yes, then I could collect that into a Map<fx:id,Dimensions> and apply this map on startup, what do you think of this approach?

Anyway, as I wouldn't know how to do this, I first started with an attempt to do it manually for a single component, but even that didn't work. See the sample application below, where I try to set up the preferred width of the left split pane. The setPrefWidth has no effect, the splitpane is still divided in the middle.

package com.example.javafxsample;

import javafx.application.Application;
import javafx.collections.ObservableMap;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.stage.Stage;

public class SampleApp extends Application {

    public static void main(String[] args) {
        launch();
    }

    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader(SampleApp.class.getResource("view.fxml"));
        Scene scene = new Scene(loader.load());
        stage.setScene(scene);
        stage.setMaximized(true);
        stage.show();
        initLayout(loader);
    }

    private void initLayout(FXMLLoader loader) {
        ObservableMap<String, Object> namespace = loader.getNamespace();
        SplitPane leftSplitPane = (SplitPane) namespace.get("leftSplitPane");
        leftSplitPane.setPrefWidth(500); // doesn't work
        // maxWidth however works: leftSplitPane.setMaxWidth(500);
    }
}

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<SplitPane xmlns="http://javafx.com/javafx"
           xmlns:fx="http://javafx.com/fxml"
           orientation="HORIZONTAL">
    <SplitPane fx:id="leftSplitPane" orientation="VERTICAL">
        <Label text="left-top" maxHeight="Infinity"/>
        <Label text="left-bottom" maxHeight="Infinity"/>
    </SplitPane>
    <Label text="right" maxWidth="Infinity"/>
</SplitPane>
user3237736
  • 845
  • 1
  • 9
  • 24
  • 1
    _best practice .. to set the preferred width/height of some components_ don't do it all, it's the task of the layout, interfering with it is wrong .. __always__ ;) – kleopatra May 01 '22 at 09:16
  • _let the user layout the UI to his liking_ don't give them too much leeway - they will wreck it ;) – kleopatra May 01 '22 at 09:18
  • 1
    _I first started with an attempt to do it manually for a single component, but even that didn't work_ not sure what you expect: you try to programmatically set a contraint on an instance that's different from the one you are showing, so I would be very surprised to see any effect :) – kleopatra May 01 '22 at 09:47
  • @kleopatra "don't give them too much leeway - they will wreck it ;)" Sorry, but it's a basic feature of pretty much every single desktop application I know, that you can slide positions of dividers or resize columns etc and the program will save it. The layout doesn't know the data, but the data is relevant for how small/big (wide/high) something would best show on the UI. So basically the answer is "not possible with JavaFX"? – user3237736 May 01 '22 at 11:07
  • @kleopatra about your comment "you try to programmatically set a contraint on an instance that's different from the one you are showing", okay I understand it know, everytime I instantiate a new Loader and load() it it will create a separate instance. I changed this and now it works. Still it seems like a strange approach to do it like this – user3237736 May 01 '22 at 11:12
  • 1
    _not possible with JavaFX_ no, I'm saying you do it the wrong way: instead save the semantic data (like percentage divider locations, for instance) and let the layouts do its jobs. And nothing strange that changes to one instance of a class has no effect on another instance of the same class – kleopatra May 01 '22 at 12:28
  • Persist the semantic data values, as @kleopatra suggests, using `java.util.prefs.Preferences`, for example. – trashgod May 01 '22 at 12:33
  • "(like percentage divider locations, for instance)". Ok, for the splitpanes this works. But what about table columns? As far as I've seen, I can't define percentage widths for columns in fxml, or can I? – user3237736 May 01 '22 at 13:26
  • But that still doesn't really answer the question: How exactly would you implement in JavaFX the persisting of the UI as the user has left it when he closes the program, which is a feature every single desktop app has. So I would have thought there is a"best practice" template for this. – user3237736 May 01 '22 at 13:28
  • @trashgod Thanks, I attempted using the Preferences, with the idea to update the preferences via a listener on the components, so whenever the user resizes something, it's stored. And then I planned to read the preferences on startup. However I got stuck before even using preferences, because setting preferred dimensions doesn't seem to work at all. I updated the sample program in my post, please check it out. Setting pref width just doesn't work? Using maxWidth works, but is useless of course, because then the user can no longer resize anything and the whole thing has no point anymore. – user3237736 May 01 '22 at 15:09
  • you __must not__ hard-code sizing constraints (setXXSize/width/height with XX == min/pref/max) of Nodes - doing so will __disable__ the working of layouts. Why do you insist on doing it? It's __wrong__. Study at least the api doc of the layouts that are available (f.i. splitPane doesn't care overly much for pref of its children, as specified). – kleopatra May 01 '22 at 15:57
  • 1
    Once you get your stage's _resize_ behavior as you like, _without_ hard-coded constraints, focus on persisting the `Window` properties like `x`, `y`, `width` & `height`. On restoration, restore those settings. Then focus on what's left. – trashgod May 01 '22 at 16:56
  • Sorry but I don't see how this could result in good user experience. For example the "appropriate" width of the individual columns of a table is something that a layout cannot know. A user might want to cut off contents of column A for the sake of having column B big enough to show all contents, because here cares about B more than A. Or with a splitpane, a user might put the slider completely to the left, hiding the left component altogether,as he doesn't care about it, etc. A proper app should remember all this, not let the user re-adjust all these aspects in the UI after every startup. – user3237736 May 01 '22 at 18:38
  • So this means: A proper UI does remember the layouting up to the exact pixels. All my desktop apps do this, your's not? So, if I *must not* hard-code sizings, for me that does sound like: Not possible in JAvaFX – user3237736 May 01 '22 at 18:40
  • Doing this in the general sense may be difficult (I would not say it is not possible). I would not expect a StackOverflow answer with a general solution that would save and restore an arbitrary UI layout to the exact pixel. As has already been pointed out numerous times, it may not be what is wanted (e.g. what should be the behavior for saving a full-screen layout when the app is opened on different resolution screens). – jewelsea May 02 '22 at 21:16
  • 1
    The closest form for saving an exact representation may be to save and restore the UI as FXML with full current constraint info. The saving part would be difficult and if you do it using preferred/min/max sizes it will likely mess with the ability to provide a good resizable UI. Probably, it is best to just keep the size preferences you remember to a minimum, following the suggestion of @trashgod. I suggest aiming for a solution that you feel is "good enough" for your users rather than perfect. – jewelsea May 02 '22 at 21:18
  • Related: [JavaFX: Save and restore visual state of TableView (order, width and visibility)](https://stackoverflow.com/questions/27189090/javafx-save-and-restore-visual-state-of-tableview-order-width-and-visibility), and [Save and Restore state of a TableView (which columns are hidden, width of columns) in JavaFX](https://stackoverflow.com/questions/48086685/save-and-restore-state-of-a-tableview-which-columns-are-hidden-width-of-column). – jewelsea May 02 '22 at 21:43
  • This [example](https://stackoverflow.com/a/34616583/230513) cites other examples and illustrates the basic life cycle. – trashgod May 02 '22 at 22:02
  • Thanks for your answers, but I won't go down that rabbit hole. It was obviously not considered by the designers then. I've learned my lesson by now, I'll move away from JavaFX, every day I come accross stuff that should be basic and just isn't there. Today's pinnacle: You want to show a text on a ProgressBar? Like "50%"? Wooah that's craaazzy, never seen it, not supported, please create a StackPane or something and build a usable widget yourself. At least that one is one of the rare cases where you won't have to implement 8 custom listeners ^^ But sry for the rant, thx again for your time. – user3237736 May 03 '22 at 19:10

0 Answers0