26

I have my JavaFX 2.0 application, where i need to make some action, after user clicked an item in ListView element. To construct user GUI i'm using FXML, in which i have something like this:

        <children>
            <ListView fx:id="listView" GridPane.columnIndex="0" 
            GridPane.rowIndex="1" labelFor="$pane" 
            onPropertyChange="#handleListViewAction"/>
        </children>

And here is what i have in a Controller for this event:

        @FXML protected void handleListViewAction(ActionEvent event) {
           System.out.println("OK");
        }

And here is an error, i recieve, when the scene, which is for this gui is constructed:

javafx.fxml.LoadException: java.lang.String does not define a property model for "property".
at javafx.fxml.FXMLLoader$Element.processEventHandlerAttributes(Unknown Source)
at javafx.fxml.FXMLLoader$ValueElement.processEndElement(Unknown Source)
at javafx.fxml.FXMLLoader.processEndElement(Unknown Source)
at javafx.fxml.FXMLLoader.load(Unknown Source)
at javafx.fxml.FXMLLoader.load(Unknown Source)
at javafx.fxml.FXMLLoader.load(Unknown Source)
at fxmlexample.FXMLExampleController.handleSubmitButtonAction(FXMLExampleController.java:49)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
at javafx.event.Event.fireEvent(Unknown Source)
at javafx.scene.Node.fireEvent(Unknown Source)
at javafx.scene.control.Button.fire(Unknown Source)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(Unknown Source)
at com.sun.javafx.scene.control.skin.SkinBase$5.handle(Unknown Source)
at com.sun.javafx.scene.control.skin.SkinBase$5.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source)
at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source)
at javafx.event.Event.fireEvent(Unknown Source)
at javafx.scene.Scene$MouseHandler.process(Unknown Source)
at javafx.scene.Scene$MouseHandler.process(Unknown Source)
at javafx.scene.Scene$MouseHandler.access$1300(Unknown Source)
at javafx.scene.Scene.impl_processMouseEvent(Unknown Source)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source)
at com.sun.glass.ui.View.handleMouseEvent(Unknown Source)
at com.sun.glass.ui.View.notifyMouse(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$100(Unknown Source)
at com.sun.glass.ui.win.WinApplication$2$1.run(Unknown Source)
at java.lang.Thread.run(Thread.java:722) java.lang.NullPointerException
at javafx.scene.Scene.doCSSPass(Unknown Source)
at javafx.scene.Scene.access$2900(Unknown Source)
at javafx.scene.Scene$ScenePulseListener.pulse(Unknown Source)
at com.sun.javafx.tk.Toolkit.firePulse(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit$8.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$100(Unknown Source)
at com.sun.glass.ui.win.WinApplication$2$1.run(Unknown Source)
at java.lang.Thread.run(Thread.java:722)

And the last block of this exception (from here java.lang.NullPointerException) is looped.

user990423
  • 1,397
  • 2
  • 12
  • 32
Victoria Agafonova
  • 2,048
  • 10
  • 33
  • 50
  • You can also observe 'selection model' instead of listening for clicks: `listView.getSelectionModel().selectedItemProperty().addListener((prop, old, new) -> System.out.println(new))` It is generic, no ugly `Object` type. – Miha_x64 Jul 30 '18 at 20:30

5 Answers5

53

FXML attributes and values are directly mapped to FX API. So to find out how to write handler you can first create required entities by API.

It seems you want to add action on ListView element on mouse click, so you need to add mouse click handler. In API it looks next way:

    final ListView lv = new ListView(FXCollections.observableList(Arrays.asList("one", "2", "3")));
    lv.setOnMouseClicked(new EventHandler<MouseEvent>() {

        @Override
        public void handle(MouseEvent event) {
            System.out.println("clicked on " + lv.getSelectionModel().getSelectedItem());
        }
    });

Looking at that code you can find out what in FXML you need to overrided attribute onMouseClicked:

<ListView fx:id="listView" onMouseClicked="#handleMouseClick"/>

And in controller you need to provide handler with MouseEvent parameter:

@FXML
private ListView listView;

@FXML public void handleMouseClick(MouseEvent arg0) {
    System.out.println("clicked on " + listView.getSelectionModel().getSelectedItem());
}
Sergey Grinev
  • 34,078
  • 10
  • 128
  • 141
  • Hello. If i have an EventHandler that is not bound to a controller but can be used independently at many places in my application. Is it possible to assign the EventHandler directly in fxml instead of a method from the controller? – FuryFart Feb 07 '14 at 12:02
  • This is exactly what I was looking for! Thanks! – Maslor Jul 31 '15 at 10:57
  • I'm really not sure how to use this outside my start method. How can I call the ownerStage from within a Controller for example? – Martin Erlic Apr 18 '17 at 17:08
  • @santafebound there are several methods. You either directly assign it after load: http://stackoverflow.com/questions/13246211/javafx-how-to-get-stage-from-controller-during-initialization/13247005#13247005 or get it from some of the controls initialized by FXML: http://stackoverflow.com/questions/11994366/how-to-reference-primarystage/12003426#12003426 (first option is more reliable). – Sergey Grinev Apr 20 '17 at 11:18
9

answer from @Sergey Grinev may have an issue. if your ListView is filled not enough full, in other words may have some blank space. then you cliked blank space will not tigger selection changing. enter image description here

the solution: Use custom ListCell.

  1. define a custom class extends ListCell.
  2. in the updateItem function you can set item click event handler to cell root node.

If you don't know how many items you have. you will not ever only do so. you should use time for space.

Demostrate:

in the ListView code segment:

listView.setCellFactory(new AppListCellFactory());

AppListCellFactory.java:

public class AppListCellFactory implements Callback, ListCell> {

// only one global event handler
private EventHandler<MouseEvent> oneClickHandler;


public AppListCellFactory(){
    oneClickHandler = new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent event) {
            Parent p = (Parent) event.getSource();
            //  do what you want to do with data.
            AppClickHandler.onAppBeanClicked((AppBean) p.getUserData());
        }
    };
}

@Override
public ListCell<AppBean> call(ListView<AppBean> param) {
    return new DemoListCell(oneClickHandler);
}

public static final class DemoListCell extends ListCell<AppBean> {

    private EventHandler<MouseEvent> clickHandler;

    /**
     * This is ListView item root node.
     */
    private Parent itemRoot;

    private Label label_AppName;
    private ImageView imgv_AppIcon;

    DemoListCell(EventHandler<MouseEvent> clickHandler) {
        this.clickHandler = clickHandler;
    }

    @Override
    protected void updateItem(AppBean app, boolean empty) {
        super.updateItem(app, empty);
        if (app == null) {
            setText(null);
            setGraphic(null);
            return;
        }
        if (null == itemRoot) {
            try {
                itemRoot = FXMLLoader.load(getClass().getResource(("fxml/appList_item.fxml")));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            label_AppName = (Label) itemRoot.lookup("#item_Label_AppName");
            imgv_AppIcon = (ImageView) itemRoot.lookup("#item_ImageView_AppIcon");
            itemRoot.setOnMouseClicked(clickHandler);
        }
        //  set user data. like android's setTag(Object).
        itemRoot.setUserData(app);
        label_AppName.setText(app.name);
        imgv_AppIcon.setImage(new Image(getClass().getResource("img/icon_64.png").toExternalForm()));
        setGraphic(itemRoot);
    }
}

}

gordonpro
  • 271
  • 3
  • 7
  • @shinzou the problem is not Java itself, but ugly APIs. Unfortunately, there are many of them. Everywhere... – Miha_x64 Jul 30 '18 at 20:17
6

You can also consume unwanted events on blank space.

in call() of cell factory add:

cell.setOnMousePressed((MouseEvent event) -> {
    if (cell.isEmpty()) {
       event.consume();
    }
});

This will disable click & dragStart too.

Whole cellFactory example from my project:

public static Callback<ListView<BlockFactory>, ListCell<BlockFactory>> getCellFactory() {
    return new Callback<ListView<BlockFactory>, ListCell<BlockFactory>>() {

        @Override
        public ListCell<BlockFactory> call(ListView<BlockFactory> param) {
            ListCell<BlockFactory> cell = new ListCell<BlockFactory>() {

                @Override
                protected void updateItem(BlockFactory item, boolean empty) {
                    super.updateItem(item, empty);
                    if (item != null) {
                        ImageView img = new ImageView(item.img);
                        Label label = new Label(item.desc);
                        HBox bar = new HBox(img, label);
                        bar.setSpacing(15);
                        bar.setAlignment(Pos.CENTER_LEFT);
                        setGraphic(bar);
                    }
                }
            };
            cell.setOnMousePressed((MouseEvent event) -> {
                if (cell.isEmpty()) {
                    event.consume();
                }
            });
            return cell;
        }
    };
}
Steel Talon
  • 81
  • 1
  • 3
2

A simple fix to Sergey's answer, without having to create a whole new listCell (in your case you do, but we don't have to in a regular text case).

all you need to simply do is create a temp variable that is equal to the first list item at first, and that gets changed to each new clicked item. Once you try to click the item again, the temp variable knows it's the same, and with an if statement you can get around that.

temp Item is a global that you put up top String tempItem = "admin";

for me I knew my first field is always going to be labeled "admin" so I set it as such. you would have to get the first entry and set it outside of the method you are going to use for list selecting.

private void selUser() throws IOException
    {
    String item = userList.getSelectionModel().getSelectedItem().toString();

        if(item != tempItem)
        {
           //click, do something
        }

        tempItem = item;    
}

As for me I used an FXML document that called my method

@FXML 
public void userSel(MouseEvent mouse) throws IOException
{
selUser();

}

In this case you could just take the entire contents of the selUser() method and put it into the userSel mouse click.

XaolingBao
  • 1,034
  • 1
  • 18
  • 34
0

Get old position Index (after search result in listview), new Position Index, values etc:

listView.setOnMouseClicked(me -> {
    System.out.println("clicked on " + listView.getSelectionModel().getSelectedItem());

    int newPosition = listView.getSelectionModel().getSelectedIndex();
    System.out.println("New Position is: " + newPosition);

    String item = listView.getSelectionModel().getSelectedItem();
    int oldPosition = observableMainList.indexOf(item); // the main list which populated the listView 
    System.out.println("Old position is: " + oldPosition);

    String ayahRanges = ayahNumbers.get(oldPosition); // another list which you store some another value 
    System.out.println("Actual AyaRanges: " + ayahRanges);
});
0009laH
  • 1,960
  • 13
  • 27
Noor Hossain
  • 1,620
  • 1
  • 18
  • 25