JavaFX event.getPickResult().getIntersectedNode() връща различни резултати за щракване върху бутон

първият въпрос за Java (вероятно от много!). Уча Java и JavaFX с помощта на Eclipse и плъгина e(fx)clispse със Scene Builder 2.0. Създадох малък прозорец с няколко бутона, TableView и ImageView в AnchorPane. Всичко добре досега.

Първоначалният ми въпрос щеше да бъде как да идентифицирам избрания бутон, ако имам само един регистриран метод за всички бутони (и двата в моя случай!). Можех да видя, че системата разполага с цялата информация (като направих System.out.println за event.getSource() и event.PickResult()). Въпреки това, преди да публикувам този въпрос, преодолях проблемите, но това доведе до още няколко въпроса;

1) За проблема с getSource() исках да стигна до информацията за източника на бутона и очаквах да мога да го направя в един ред. т.е. event.getSource().[ очаквам Eclipse да ми даде методи за Типа, върнат от getSource() в списъка с предложения]. Въпреки това, след event.getSource()....предложеният списък с методи са тези, които според мен са наследени от обектния метод. Това, което трябваше да направя вместо това, беше да разделя изявлението, както следва:

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

Защо след първия event.getSource().. getSource() не е наличен отново като предложение?

Явно има празнина в моето разбиране. В крайна сметка списъкът с методи, които получавам след System.out.[method list] е различен от System.in.[method list]. Може ли някой да изясни?

2) Вторият въпрос беше относно getPickResult(). Когато използвах event.getPickResult().getIntersectedNode().getId(), получавах нулев резултат? Не може да е прав, помислих си, че отпечатването на event.getPickResult() показа, че има цялата информация, от която мога да се нуждая. След това опитах event.getPickResult().getIntersectedNode().getParent().getId() и това проработи! Аха, помислих си, но уви, твърде рано. Това, което открих е, че дали съм получил нулев резултат, идентификаторът на бутона или идентификаторът на закотвящия панел (!) зависи от това къде точно върху бутона, който съм щракнал. Ударът в средата на текста на бутона доведе до нулев резултат, леко извън центъра създаде идентификатора на бутона, а външните области на бутона (но с курсор на мишката все още изцяло вътре в бутона) дадоха идентификатора на AnchorPane. Това за мен прави PickResult изключително ненадежден. Трябва да има свойства, които позволяват това да се случи. Може ли някой да ме просветли.

Също свързано с въпрос 1, event.getPickResult().getandSoOn....винаги създава списък с правилните налични методи след всеки период. Объркан съм защо това се случва, защото getPickResult връща PickResult и в подобен дух getSource връща EventType.

Благодаря за всякакви прозрения.

Приложен е код за контролера. Много System.out, за да видите какво се случва. Основният е стандартно генериран код за FX проект. Също така създаде FXML кода, ако искате точно да копирате оформлението на прозореца.

Код на контролера:

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());
    }

И FXML файла:

<?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>

person wyc73    schedule 19.08.2014    source източник
comment
Обичайният подход би бил просто да се използва различен метод на обработка за всеки бутон. Имате ли случай на употреба, който налага да правите това по различен начин? Освен това вероятно трябва да използвате onAction вместо onMouseClicked за бутон.   -  person James_D    schedule 19.08.2014
comment
Трябва да се съглася с вашите коментари. Реалността е, че като имате един манипулатор на събития и след това определяте кой бутон е бил щракнат, вие просто дублирате това, което системата вече е направила за вас. Това беше по-скоро като упражнение за кодиране и обучение, което продължих.   -  person wyc73    schedule 20.08.2014
comment
Вие всъщност заменяте полиморфизма (различни реализации на интерфейса EventHandler, когато използвате различни манипулатори) с превключвател (или еквивалентна последователност от if/else конструкции). Това просто като цяло е лоша практика (ще стане грозно, когато имате 200 елемента от менюто) и на теория производителността е по-лоша (макар че във всяко реално приложение няма да забележите разлика).   -  person James_D    schedule 20.08.2014


Отговори (1)


Не съм запознат с JavaFX и всички неща наоколо. Но с вашия малък бутон на слушателя за щракване мога да ви помогна. Това е нормално импл. модел за слушатели на събития и общо решение.

Първо, в зависимост от това, че всеки вид джаджа може да изпраща събития от същия тип, източникът на събитието трябва да бъде обект. Но това не е проблем, вие сте разработчикът и знаете кой слушател към коя джаджа сте свързали. Ако само един слушател слуша за една джаджа (например вградена), винаги можете да игнорирате източника, защото знаете единствения. Когато използвате повторно един слушател (например класа, който държи уиджетите) за няколко уиджета, трябва да проверите източника, за да разберете кой е извикал събитието.

Най-лесният начин да направите това е

widgetObject == event.getSource()

Така че първото намерение би било, argh сравняване на обект с "==". Но това не е проблем в тази ситуация, защото event.getSource() е същият екземпляр като widgetObject, който е изпратил събитието. Така че е възможно сравнение на обекта.

Друг начин, ако вашият слушател е по-сложен или екземплярът на приспособлението е динамичен, можете да проверите вида на обекта чрез "instanceof" и след това да прехвърлите към желаното изпълнение. Обикновено в GUI рамка всяка джаджа има реализация setData / getData, където можете да поставите всичко, което искате. Така че уликата е:

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

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

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

   ...

  }
}

Виждате, че чрез комбинацията от "instanceof" и джоба за данни в джаджа можете да внедрите толкова сложни слушатели, колкото искате. Освен това динамичното генериране на джаджи не е проблем по този начин и интерфейсът остава чист.

ЗАБЕЛЕЖКА: Кодът по-горе е повече псевдокод, отколкото истински код. Enum например трябва да бъде Enum impl. вие предоставяте ;)

Надявам се това да помогне.

person Rene M.    schedule 19.08.2014
comment
Благодаря за обратната връзка. Вече знам как да използвам ключова дума instanceof! След като сте го опитали, трябва да сте малко внимателни. Добавих няколко квадратчета за отметка и ги насочих към същия манипулатор на събития и редът event.getSource() instanceof Button ще бъде верен, когато щракнете и върху тях (и двата бутона и квадратчетата за отметка са подтипове на един и същ родител). Вашият кастинг за бутон също проработи. Мислех, че няма, защото event.getSource() връща EventType и следователно ще получите несъответствие на типа, но не се оплака! - person wyc73; 20.08.2014