Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Saturday, November 21, 2015

A Popup Helper in JavaFX

This post shows how to use a JavaFX popup to supplement a TextField.  Like a ComboBox, text can be entered directly into the TextField or a selection can be made from a list.  The list is implemented as a Popup subclass -- rather than a ComboBox extension -- to allow for a more complicated extension or an alternative presentation.

All of the source code in this example is available on GitHub.

The main view of this JavaFX application contains a TextField and a Button.  Pressing the Button will launch a Popup.

Popup Displayed When Empl Button Pressed
Both the main view and the popup are implemented in FXML and paired with a JavaFX Controller.

Main View

The Java main() and start(), ListViewHelperApp, loads the ListViewHelperMain.fxml file.

ListViewHelperMain.fxml and ListViewHelperController implement the "ListView Helper" window in the above screenshot.  The following is the code from ListViewHelperController.  The position for the Empl Button, a reference to the target TextField, and the Window are passed along to a JavaFX Popup subclass.

@FXML
TextField tfEmployee;
 
@FXML
public void clear() { tfEmployee.setText( "" ); }

@FXML
public void showEmployeesHelper(ActionEvent evt) {
  
  Button btn = (Button)evt.getSource();
  
  Point2D point = btn.localToScreen(0.0d + btn.getWidth(), 
      0.0d - btn.getHeight());
  
  try {
   
    Popup employeesHelper = 
      new ListViewHelperEmployeesPopup(tfEmployee, point);
   
    employeesHelper.show(btn.getScene().getWindow());
  
  } catch(Exception exc) {
    exc.printStackTrace();
    Alert alert = new Alert(AlertType.ERROR, 
      "Error creating employees popup; exiting");
    alert.showAndWait();
    btn.getScene().getWindow().hide();  // close and implicit exit
  }
}

Employees View

The Employees View -- the list of employees -- is a very small class that fills the ListView.  It is implemented with ListViewHelperEmployees.fxml and ListViewHelperEmployeesController.  The following is from ListViewHelperEmployeesController.

@FXML
ListView lvEmployees;
 
@FXML
public void initialize() {
    lvEmployees.getItems().addAll( 
      "Adams, John", 
      "Lincoln, Abe", 
      "Washington, George" 
 };
}

Popup Subclass

The Popup subclass ListViewHelperEmployeesPopup creates the Employees View through an FXMLLoader.  It also puts the selection handler on the Employees View's ListView member.  The subclass constructor initializes the Popup Stage.  The initialization positions the popup, sets the autoHide property, and adds the content.

There is an animation that I put on the Popup when hidden.  See the following video for a detailed discussion of that code.


The following is the Popup subclass' constructor.

public ListViewHelperEmployeesPopup(TextField tfTarget, Point2D position) throws Exception{
  
  final WeakReference tfTargetRef = new WeakReference<>(tfTarget);
 
  FXMLLoader fxmlLoader = new FXMLLoader(ListViewHelperApp.class.getResource("/ListViewHelperEmployees.fxml"));
  
  Parent content = fxmlLoader.load();
  
  ListViewHelperEmployeesController controller = fxmlLoader.getController();
  
  controller
    .lvEmployees
    .getSelectionModel()
    .selectedItemProperty()
    .addListener((obs, oldValue, newValue) -> {
     
      if( newValue != null ) {
   
       tfTargetRef.get().setText( newValue );
       
       // no delay -- and no feedback
       //ListViewHelperEmployeesPopup.this.hide();
    
       // slight delay + gentle fade -- maintains highlight
       Duration duration = new Duration( 300 );
    
       KeyValue kvOpacity = new KeyValue(content.opacityProperty(), 0);
          
       KeyFrame atEndFrame = new KeyFrame( 
          duration, 
          (evt) -> ListViewHelperEmployeesPopup.this.hide(),
          kvOpacity
       );
   
       Timeline timeline = new Timeline();
       timeline.getKeyFrames().add( atEndFrame );
       timeline.play();
     }
   
    });
  
  setX( position.getX() );
  setY( position.getY() );
  setAutoHide(true);  
  getContent().add( content );
}

In some cases, it may be easier to work with a ComboBox subclass rather than a special purpose Popup to enhance TextFields.  However, I like to use FXML to build more complicated UIs.  This approach will scale should the Popup UI need to grow.

All of the source code in this example is available on GitHub.

No comments:

Post a Comment