2015-09-23 5 views
0

В приложении JavaFX javafx.application.Application должно быть подклассифицировано, и унаследованный метод launch(), хотя он является общедоступным, должен быть вызван из этого производного класса, в противном случае создается исключение. Затем метод launch() использует отражение для создания производного класса, что затрудняет установку значений для членов класса без потери их при запуске. Все, что мне кажется совершенно необычным, и мне было интересно, почему запуск приложения JavaFX настолько сложный, если у этого типа дизайна программного обеспечения (шаблон дизайна?) Есть имя или это просто плохой дизайн?Разработка программного обеспечения JavaFX

EDIT:

Чтобы быть более конкретным, я хочу использовать шаблон наблюдателя, поэтому моя Java-приложение получает уведомление, когда документ был загружен, например:

public class MyDocumentLoader extends Application 
{ 
    private ChangeListener<Worker.State> changeListener; 

    public void setChangeListener(ChangeListener<Worker.State> changeListener) 
    { 
     this.changeListener = changeListener; 
    } 

    ... 

    public void loadDocument(String url) 
    { 
     webEngine.getLoadWorker().stateProperty().addListener(changeListener); 
     webEngine.load(url); 
    } 

    ... 

} 

Мне нужна callback в нескольких методах, и в идеале я могу иметь более одного экземпляра класса, который загружает документы, поэтому я могу установить различные ChangeListeners для разных URL-адресов.

+1

Возможно, вы могли бы указать более конкретный пример того, где это создает трудности, возможно, кто-то сможет предложить соответствующий рефакторинг вашего кода. –

+0

ОК, но этот класс выглядит очень маловероятным кандидатом для подкласса 'Application'. Разве это не просто отдельный класс, который используется приложением? Подкласс 'Application' представляет собой реальное приложение в целом. –

+0

Да, это должен быть отдельный класс с несколькими экземплярами. Мое намерение задать этот вопрос было именно тем, что я не хочу, чтобы приложение использовало мой класс. Я хочу, чтобы мое приложение Java использовало JavaFX. Мой основной класс в другом месте и его изменение в подклассу класса приложений javafx полностью исключается. –

ответ

2

JavaFX поддерживает большое количество стратегий развертывания и упаковки, ref. https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html, а также стандартизованная точка входа и выхода из жизненного цикла упрощает поддержку всех этих стратегий.

Если вы пытаетесь инициализировать свой основной класс приложения, из-за того, что он запускается при запуске JavaFX, лучшим вариантом является использование методов Application.init() и Application.stop(), как указывает James_D.

+0

Выбор этого решения. Это действительно не имеет значения, так как другие ответы были полезными. –

7

Я предполагаю, что этот дизайн был мотивирован (огромным) числом приложений Swing, которые были неправильно написаны, причем «первичный» JFrame s был создан и показан на неправильной нити (то есть не на потоке отправки событий AWT). Я предполагаю, что так много приложений Swing были неправильно написаны, что им пришлось защищать фреймворк от неправильного использования и что они хотели избежать этого сценария с помощью JavaFX.

Форсирование (ну, почти форсирование, есть хаки), приложение FX для запуска этого способа затрудняет неправильное создание приложения аналогичным образом. launch метод (и эквивалентный процесс запуска Oracle JVM, если у вас есть Application подкласса без метода main и призыва к launch) делают совсем немного шаблонную работу: он запускает FX инструментарий, инстанцирует Application подкласса и вызывает его метод init() , то в потоке приложения FX он создает первичный Stage и передает его методу Application подкласса start(...). Затем это гарантирует, что все работает на правильной нити.

Вы должны в основном рассмотреть метод start(...) в приложении JavaFX в качестве замены для метода main(...) в «традиционном» Java-приложении с пониманием, которое оно вызывается в потоке приложения FX.

Моя рекомендация заключается в том, что подкласс Application должен быть как можно более минимальным; он должен просто делегировать что-то еще, чтобы фактически создать пользовательский интерфейс, а затем просто разместить его на первом этапе и показать его. Включите метод main, который ничего не делает, кроме вызова launch(...) в качестве резервной копии для JVM, не поддерживающих JavaFX. У вас должен быть только один экземпляр одного подкласса Application, присутствующего в любой JVM. Таким образом, ваш подкласс Application не имеет членов класса для установки, и поэтому описанные вами проблемы просто не возникают.

Если вы используете FXML, это на самом деле довольно естественно: метод start(...) по существу просто делегирует пару FXML-контроллеров для выполнения реальной работы. Если вы не используете FXML, создайте отдельный класс для выполнения фактического макета и т. Д. И делегируйте его. См. this related question, который получает ту же идею.

Заметим также, что ваше заявление

метод наследуется запуска(), хотя это публично, должно быть названо внутри этого производного класса

не совсем точно, так как есть overloaded form of the launch(...) method, в котором вы можете указать подкласс приложения. Так что, если вам действительно нужно, вы можете просто создать заглушку для запуска FX инструментарий:

public class FXStarter extends Application { 

    @Override 
    public void start(Stage primaryStage) { 
     // no-op 
    } 
} 

Теперь вы можете сделать:

public class MyRegularApplication { 

    public static void main(String[] args) { 
     // start FX toolkit: 
     new Thread(() -> Application.launch(FXStarter.class)).start(); 
     // other stuff here... 
    } 
} 

Обратите внимание, что launch не возвращается, пока FX инструментарий не отключит , поэтому необходимо поместить этот вызов в другой поток. Это потенциально создает условия гонки, где вы можете попробовать сделать что-то нуждаясь в FX инструментарий, прежде чем launch(...) фактически инициализируются его, так что вы, вероятно, следует остерегаться, что:

public class FXStarter extends Application { 

    private static final CountDownLatch latch = new CountDownLatch(1); 

    public static void awaitFXToolkit() throws InterruptedException { 
     latch.await(); 
    } 

    @Override 
    public void init() { 
     latch.countDown(); 
    } 

    @Override 
    public void start(Stage primaryStage) { 
     // no-op 
    } 
} 

, а затем

public class MyRegularApplication { 

    public static void main(String[] args) throws InterruptedException { 
     // start FX toolkit: 
     new Thread(() -> Application.launch(FXStarter.class)).start(); 
     FXStarter.awaitFXToolkit(); 
     // other stuff here... 
    } 
} 

SSCCE (Я просто использовал внутренние классы для всего, так что это удобно работать, но в реальной жизни это было бы автономные классы):

import java.util.Random; 
import java.util.concurrent.CountDownLatch; 

import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.geometry.Pos; 
import javafx.scene.Scene; 
import javafx.scene.control.Button; 
import javafx.scene.control.Label; 
import javafx.scene.layout.VBox; 
import javafx.stage.Stage; 

public class BackgroundProcessDrivenApp { 

    public static void main(String[] args) throws InterruptedException { 
     Platform.setImplicitExit(false); 
     new Thread(() -> Application.launch(FXStarter.class)).start(); 
     FXStarter.awaitFXToolkit(); 
     new MockProcessor().doStuff() ; 
    } 

    public static class FXStarter extends Application { 

     private static final CountDownLatch latch = new CountDownLatch(1); 

     @Override 
     public void init() { 
      latch.countDown(); 
     } 

     public static void awaitFXToolkit() throws InterruptedException { 
      latch.await(); 
     } 

     @Override 
     public void start(Stage primaryStage) { } 
    } 

    public static class MockProcessor { 

     private final int numEvents = 10 ; 

     public void doStuff() { 
      Random rng = new Random(); 
      try { 
       for (int event = 1 ; event <= numEvents; event++) { 
        // just sleep to mimic waiting for background service... 
        Thread.sleep(rng.nextInt(5000) + 5000); 
        String message = "Event " + event + " occurred" ; 
        Platform.runLater(() -> new Messager(message).showMessageInNewWindow()); 
       } 
      } catch (InterruptedException exc) { 
       Thread.currentThread().interrupt(); 
      } finally { 
       Platform.setImplicitExit(true); 
      } 
     } 
    } 

    public static class Messager { 
     private final String message ; 

     public Messager(String message) { 
      this.message = message ; 
     } 

     public void showMessageInNewWindow() { 
      Stage stage = new Stage(); 
      Label label = new Label(message); 
      Button button = new Button("OK"); 
      button.setOnAction(e -> stage.hide()); 
      VBox root = new VBox(10, label, button); 
      root.setAlignment(Pos.CENTER); 
      Scene scene = new Scene(root, 350, 120); 
      stage.setScene(scene); 
      stage.setAlwaysOnTop(true); 
      stage.show(); 
     } 
    } 
} 
Смежные вопросы