0
votes

first Java question( probably of many! ). I'm learning Java and JavaFX using Eclipse and e(fx)clispse plugin with Scene Builder 2.0. I built a small window with a couple of buttons, a TableView and an ImageView in an AnchorPane. All good so far.

My original question was going to be how how to identify the button being selected if I have only one method registered for all buttons( all two of them in my case! ). I could see the system had all the info ( by doing System.out.println for event.getSource() and event.PickResult() ). However, before posting this question, I overcame the issues but it has led to a couple of more questions;

1) For the getSource() issue, I wanted to get down to the button source information and I was expecting to be able to do it in one line. i.e. event.getSource().[ expecting Eclipse to give me methods for the Type returned by getSource() in proposals list]. However, after event.getSource()....the proposal list of methods are those which I believe are inherited from the object method. What I had to do instead was break out the statement as follows:

EventObject evo = new EventObject(event.getSource());
Object obj = evo.getSource();
Button btnMirror = (Button)obj;
System.out.println("Button id is:" + btnMirror.getId());

Why after the first event.getSource().. is getSource() not available again as a proposal?

There's clearly a gap in my understanding. After all, the list of methods I get after System.out.[method list] is different to System.in.[method list]. Could someone clarify?

2) The second question was regarding getPickResult(). When using event.getPickResult().getIntersectedNode().getId(), I was getting a null result? Can't be right I thought as printing event.getPickResult() showed it has all the info I could need. Then I tried event.getPickResult().getIntersectedNode().getParent().getId() and that worked! Aha, I thought, but alas, too early. What I discovered is that whether I got a null result, the button id, or the Anchor Pane id(!) is dependent on where exactly on the button I clicked. Bang in the middle on the button text produced a null result, slightly off centre produced the button id and the outer regions of the button( but with mouse cursor still fully inside the button ) produced the AnchorPane id. This to me makes PickResult extremely unreliable. There must be properties which are allowing this happen to. Could anyone enlighten me.

Also related to question 1, event.getPickResult().getandSoOn....always produced a list of the correct available methods after each period. Confused why this happens because the getPickResult returns a PickResult and in a similar vein, getSource return an EventType.

Thanks for any and all insights.

Attached is code for the Controller. Lots of System.out's to see what's going on. The Main is standard generated code for an FX project. Also produced the FXML code if you want to exactly replicate the window layout.

Controller code:

package myapplication;

import java.net.URL;
import java.util.EventObject;
import java.util.ResourceBundle;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventType;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.PickResult;
import javafx.scene.layout.AnchorPane;

public class IrfController implements Initializable {

    @FXML private AnchorPane myAnchorPane;
    @FXML private Button btnClickMe;
    @FXML private Button btnAddRow;
    @FXML private TableView<Person> myTV;
    @FXML private TableColumn<Person, String> myCol1;
    @FXML private TableColumn<Person, String> myCol2;

    final ObservableList<Person> data = FXCollections.observableArrayList(
            new Person("John","Smith"),
            new Person("Adam", "West"));

     @FXML protected void btnClick(MouseEvent event) {
/**Get source and get Pick Result both have the info**/      
         System.out.println( "Event GetSource: " + event.getSource());
         System.out.println( "Event PickResult:" + event.getPickResult());   
/** the id's of the button **/       
         System.out.println("Add Row buton id: " +  btnAddRow.getId());
         System.out.println("Click Me button id: " +  btnClickMe.getId());
/**get the Node of the pick result - a direct click returns null **/     
         System.out.println("Pick Result Node id: " + event.getPickResult().getIntersectedNode().getId());

        EventObject evo = new EventObject(event.getSource());
        Object obj = evo.getSource();
        Button btnMirror = (Button)obj;
        System.out.println("Button id is:" + btnMirror.getId());

       System.out.println( "Pick Result: " + event.getPickResult().getIntersectedNode().getParent().getId());
        PickResult pkr = event.getPickResult();
        Node nd = pkr.getIntersectedNode().getParent();
        System.out.println("Parent Node id: " + nd.getId());

        switch (btnMirror.getId()) {
        case "btnClickMe": System.out.println("You selected the Click Me Button"); break;
        case "btnAddRow":  System.out.println("You selected the Add Row Button"); break;
        default: System.out.println("No button registered"); break;
        }

        System.out.println("Event Type: " + event.getEventType().getName());
    }
    @FXML
    public void onActionFired(ActionEvent event){
        System.out.println("In ActionFired - Start");
        System.out.println(event.getClass().toGenericString()); 

        System.out.println(event.toString());
        System.out.println(event.getEventType().toString());
        System.out.println("In ActionFired - End");
    }

    @FXML protected void clickAnchor(MouseEvent event) {
        System.out.println(event.toString());
        System.out.println("Hello Click Anchor!");
        System.out.println(event.getEventType().toString());
    }

And the FXML file:

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

<?import javafx.scene.effect.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane fx:id="myAnchorPane" onMouseClicked="#clickAnchor" prefHeight="441.0" prefWidth="578.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myapplication.IrfController">
   <children>
      <Button fx:id="btnClickMe" layoutX="481.0" layoutY="135.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onMouseClicked="#btnClick" prefHeight="50.0" prefWidth="83.0" text="ClickMe" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="135.0">
         <opaqueInsets>
            <Insets />
         </opaqueInsets>
         <effect>
            <Glow />
         </effect></Button>
      <ImageView fitHeight="100.0" fitWidth="143.0" layoutX="35.0" layoutY="22.0" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="344.0" AnchorPane.topAnchor="22.0">
         <image>
            <Image url="@../../../../../Users/Ayesha/Pictures/2014-03-06/IMG_0324.JPG" />
         </image>
      </ImageView>
      <TableView fx:id="myTV" layoutX="35.0" layoutY="135.0" prefHeight="264.0" prefWidth="396.0" AnchorPane.bottomAnchor="42.0" AnchorPane.leftAnchor="35.0" AnchorPane.rightAnchor="147.0" AnchorPane.topAnchor="135.0">
        <columns>
          <TableColumn fx:id="myCol1" prefWidth="75.0" text="First Name" />
          <TableColumn fx:id="myCol2" prefWidth="100.0" text="Last Name" />
            <TableColumn fx:id="myCol3" prefWidth="140.0" text="Something Else" />
        </columns>
      </TableView>
      <Button fx:id="btnAddRow" layoutX="481.0" layoutY="204.0" mnemonicParsing="false" onMouseClicked="#btnClick" prefHeight="50.0" prefWidth="83.0" text="Add Row" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="204.0" />
   </children>
</AnchorPane>
1
The usual approach would just be to use a different handler method for each button. Is there a use case you have that necessitates you doing this differently? Also, you should probably use onAction instead of onMouseClicked for a Button.James_D
I have to agree with your comments. Reality is that by having a single event handler and then working out which button has been clicked, you are just duplicating what the system has already done for you. It was more as a coding and learning exercise I persisted.wyc73
You're actually replacing polymorphism (different implementations of the EventHandler interface, when you use different handlers) with a switch (or equivalent sequence of if/else constructs). That's just generally bad practice (it will get ugly when you have 200 menu items) and in theory performance is worse (though in any real application you won't notice a difference).James_D

1 Answers

0
votes

I'm not familar with JavaFX and all the stuff around. But with your small button on click listener I can help you. Its a normal impl. pattern for eventlisteners and a common solution.

First off all, depending on that every kind of widget could send events of the same type, the source of the event has to be object. But this is no problem, you are the developer and you know which listener you have connected to which widget. If only one listener is listening for one widget (inline for example) you can always ignore source because you know the only one. When you reusing one listener (for example the class holding the widgets) for a couple of widget you have to inspect the source to figure out who has called the event.

The easest way to do this is

widgetObject == event.getSource()

So first intent would be, argh comparing object with "==". But it's not a problem in this situation, because the event.getSource() is the same instance as the widgetObject that has send the event. So a compare on the object is possible.

A other way, if your listener is more complex or the widget instance is dynamicly, you can inspect the kind of object by "instanceof" and then cast to the desired implementation. Normaly in a GUI framework any widget has a setData / getData implementation where you can place anything you want. So the clue is:

if (event.getSource() instanceof Button) {
  Button eventBtn = (Button) event.getSource();

  Enum kindOfButton = (Enum) eventBtn.getData();

  switch (kindOfButton) {
   case SAVE:
     doSave();
   break;

   ...

  }
}

You see by the combination of "instanceof" and the data poket in a widget you can implement as complex listeners as you want. Also generating widgets dynamicly is no problem this way and the interface keeps clean.

NOTE: The code above is more pseudo code then real code. Enum for example should be Enum impl. you provide ;)

Hope that helps.