当前位置: 首页 > 知识库问答 >
问题:

JavaFX-MVPC模式-分离FXML对象和事件处理程序方法

阎作人
2023-03-14

  • 视图:简单FXML文件
  • 控制器:包含视图中定义的事件处理程序函数,更新表示模型
  • 表示模型:简单数据,包含可观察对象(ObjectProperty、ObservableList等)
  • presenter:包含FXML文件中由fx:id定义的JavaFX节点,将这些节点绑定到表示模型中的可观察对象,并处理其他表示函数,如弹出窗口。这就是JavaFX应用程序

正如您所注意到的,我的目的是将FXML对象(如@FXML Label Label)分离到PRESENTER并将FXML事件处理程序方法(如@FXML submit(Action event e){})分离到controller。

简而言之:我有一个FXML文件,其中包含fx:id=“passwordfield”等元素和onaction=“#browsesbx”等事件处理程序。我希望有两个独立的。java控制器,一个用于包含fx:ids的对象,一个用于处理事件方法。

我的问题是:有什么“干净”的方法可以做到这一点吗?还是我的计划有什么概念上的错误?

谢了!

共有1个答案

龙德义
2023-03-14

关于可用性,请注意:如果您将“操作”与“视图”完全分离(即,如果您的控制器确实对UI组件一无所知),事情可能会变得有点复杂。例如,time按钮的大部分操作都希望查看文本字段的状态等。您当然可以通过使用演示器将文本字段中的文本绑定到演示模型中的数据,然后让控制器调用模型上引用该状态的方法来实现这一点。问题是,控制器方法除了调用表示模型上的等效方法之外,基本上什么也不做;您最终得到的层实在是太薄了,没有发挥它的重量,而且架构看起来过于设计。

也就是说,如果你真的想尝试一下,这里有一个方法会奏效。

这里的主要障碍是FXMLLoader有一个与之关联的Controller实例。当它加载FXML时,它将带有fx:id属性的元素注入控制器,并将控制器中的“处理程序”方法与通过FXML中的onxxx属性指定的事件处理程序相关联。

后面的部分看起来像:

private void injectFieldsIntoPresenter(FXMLLoader loader, P presenter) throws IllegalArgumentException, IllegalAccessException  {
    Map<String, Object> namespace = loader.getNamespace() ;
    for (Field field : presenter.getClass().getDeclaredFields()) {
        boolean wasAccessible = field.isAccessible() ;
        field.setAccessible(true);
        if (field.getAnnotation(FXML.class) != null) {
            if (namespace.containsKey(field.getName())) {
                field.set(presenter, namespace.get(field.getName()));
            }
        }
        field.setAccessible(wasAccessible);
    }
}

当然,您的演示者还需要执行一些绑定,因此我们需要安排在注入字段后调用一个方法。对于控制器类,FXMLLoader调用任何public@fxml注释的方法,称为initialize()来完成此操作;因此,如果希望演示者具有相同的功能,可以执行以下操作:

private void initializePresenterIfPossible(P presenter) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    for (Method m : presenter.getClass().getDeclaredMethods()) {
        boolean wasAccessible = m.isAccessible() ;
        m.setAccessible(true);
        if ("initialize".equals(m.getName()) && m.getParameterCount() == 0) { 
            if ((m.getModifiers() & Modifier.PUBLIC) != 0 || m.getAnnotation(FXML.class) != null) {
                m.invoke(presenter);
            }
        }
        m.setAccessible(wasAccessible);
    }
}

(您可以在这里使用其他方案,例如使用javax.inject注释和简单地调用任何@PostConstruct注释的方法。)

package mvpc;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Map;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;

public class MVPCLoader<M, V, P, C> {

    private P presenter ;
    private C controller ;
    private V view ;
    private M model ;

    public V load(URL resource, M model, P presenter) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException  {

        if (view != null) {
            throw new IllegalStateException("FXML can only be loaded once by a MVPCLoader instance");
        }

        this.model = model ;
        this.presenter = presenter ;

        FXMLLoader loader = new FXMLLoader(resource);
        loader.setControllerFactory(this::controllerFactory);
        view =  loader.load();
        controller = loader.getController() ;
        injectInto(presenter, model);
        injectFieldsIntoPresenter(loader, presenter);
        initializePresenterIfPossible(presenter);
        return view ;
    }

    public P getPresenter() {
        return presenter ;
    }

    public M getModel() {
        return model ;
    }

    public C getController() {
        return controller ;
    }

    private void initializePresenterIfPossible(P presenter) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        for (Method m : presenter.getClass().getDeclaredMethods()) {
            boolean wasAccessible = m.isAccessible() ;
            m.setAccessible(true);
            if ("initialize".equals(m.getName()) && m.getParameterCount() == 0) { 
                if ((m.getModifiers() & Modifier.PUBLIC) != 0 || m.getAnnotation(FXML.class) != null) {
                    m.invoke(presenter);
                }
            }
            m.setAccessible(wasAccessible);
        }
    }

    private void injectFieldsIntoPresenter(FXMLLoader loader, P presenter) throws IllegalArgumentException, IllegalAccessException  {
        Map<String, Object> namespace = loader.getNamespace() ;
        for (Field field : presenter.getClass().getDeclaredFields()) {
            boolean wasAccessible = field.isAccessible() ;
            field.setAccessible(true);
            if (field.getAnnotation(FXML.class) != null) {
                if (namespace.containsKey(field.getName())) {
                    field.set(presenter, namespace.get(field.getName()));
                }
            }
            field.setAccessible(wasAccessible);
        }
    }

    private C controllerFactory(Class<?> type) {
        try {
            @SuppressWarnings("unchecked")
            C controller = (C) type.newInstance();
            injectInto(controller, model);
            return controller ;
        } catch (Exception exc) {
            if (exc instanceof RuntimeException) throw (RuntimeException)exc ;
            throw new RuntimeException(exc);
        }
    }

    private void injectInto(Object target, Object value) throws IllegalArgumentException, IllegalAccessException  {
        for (Field field : target.getClass().getDeclaredFields()) {
            boolean wasAccessible = field.isAccessible() ;
            field.setAccessible(true);
            if (field.get(target) == null && field.getType() == value.getClass() && field.getAnnotation(FXML.class) != null) {
                field.set(target, value);
            }
            field.setAccessible(wasAccessible);
        }
    }
}

通过查看afterburner.fx的源代码,可以获得实现此目的的技术。

下面是一个使用该类的快速测试:

package mvpc;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

public class PresentationModel {

    private final IntegerProperty count = new SimpleIntegerProperty();

    public IntegerProperty countProperty() {
        return count ;
    }

    public final int getCount() {
        return countProperty().get();
    }

    public final void setCount(int count) {
        countProperty().set(count);
    }

    public final void increment() {
        setCount(getCount() + 1);
    }
}
package mvpc;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class Presenter {

    @FXML
    private PresentationModel model ;

    @FXML
    private Label display ;

    public void initialize() {
        display.textProperty().bind(model.countProperty().asString("Count: %d"));
    }
}
package mvpc;
import javafx.fxml.FXML;

public class Controller {

    @FXML
    private PresentationModel model ;

    @FXML
    private void increment() {
        model.increment();
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="mvpc.Controller" spacing="5" alignment="CENTER">
    <padding>
        <Insets top="10" left="10" bottom="10" right="10"/>
    </padding>
    <Label fx:id="display"/>
    <Button text="Increment" onAction="#increment"/>
</VBox>
package mvpc;

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MVPCTest extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        PresentationModel model = new PresentationModel();
        Presenter presenter = new Presenter();
        MVPCLoader<PresentationModel, Parent, Presenter, Controller> loader = new MVPCLoader<>();
        Scene scene = new Scene(loader.load(getClass().getResource("View.fxml"), model, presenter));
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
 类似资料:
  • 我想在JavaFX中制作一个程序,其中包含一个按钮,单击该按钮时,将创建一个圆并将其添加到形状的ArrayList中。以下是我的代码: 我的问题是-如何从内部句柄方法访问“circle1”?在JavaScript中,我们使用e.currentTarget。 我无法声明“Circle1”最终版本,因为我需要在之后更改它。

  • 问题内容: 我在JavaFX中有一个舞台,可以通过多种方式关闭该舞台,方法是单击红色(X)或通过一个调用 无论舞台如何关闭,我都希望在舞台关闭之前(或之后)执行操作。 如果我使用以下代码: 然后当我单击(X)时调用处理程序,但当我调用 不同之处在于,他希望在整个应用程序关闭时调用处理程序,因此可以覆盖的方法。但是,我并没有关闭整个应用程序,只是一个阶段。并且没有重写的方法。 谢谢你的帮助。 问题答

  • 我在JavaFX中有一个阶段,可以通过多种方式关闭,通过单击红色(X)或通过调用的按钮 无论舞台如何关闭,我都想在舞台关闭之前(或关闭时)执行一个动作。 如果我使用以下代码: 然后,当我单击(X)时调用处理程序,但当我调用<code>myStage.close()</code>时不会调用 这与这个问题讨论的问题相同(有一个关键区别):JavaFX:Stage-close-handler 不同之处在

  • 我正在研究javaFX上事件处理程序的机制,但我不确定我是否理解了它,事实上我有一点怀疑:如果我有两个对象,它们有处理事件的所有必要代码(EventHandler接口ECC..),它们属于同一stackPane,问题是:有没有一种方法让第一个对象启动一个事件(例如ActionEvent),尽管它们属于同一Pane但将由两个对象处理?因为对于我所理解的“事件路线”来说,这是不可能的,至少是直接的。实

  • 我正在尝试在javaFx中为特殊需要定制一个快捷方式系统。 这种特殊需求使得不可能使用KeyCombinaison(只限制一个键修饰符是不可接受的)。 我已经做了我适当的KeyCompin联络员系统,现在我想从节点调用一个处理程序(我在控制器之外)。但是我找不到任何优雅的解决方案来执行这个。 有一个按钮声明: 在我想从我的快捷方式代码调用控制器的操作之后。 和标准控制器。 我可以做一些工作,例如使

  • 本文向大家介绍学习JavaScript事件流和事件处理程序,包括了学习JavaScript事件流和事件处理程序的使用技巧和注意事项,需要的朋友参考一下 本文全篇介绍了JavaScript事件流和事件处理程序,分享给大家供大家参考,具体内容如下 一、事件流 事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。 二、事件冒泡