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.[список методов], отличается от System.in.[список методов]. Может ли кто-нибудь прояснить?

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, чтобы увидеть, что происходит. Main — это стандартный сгенерированный код для проекта 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()

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

Другой способ, если ваш слушатель более сложный или экземпляр виджета динамический, вы можете проверить тип объекта с помощью «instanceof», а затем привести его к желаемой реализации. Обычно в среде графического интерфейса любой виджет имеет реализацию 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 будет иметь значение true, когда вы нажмете на них (и кнопка, и флажки являются подтипами одного и того же родителя). Ваш кастинг на кнопку тоже сработал. Я думал, что это не так, потому что event.getSource() возвращает EventType и, следовательно, вы получите несоответствие типов, но это не жаловалось! - person wyc73; 20.08.2014