4 Replies Latest reply: Dec 20, 2012 8:39 PM by James_D RSS

    JavaFX Dynamic forms

    980678
      I want to create a form that dynamically shows/hides input fields.

      For example, I have a RadioButton that will, depending on the value selected, cause a TextArea to be displayed (using a translation & fade transitions). If the radio button has any other controls below it then they will be moved down to make space for the TextArea, if the 'trigger' value in the ToggleGroup of RadioButtons is changed then hide the TextArea (again using transitions) and move any controls up to take the space of the TextArea.

      Any help (or examples) will be appreciated.
        • 1. Re: JavaFX Dynamic forms
          975668
          hi,
          You can hidden fields using Scene buider. in the controler active the fields.

          public void initialize(URL url, ResourceBundle rb) {

          txarea_obs.setDisable(true);

          listviewClick.getSelectionModel().selectedItemProperty().addListener(

          VisualizarListener = new ChangeListener<String>() {

          public void changed(ObservableValue<? extends String> ov, String old_val, String new_val) {
          try {

          if(new_val == "Enabled"){

          txarea_obs.setDisable(false);
          }


          }catch (SQLException ex) {
          JOptionPane.showMessageDialog(null, "Erro encontrado:"+ex);
          }

          }
          }


          );
          • 2. Re: JavaFX Dynamic forms
            James_D
            If you don't want the animation, you can simply bind, eg. the maxHeight of the "dynamic" control to the radio button's selected property:
            import javafx.application.Application;
            import javafx.beans.binding.Bindings;
            import javafx.scene.Scene;
            import javafx.scene.control.Button;
            import javafx.scene.control.RadioButton;
            import javafx.scene.control.TextArea;
            import javafx.scene.control.ToggleGroup;
            import javafx.scene.layout.HBox;
            import javafx.scene.layout.VBox;
            import javafx.stage.Stage;
            
            public class DynamicForm extends Application {
            
              @Override
              public void start(Stage primaryStage) {
                final VBox form = new VBox(10);
                final VBox choices = new VBox(5);
                RadioButton male = new RadioButton("Male");
                RadioButton female = new RadioButton("Female");
                RadioButton other = new RadioButton("Other (please specify)");
                ToggleGroup toggleGroup = new ToggleGroup();
                toggleGroup.getToggles().addAll(male, female, other);
                choices.getChildren().addAll(male, female, other);
                TextArea otherText = new TextArea();
                otherText.setPrefColumnCount(40);
                otherText.setPrefRowCount(3);
                HBox buttons = new HBox(10);
                buttons.getChildren().addAll(new Button("OK"), new Button("Cancel"));
            
                form.getChildren().addAll(choices, otherText, buttons);
            
                otherText.maxHeightProperty().bind(
                    Bindings.when(other.selectedProperty())
                        .then(Control.USE_COMPUTED_SIZE).otherwise(0));
            
                Scene scene = new Scene(form, 300, 300);
                primaryStage.setScene(scene);
                primaryStage.show();
            
              }
            
              public static void main(String[] args) {
                launch(args);
              }
            }
            To animate, just register a change listener with the relevant selectedProperty and start the appropriate animation depending on the selection.

            Edited by: James_D on Dec 19, 2012 12:05 PM
            • 3. Re: JavaFX Dynamic forms
              980678
              Hi James_D
              Thanks for your response. Could you give an example of adding the animation. Really not sure where to start with that. Still very new to JavaFX.

              For example if I wanted to animate a slide down/up effect on the TextArea when the Other value is selected, how would I do that?

              Edited by: 977675 on Dec 21, 2012 10:17 AM

              Edited by: 977675 on Dec 21, 2012 10:18 AM
              • 4. Re: JavaFX Dynamic forms
                James_D
                There's a nice overview of Animation (Timelines and Transitions) here: http://docs.oracle.com/javafx/2/animations/basics.htm

                The basic idea is that a Timeline changes the value of one or more properties over a specified time interval. You can think of a property as an object that holds a value. In JavaFX properties are "observable", which means that other objects can register to be notified when they change. Controls observe their own properties, and if a change is made which means the control needs to be redisplayed, it schedules a request to be redisplayed. For example, the TextArea in the example I posted has a maxHeight property. Changing the value of the maxHeight property causes the text area itself to be notified, and it is redisplayed. There's a little bit of intelligent logic in there to ensure that if a property is changed multiple times during a single "pulse" of the thread managing the actual repaints, that all changes are coalesced into the last change.

                So to animate a slide down effect, you simply create a timeline that changes the value of the maxHeight property, and the maximum height will change from a zero value (causing it to take up no space in your layout) to the full size which you need. (Calculating that size is actually a bit problematic, the code I post below has a bit of a hack to do it.) There are some subtleties to worry about to do with multithreading here, but the Timeline class and other associated classes take care of all of these subtleties for you.

                The mechanics of how you create a timeline are as follows. First you create a bunch of KeyValue objects. Each KeyValue specifies a property to be set and a value to set it to. So we'll create a KeyValue specifying the maxHeight property of the text area, and a value of 0; and another KeyValue specifying the same property and a value which is the final height we want the text area to be. Use the KeyValues to create a sequence of KeyFrames. Each KeyFrame contains a KeyValue (property and value to which to set it) and a time at which to attain the KeyValue. Finally, create a timeline with a collection of KeyFrames. When play() is called on the timeline, it sets all properties it knows about to their keyValues at 0, and then incrementally changes them to meet each specified value at the appropriate times specified by all the key frames. In your case it's pretty simple: one key frame for a time of zero and a key value setting the maxHeight property to zero, and one key frame at the end of the transition (say 500 milliseconds) with a key value setting the maxHeight to it's target value. To hide the text area, just play a timeline with the key values reversed.

                To create a "fade out" animation, you could create a timeline that changes the value of the opacity property. Since this is a really common effect, there's a FadeTransition class that does exactly that. You construct it specifying a Node which will be faded, a duration, and specify the "from" value (starting opacity) and "to" value (ending opacity).

                To run two or more transitions one after the other, use a SequentialTransition and specify the transitions to be run in sequence. To run two or more simultaneously, use a ParallelTransition. I'll show a ParallelTransition which runs a FadeTransition simultaneously with the transition changing the maxHeight.

                The only tricky part here is figuring out what to set the maxHeight to when the text area is fully displayed (i.e. the target value at the end of the transition). The default maxHeight for a text area (and many other controls) is the special value Control.USE_COMPUTED_SIZE. This means the size should be computed based on the requirements of the control. The actual value this evaluates to is negative (I think -1), and animating from 0 to -1 obviously won't achieve the desired effect. The height isn't actually computed until the control is added to a scene and displayed in a stage. So the (somewhat ugly) approach we'll take is to add the text area to the scene in the usual way, display the stage, ask the textArea for it's height and save that in a variable, then reset the maxHeight of the text area to zero. Note this won't work if you're using FXML and you're trying to do this in the initialize() method (the textArea will not be displayed in a stage at this point). In this scenario you could either register a listener for changes to the height property and set a variable the first time you get something meaningful. For a text area it's probably good enough to make some reasonable computation for the height (8 + 16*number of desired rows, for example). The text area will just display additional rows if there's too much space and use scroll bars if there's not enough. For other controls it might be a bit more important to get it right.
                import javafx.animation.FadeTransition;
                import javafx.animation.KeyFrame;
                import javafx.animation.KeyValue;
                import javafx.animation.ParallelTransition;
                import javafx.animation.Timeline;
                import javafx.application.Application;
                import javafx.beans.value.ChangeListener;
                import javafx.beans.value.ObservableValue;
                import javafx.scene.Scene;
                import javafx.scene.control.Button;
                import javafx.scene.control.RadioButton;
                import javafx.scene.control.TextArea;
                import javafx.scene.control.ToggleGroup;
                import javafx.scene.layout.HBox;
                import javafx.scene.layout.VBox;
                import javafx.stage.Stage;
                import javafx.util.Duration;
                
                public class DynamicForm extends Application {
                
                  @Override
                  public void start(Stage primaryStage) {
                    final VBox form = new VBox(10);
                    final VBox choices = new VBox(5);
                    final RadioButton male = new RadioButton("Male");
                    final RadioButton female = new RadioButton("Female");
                    final RadioButton other = new RadioButton("Other");
                    final ToggleGroup toggleGroup = new ToggleGroup();
                    toggleGroup.getToggles().addAll(male, female, other);
                    choices.getChildren().addAll(male, female, other);
                    final TextArea otherText = new TextArea();
                    otherText.setPrefColumnCount(40);
                    otherText.setPrefRowCount(3);
                    otherText.setPromptText("Please specify");
                    final VBox otherBox = new VBox();
                    otherBox.getChildren().add(otherText);
                    final HBox buttons = new HBox(10);
                    buttons.getChildren().addAll(new Button("OK"), new Button("Cancel"));
                
                    form.getChildren().addAll(choices, otherBox, buttons);
                
                    // otherText.maxHeightProperty().bind(
                    // Bindings.when(other.selectedProperty())
                    // .then(Control.USE_COMPUTED_SIZE).otherwise(0));
                
                    Scene scene = new Scene(form, 300, 300);
                    primaryStage.setScene(scene);
                    primaryStage.show();
                
                    final double textHeight = otherText.getHeight();
                
                    other.selectedProperty().addListener(new ChangeListener<Boolean>() {
                      @Override
                      public void changed(ObservableValue<? extends Boolean> observable,
                          Boolean oldValue, Boolean newValue) {
                        final Duration duration = new Duration(500);
                        FadeTransition fade = new FadeTransition(duration, otherText);
                        KeyValue sizeStart;
                        KeyValue sizeEnd;
                        if (newValue) {
                          fade.setFromValue(0);
                          fade.setToValue(1);
                          sizeStart = new KeyValue(otherText.maxHeightProperty(), 0);
                          sizeEnd = new KeyValue(otherText.maxHeightProperty(), textHeight);
                        } else {
                          fade.setFromValue(1);
                          fade.setToValue(0);
                          sizeStart = new KeyValue(otherText.maxHeightProperty(), textHeight);
                          sizeEnd = new KeyValue(otherText.maxHeightProperty(), 0);
                        }
                        Timeline animateSize = new Timeline(new KeyFrame(new Duration(0),
                            sizeStart), new KeyFrame(duration, sizeEnd));
                        ParallelTransition transition = new ParallelTransition(fade,
                            animateSize);
                        transition.play();
                      }
                    });
                    System.out.println(textHeight);
                    otherText.setMaxHeight(0);
                    otherText.setOpacity(0);
                  }
                
                  public static void main(String[] args) {
                    launch(args);
                  }
                }