AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 78533187
Accepted
SquidXTV
SquidXTV
Asked: 2024-05-26 01:17:29 +0800 CST2024-05-26 01:17:29 +0800 CST 2024-05-26 01:17:29 +0800 CST

FXMLLoader setController vs setControllerFactory

  • 772

No JavaFX, FXMLLoadercria automaticamente o controlador especificado em um arquivo .fxml usando o construtor 0-arg. Ele também fornece métodos para definir/criar o controlador manualmente usando FXMLLoader#setControllerou FXMLoader#setControllerFactory.

Minha pergunta agora é qual é a diferença entre eles? Existem situações em que um é melhor que o outro?

A documentação de ambos os métodos realmente não responde à minha pergunta.

java
  • 2 2 respostas
  • 64 Views

2 respostas

  • Voted
  1. Best Answer
    James_D
    2024-05-26T23:02:03+08:002024-05-26T23:02:03+08:00

    Quando você carrega um arquivo FXML com um dos load(...)métodos definidos em FXMLLoader, na configuração padrão dois objetos importantes são criados pelo FXMLLoader:

    1. O root, que corresponde ao elemento raiz da estrutura FXML
    2. O controller, que é um objeto (uma instância específica) conectado ao rootde várias maneiras pelo FXMLLoader.

    Como você não pode especificar um objeto real com um valor de string de um atributo XML (ou seja, o valor fornecido para fx:controller="..."), a maneira padrão como isso funciona é especificar o nome da classe do controlador no fx:controlleratributo. Por padrão, o FXMLLoadercria a instância do controlador invocando reflexivamente o construtor de argumento zero da classe especificada.

    Ele FXMLLoaderinjeta @FXMLelementos anotados do arquivo FXML em campos correspondentes na instância do controlador que ele criou. Feito isso, ele chama o initialize()método, se houver, permitindo uma inicialização bastante intuitiva do controlador e das propriedades da visualização associada.

    Algumas consequências disso são dignas de nota (nem todas são relevantes para esta questão):

    • Se você chamar FXMLLoader.load(...)suas variantes várias vezes, várias instâncias da classe do controlador serão criadas. Isso é (quase?) sempre o que você deseja, mas é importante notar que o estado não persistirá de uma instância do controlador para outra.
    • Se a classe do controlador não tiver um construtor com argumento zero, um erro será gerado. Este erro é necessariamente gerado em tempo de execução, pois a compilação não pode verificar a validade da chamada reflexiva do construtor.

    É comum querer fornecer dados de alguma forma ao controlador. Por exemplo, em arquiteturas do tipo MVC/MVP/MVVM, o controlador (ou equivalente) geralmente precisa de acesso a um objeto de modelo. Você pode conseguir isso com a configuração padrão recuperando uma referência ao controlador após o FXML ser carregado e passando-o:

    Model model = ...;
    
    FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/view.fxml"));
    Parent root = loader.load();
    MyController controller = loader.getController();
    controller.setModel(model);
    

    Embora esta abordagem seja viável, ela tem várias desvantagens:

    • O modelo não é fornecido até que o FXML seja carregado e, consequentemente, depois que o initialize()método seja invocado. Isso significa que qualquer inicialização que deva ser realizada e que dependa do modelo deve ser feita como resultado da setModel()chamada, e não nos locais mais intuitivos (como o initialize()método).
    • O modelo não pode estar finalna classe do controlador (o que geralmente é natural e desejável) e não existe uma maneira elegante de impor a configuração do modelo apenas uma vez.
    • A classe do controlador associada à visualização é codificada no código que carrega o FXML, o que possivelmente viola o encapsulamento do par visualização-controlador.
    • O código é um pouco detalhado.

    As duas primeiras desvantagens podem ser corrigidas chamando setController(...)o FXMLLoader antes de carregar o FXML. No entanto, isso introduz novas desvantagens:

    Model model = ... ;
    MyController controller = new MyController(model);
    FXMLLoader loader = new FXMLLoader(getClass().getResources("path/to/view.fxml"));
    loader.setController(controller);
    Parent root = loader.load();
    

    Observe que, neste caso, podemos passar uma instância do modelo para o construtor do controlador. Isso significa que o modelo pode ser final e estará disponível assim que o controlador for instanciado. Portanto, o controlador pode se parecer com:

    public class MyController {
    
        private final Model model;
    
        @FXML
        private Label fooLabel;
    
        public MyController(Model model) {
            this.model = model;
        }
    
        @FXML
        private void initialize() {
            fooLabel.setText(model.getFoo());
        }
    }
    

    O que acontece quando chamamos setController(...)é que substituímos completamente o mecanismo padrão para criar um controlador, em vez disso, fornecemos uma instância de controlador de nossa escolha. Por esta razão, agora não podemos especificar um fx:controlleratributo no arquivo FXML; seria, na melhor das hipóteses, redundante e possivelmente contraditório, e uma exceção será lançada se o fizermos.

    As duas últimas desvantagens acima (verbosidade e conexão explícita no código entre a visão e a classe do controlador) ainda existem, embora tenhamos resolvido as outras duas que são provavelmente mais importantes. No entanto, não poder especificar a classe do controlador no arquivo FXML significa que introduzimos uma nova desvantagem:

    • Ferramentas de desenvolvedor, como SceneBuilder ou seu IDE, não têm mais como determinar a classe do controlador que está sendo usada para o arquivo FXML. Isso significa que eles não podem mais destacar o nome do método do manipulador de eventos ou erros de digitação no nome do campo injetado, ou criar stubs deles para nós. Isso pode ser uma grande desvantagem em termos de produtividade.

    Por outro lado, a fábrica de controladores nos permite usar o mecanismo padrão de criação de uma instância de controlador (ou seja, especificar a classe no fx:controlleratributo e permitir que ele FXMLLoaderforneça a instância), mas nos permite configurar como a instância é criada.

    A fábrica do controlador é um Callback<Class<?>, Object>, que é essencialmente uma função que mapeia a Class<?>(um tipo) para um objeto que será usado como controlador. Deve ser uma instância da classe fornecida, mas não há verificação real para isso.

    O exemplo anterior pode ser reescrito

    Model model = ... ;
    FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/view.fxml"));
    loader.setControllerFactory(type -> new MyController(model));
    Parent root = loader.load();
    

    Neste caso podemos (e devemos) ter fx:controller="com.example.MyController"no arquivo FXML. Nosso controlador pode ser exatamente igual à versão anterior, com a finalinstância do modelo que está acessível no initialize()método. E nossas ferramentas de desenvolvedor "sabem" qual classe está sendo usada para o controlador, para que possam verificar nomes de métodos e campos, etc.

    Observe que ainda codificamos o nome da classe do controlador em nosso código de carregamento. Podemos fazer uma fábrica geral de controladores usando reflexão. Esta implementação analisa a classe de controlador fornecida, tenta encontrar um construtor usando uma instância de modelo e usa esse construtor, se houver. Se não encontrar tal construtor, ele tentará usar o construtor padrão sem argumentos. Se nenhum deles existir, uma exceção será lançada. Obviamente, a lógica aqui é arbitrária e pode ser qualquer coisa que você escolher.

    Model model = ...;
    FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/view.fxml"));
    loader.setControllerFactory(type -> {
        for (Constructor<?> constructor : type.getConstructors()) {
            if (constructor.getParameterCount() == 1 && constructor.getParameterTypes()[0].equals(Model.class)) {
                return constructor.newInstance(model);
            }
        }
        return type.getConstructor().newInstance();
    });
    Parent root = loader.load();
    

    Isso é um pouco mais detalhado, mas poderíamos escrevê-lo uma vez e reutilizá-lo em todo o aplicativo. Os arquivos FXML são "naturais" e têm exatamente a mesma aparência da configuração padrão, e o controlador pode se parecer com o postado aqui (com um construtor tomando uma instância de modelo, etc.). Existe sem dúvida uma desvantagem geral de usar reflexão em termos de desempenho e falta de verificação de tipo em tempo de compilação, mas FXMLLoaderjá está usando tanta reflexão que o custo adicional aqui provavelmente será mínimo.

    Um ponto digno de nota é para arquivos FXML aninhados, usados <fx:include>​​no FXML. A mesma fábrica do controlador será usada quando o arquivo FXML incluído for carregado como para o arquivo FXML carregado explicitamente que o contém. Portanto, se você usar uma fábrica de controladores, ela deverá ser flexível o suficiente para fornecer o controlador correto tanto para o FXML "principal" quanto para quaisquer FXMLs incluídos.


    Se bem me lembro, a fábrica de controladores foi introduzida na versão 2.1 em resposta a uma solicitação de alguém que deseja usar uma estrutura de injeção de dependência para gerenciar controladores JavaFX. (Pode ter sido Adam Bien, na época em que ele estava escrevendo a estrutura do pós-combustão , agora sem manutenção , mas minha memória não é tão boa.)

    As fábricas de controladores funcionam bem neste cenário. Existem vários exemplos de Spring-JavaFX neste site, mas a ideia geral é:

    ApplicationContext context = ... ;
    
    FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/view.fxml"));
    loader.setControllerFactory(context::getBean);
    Parent root = loader.load();
    

    Essencialmente, apenas informamos FXMLLoaderpara obter os controladores do contexto do aplicativo Spring.

    O controlador baseado em Spring se parece com

    @Component(scope="prototype")
    public class MyController {
        @Autowired
        private Model model;
    
        @FXML
        private Label fooLabel;
    
        @FXML
        private void initialize() {
            fooLabel.setText(model.getFoo());
        }
    }
    
    • 2
  2. Fi0x
    2024-05-26T01:43:24+08:002024-05-26T01:43:24+08:00

    Possível duplicata aqui: JavaFX: Precisa de ajuda para entender setControllerFactory

    Conforme explicado na duplicata, setControllerFactoryé útil quando você usa uma estrutura de injeção de dependência e deseja usar sua fábrica personalizada para injetar dependências. Se você não usa nenhum framework para injeção de dependência, é mais fácil usar setController.

    • 0

relate perguntas

  • Lock Condition.notify está lançando java.lang.IllegalMonitorStateException

  • Resposta de microsserviço Muitos para Um não aparece no carteiro

  • Validação personalizada do SpringBoot Bean

  • Os soquetes Java são FIFO?

  • Por que não é possível / desencorajado definir um lado do servidor de tempo limite de solicitação?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve