Скалирование имени в приватных полях и внедрение JavaFX FXML
Следующий пример и пояснения довольно длинные, поэтому суть моего вопроса в том, как бороться с искажением имен в приватных полях при использовании фреймворка, который настаивает на внедрении полей (на полях, которые действительно должны оставаться закрытыми)?
Я пишу приложение в Scala, используя ScalaFX/JavaFX и FXML. Когда вы используете FXML для определения ваших представлений в JavaFX, объекты, определенные в FXML (такие как кнопки и текстовые поля), вводятся в контроллер с помощью:
- добавив
fx:id
свойство к элементам FXML - добавление (обычно приватных) полей в контроллер, с
@FXML
аннотации и с именами полей, соответствующими значениямfx:id
свойства, определенные в FXML - когда
FXMLoader
создает экземпляр контроллера, он автоматически внедряетfx:id
аннотированные элементы в соответствие@FXML
аннотированные поля контроллера через рефлексию
Я не большой поклонник инъекций, но так работает FXML. Тем не менее, я столкнулся с неожиданными осложнениями в Scala из-за искажения имен полей, выполняемого компилятором при некоторых обстоятельствах...
Вот пример приложения:
test / TestApp.scala (ничего интересного, просто нужно запустить пример)
package test
import javafx.application.Application
import javafx.fxml.FXMLLoader
import javafx.scene.{Scene, Parent}
import javafx.stage.Stage
object TestApp {
def main(args: Array[String]) {
Application.launch(classOf[TestApp], args: _*)
}
}
class TestApp extends Application {
override def start(primaryStage: Stage): Unit = {
val root: Parent = FXMLLoader.load(getClass.getResource("/test.fxml"))
val scene: Scene = new Scene(root, 200, 200)
primaryStage.setTitle("Test")
primaryStage.setScene(scene)
primaryStage.show()
}
}
test.fxml (вид)
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="test.TestController">
<children>
<CheckBox fx:id="testCheckBox" mnemonicParsing="false" text="CheckBox"/>
<Button fx:id="testButton" mnemonicParsing="false" text="Button"/>
</children>
</VBox>
test / TestController.scala (контроллер для представления test.fxml)
package test
import javafx.fxml.FXML
import javafx.scene.{control => jfxsc}
import scalafx.Includes._
class TestController {
@FXML private var testCheckBox: jfxsc.CheckBox = _
@FXML private var testButton: jfxsc.Button = _
def initialize(): Unit = {
println(s"testCheckBox=$testCheckBox")
println(s"testButton=$testButton")
testCheckBox.selected.onChange {
testButton.text = "changed"
}
}
}
При запуске приложения println
заявления показывают, что testCheckBox
впрыскивается правильно, но testButton
нулевой. Если я нажму на флажок, то, как и ожидалось, NullPointerException
при звонке testButton.text_=
,
Причина вполне очевидна при взгляде на скомпилированные классы:
- E сть
TestController$$anonfun$initialize$1
класс, для анонимной функции, переданной вtestCheckBox.selected.onChange()
вinitialize()
метод - в
TestController
класс, есть два приватных поля:testCheckBox
(как и ожидалось) иtest$TestController$$testButton
(а не простоtestButton
) и методы доступа / мутатора. Из них только метод доступа дляtest$TestController$$testButton
является публичным.
Ясно, что компилятор Scala исказил имя testButton
поле, потому что он должен был сделать свой метод доступа открытым (доступ к нему из TestController$$anonfun$initialize$1
) и потому что методы field и accesor/mutator должны иметь одинаковое имя.
Теперь, наконец, вот мой вопрос: есть ли разумное решение, чтобы справиться с этой ситуацией? Прямо сейчас я сделал поля общедоступными: поскольку компилятору не нужно изменять их видимость, он не будет искажать их имя. Тем не менее, в этих сферах действительно нет бизнеса.
Примечание. Другим решением будет использование библиотеки scala-fxml, которая полностью скрывает внедрение полей, но я бы предпочел использовать стандартную загрузку FXML по другим причинам.
2 ответа
Вот как я объявляю свои введенные поля:
@FXML
var popoutButton: Button = _
IOW, оставь private
и все работает нормально. Чувствуется себя немного грязно, если вы пришли из мира Java, но обходные пути требуют гораздо больше кода.
Контроллеры подразумеваются fx:include
также отлично работает:
FXML:
<fx:include fx:id="paramTable" source="ParameterTable.fxml" />
Скала:
@FXML
var paramTable: TableView[(ModelParameter, ParameterValue)] = _
@FXML
var paramTableController: ParameterTableController = _
Вы можете аннотировать свои поля с помощью @BeanProperty, чтобы Scala не переписывала имя поля. Единственным недостатком, по-видимому, является то, что поле теперь должно быть как минимум защищено, или компилятор Scala жалуется.