You don't need to use CDI to manage the stages: stages themselves simply have a Scene; they do not have any dependencies on any other objects you need to manage. All you need to do is ensure that the FXMLLoader has a controllerFactory that retrieves controller instances from the DI framework.
Here is a quick example (caveat: I have never used CDI/Weld before, so I might not have the optimal way of doing things here).
First, it's probably a good idea to expose a controller factory that gets the appropriate controllers:
package app;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javafx.util.Callback;
@ApplicationScoped
public class CDIControllerFactory implements Callback<Class<?>, Object> {
@Inject
private Instance<Object> instance ;
@Override
public Object call(Class<?> type) {
Object controller = instance.select(type).get();
return controller;
}
}
Here is a model class we want to share with all the controllers. Since we only want one instance, we make it @ApplicationScoped:
package app;
import javax.enterprise.context.ApplicationScoped;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@ApplicationScoped
public class Model {
private final ObservableList<String> names = FXCollections.observableArrayList();
public ObservableList<String> getNames() {
return names ;
}
public void addName(String name) {
names.add(name);
}
}
The test application will just have a list view (with a list of names) and a button for adding a new name from a dialog. Here is the main controller:
package app;
import java.io.IOException;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class MainController {
@Inject
private Model model ;
@Inject
private CDIControllerFactory controllerFactory ;
@FXML
private ListView<String> listView ;
@FXML
private void initialize() {
listView.setItems(model.getNames());
}
@FXML
private void showAddDialog() throws IOException {
FXMLLoader loader = new FXMLLoader(AddNameController.class.getResource("AddNameDialog.fxml"));
loader.setControllerFactory(controllerFactory);
Scene scene = new Scene(loader.load());
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setScene(scene);
stage.show();
}
}
Note how it uses the controller factory on the FXMLLoader. The stage can just be created "by hand".
Here's the controller for the dialog that is used to add new names. Note how it has a reference to the same model instance, via CDI:
package app;
import javax.enterprise.inject.Default;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
@Default
public class AddNameController {
@Inject
private Model model ;
@FXML
private TextField nameField ;
@FXML
private void submit() {
model.addName(nameField.getText());
close();
}
@FXML
private void close() {
nameField.getScene().getWindow().hide();
}
}
Here are the two FXML files (they are both in the app package: the only real requirement with the way I coded these is that they should be in the same package as their corresponding controller classes).
Main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="app.MainController">
<center>
<ListView fx:id="listView" />
</center>
<bottom>
<HBox alignment="CENTER">
<padding>
<Insets top="5" right="5" left="5" bottom="5" />
</padding>
<Button text="Add..." onAction="#showAddDialog" />
</HBox>
</bottom>
</BorderPane>
AddNameDialog.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="app.MainController">
<center>
<ListView fx:id="listView" />
</center>
<bottom>
<HBox alignment="CENTER">
<padding>
<Insets top="5" right="5" left="5" bottom="5" />
</padding>
<Button text="Add..." onAction="#showAddDialog" />
</HBox>
</bottom>
</BorderPane>
Here's the application class:
package app;
import java.io.IOException;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
private Weld weld ;
private WeldContainer container ;
@Override
public void init() {
weld = new Weld();
container = weld.initialize();
}
@Override
public void stop() {
weld.shutdown();
}
@Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader loader = new FXMLLoader(MainController.class.getResource("Main.fxml"));
loader.setControllerFactory(container.select(CDIControllerFactory.class).get());
Scene scene = new Scene(loader.load(), 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and of course the CDI configuration class, META-INF/beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</beans>
If you really want to let CDI provide your stages, you can, but I don't really see there's much to gain by it. But, e.g. you can do something like:
package app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD,
ElementType.TYPE, ElementType.PARAMETER})
public @interface ModalStage { }
which lets you provide modal and non-modal stages:
package app;
import javax.enterprise.inject.Produces;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class StageProducer {
@Produces
public Stage stage() {
return new Stage();
}
@Produces
@ModalStage
public Stage modalStage() {
Stage stage = stage();
stage.initModality(Modality.APPLICATION_MODAL);
return stage ;
}
}
And then your MainController might look like
public class MainController {
@Inject
private Model model ;
@Inject
private CDIControllerFactory controllerFactory ;
@Inject
@ModalStage
private Stage addNameDialogStage ;
@FXML
private ListView<String> listView ;
@FXML
private void initialize() {
listView.setItems(model.getNames());
}
@FXML
private void showAddDialog() throws IOException {
FXMLLoader loader = new FXMLLoader(AddNameController.class.getResource("AddNameDialog.fxml"));
loader.setControllerFactory(controllerFactory);
Scene scene = new Scene(loader.load());
addNameDialogStage.setScene(scene);
addNameDialogStage.show();
}
}
There are other facilities you could easily build into this, e.g. providing a class for loading FXML from a resource name, which incorporates the controller factory already, etc etc.