I have a gui built with javafx the controllers are loaded from fxml and created as Beans with spring so I can access my model. But that is predefined in fxml and loaded at start. Now I would like to load components, defined in fxml at runtime, but I could not yet find a working example, and no matter how I try it doesn't work.
So my question:
How can I create a custom Dialog (or any custom component) in runtime , that is loaded from .fxml and is aware of (Spring application) context?
Edit
So it loads but some fields are not initialized.
This is my custom DialogPane,
#Controller
#Scope("prototype")
public class NewProgramDialogPane extends DialogPane implements Initializable {
public static final ButtonType buttonTypeOk = new ButtonType("Create", ButtonBar.ButtonData.OK_DONE);
public static final ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
public TextField nameField;
public TextField data1Field;
public TextField data2Field;
public RegexValidator requiredField1;
public RequiredField requiredField2;
public RequiredField requiredField3;
public ErrorLabel duplicateProjectErrorLabel;
private SimpleBooleanProperty match = new SimpleBooleanProperty(false);
#Autowired
MainService mainService;
public NewProgramDialogPane() {
URL url = getClass().getClassLoader().getResource("com/akos/fxml/NewProgramDialog.fxml");
FXMLLoader loader = new FXMLLoader();
loader.setLocation(url);
loader.setRoot(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
this.lookupButton(buttonTypeOk).addEventHandler(ActionEvent.ACTION, event -> {
if (!validate()) {
event.consume();
}
});
duplicateProjectErrorLabel.visibleProperty().bind(match);
}
public boolean validate() {
requiredField1.eval();
requiredField2.eval();
requiredField3.eval();
match.set(mainService.getPrograms().stream().anyMatch(
program -> program != null && program.getName().equals(nameField.getText())));
return !match.get() &&
!requiredField1.getHasErrors() &&
!requiredField2.getHasErrors() &&
!requiredField3.getHasErrors();
}
}
And when I try to read the nameField, it is null.
public class NewProgramDialog extends Dialog<Program> {
public NewProgramDialog() {
this.setDialogPane(new NewProgramDialogPane());
this.setTitle("New program");
this.initModality(Modality.APPLICATION_MODAL);
this.initStyle(StageStyle.DECORATED);
this.setResultConverter(param -> {
if (param == NewProgramDialogPane.buttonTypeOk) {
int x = 0;
return new Program(((NewProgramDialogPane) getDialogPane()).nameField.getText());
}
return null;
});
}
}
Define your custom dialog using the custom component FXML pattern; then just expose the custom component as a (prototype-scoped) spring bean.
Related
Has anyone succesfully used the CXF HttpConduitFeature for DOSGi ?
Looking at the CXF code for HttpConduitFeature.java
public class HttpConduitFeature extends DelegatingFeature<HttpConduitFeature.Portable> {
public HttpConduitFeature() {
super(new Portable());
}
public void setConduitConfig(HttpConduitConfig conduitConfig) {
delegate.setConduitConfig(conduitConfig);
}
public static class Portable implements AbstractPortableFeature {
private HttpConduitConfig conduitConfig;
#Override
public void initialize(Client client, Bus bus) {
Conduit conduit = client.getConduit();
if (conduitConfig != null && conduit instanceof HTTPConduit) {
conduitConfig.apply((HTTPConduit)conduit);
}
}
public void setConduitConfig(HttpConduitConfig conduitConfig) {
this.conduitConfig = conduitConfig;
}
}
}
And this method from the class JAXRSClientFactoryBean.java
protected void applyFeatures(AbstractClient client) {
if (getFeatures() != null) {
getFeatures().forEach(feature -> {
feature.initialize(client.getConfiguration(), getBus());
});
}
}
Which is what happens from the RsProvider-class in CXF-DOSGi, I don't understand how the initialize() from the HttpConduitFeature.Portable class will ever get called..
I tried to create my own implementation, a copy from HttpConduitFeature, but with an override of the method initialize(final InterceptorProvider interceptorProvider, final Bus bus), but then I have nothing to add the conduitConfig to. I don't see how I can make progress here.
Anyone has a better idea to add a Basic Authentication AuthorizationPolicy to my DOSGi client ? This was my attempt :
public class BasicAuthorizationIntent implements IntentsProvider {
#Override
public List<?> getIntents() {
HttpConduitConfig conduitConfig = new HttpConduitConfig();
conduitConfig.setAuthorizationPolicy(basicAuthorization());
HttpConduitFeature conduitFeature = new HttpConduitFeature();
conduitFeature.setConduitConfig(conduitConfig);
return Arrays.asList((Object) conduitFeature);
}
private AuthorizationPolicy basicAuthorization() {
AuthorizationPolicy authorizationPolicy = new AuthorizationPolicy();
authorizationPolicy.setUserName("dosgi");
authorizationPolicy.setPassword("dosgi");
authorizationPolicy.setAuthorizationType("Basic");
return authorizationPolicy;
}
}
I try register eventHandler in my custom class. I don't know what interface or methods I have to implement for having addEventHandler method in my custom class. For this reason my Model class extends Rectangle (Rectangle class has addEventHandler mechanism).
Also I don't know why assigned source object not working (please see comment in Controller class).
Creating custom events I make by this tutorial: https://stackoverflow.com/a/27423430/3102393.
Project Structure
Controller
package sample;
import javafx.event.Event;
public class Controller {
private Model model;
public Controller() {
model = new Model();
model.addEventHandler(MyEvent.ROOT_EVENT, this::handler);
}
private void handler(MyEvent event) {
if(event.getEventType().equals(MyEvent.INSTANCE_CREATED)) {
// Why is event.getSource() instence of Rectangle and not instance of assigned MyObject?
Object obj = event.getSource();
System.out.println(event.getMyObject().getText());
}
}
public void clickedCreate(Event event) {
model.makeEvent();
}
}
Model
package sample;
import javafx.scene.shape.Rectangle;
import java.util.ArrayList;
public class Model extends Rectangle {
private ArrayList<MyObject> objects = new ArrayList<>();
private Integer counter = 0;
public void makeEvent() {
MyObject object = new MyObject((++counter).toString() + "!");
objects.add(object);
fireEvent(new MyEvent(object, null, MyEvent.INSTANCE_CREATED));
}
}
Custom event MyEvent
package sample;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.event.EventType;
public class MyEvent extends Event {
public static final EventType<MyEvent> ROOT_EVENT = new EventType<>(Event.ANY, "ROOT_EVENT");
public static final EventType<MyEvent> INSTANCE_CREATED = new EventType<>(ROOT_EVENT, "INSTANCE_CREATED ");
public static final EventType<MyEvent> INSTANCE_DELETED = new EventType<>(ROOT_EVENT, "INSTANCE_DELETED");
private MyObject object;
public MyEvent(MyObject source, EventTarget target, EventType<MyEvent> eventType) {
super(source, target, eventType);
object = source;
}
public MyObject getMyObject() {
return object;
}
}
And finally MyObject
package sample;
public class MyObject {
private String text;
MyObject(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
Note (and question): I also tried using a ObservableList of instances of MyObjects, but I think that there is no notify for updating instance attribute.
Basics of Events
Events are fired using Event.fireEvent which works in 2 steps:
Build the EventDispatchChain using EventTarget.buildEventDispatchChain.
Pass the Event to the first EventDispatcher in the resulting EventDispatchChain.
This code snippet demonstrates the behaviour:
EventTarget target = new EventTarget() {
#Override
public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
return tail.append(new EventDispatcher() {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
System.out.println("Dispatch 1");
tail.dispatchEvent(event);
return event;
}
}).append(new EventDispatcher() {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
System.out.println("Dispatch 2");
tail.dispatchEvent(event);
return event;
}
});
}
};
Event.fireEvent(target, new Event(EventType.ROOT));
It prints
Dispatch 1
Dispatch 2
As you can see, the way the EventTarget constructs the EventDispatchChain is totally up to the EventTarget.
This explains why you have to implement addEventHandler ect. yourself.
How it's done for Nodes
This is described in detail in the article JavaFX: Handling Events - 1 Processing Events on the Oracle website.
The important details are:
Different source objects are used during the event handling.
EventHandlers / EventFilters are used during the event dispatching (2.).
This explains why the source value is unexpected.
How to implement addEventHandler
It's not that hard to do this, if you leave out the event capturing and bubbling. You just need to store the EventHandlers by type in a Map<EventType, Collection>> and call the EventHandlers for each type in the EventType hierarchy:
public class EventHandlerTarget implements EventTarget {
private final Map<EventType, Collection<EventHandler>> handlers = new HashMap<>();
public final <T extends Event> void addEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler) {
handlers.computeIfAbsent(eventType, (k) -> new ArrayList<>())
.add(eventHandler);
}
public final <T extends Event> void removeEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler) {
handlers.computeIfPresent(eventType, (k, v) -> {
v.remove(eventHandler);
return v.isEmpty() ? null : v;
});
}
#Override
public final EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
return tail.prepend(this::dispatchEvent);
}
private void handleEvent(Event event, Collection<EventHandler> handlers) {
if (handlers != null) {
handlers.forEach(handler -> handler.handle(event));
}
}
private Event dispatchEvent(Event event, EventDispatchChain tail) {
// go through type hierarchy and trigger all handlers
EventType type = event.getEventType();
while (type != Event.ANY) {
handleEvent(event, handlers.get(type));
type = type.getSuperType();
}
handleEvent(event, handlers.get(Event.ANY));
return event;
}
public void fireEvent(Event event) {
Event.fireEvent(this, event);
}
}
I am using javafx combined with FXML.
I want to apply the MVC pattern. For that I want my Model.java class to be the model, which launches the View.fxml and the controller of that view would be viewController.java.
I need to bring Model.java and Controller.java to communicate at some point. So let's say ViewController.java looks this way:
public class ViewController implements Initializable {
private String parameter = "hello";
#FXML
private Label label;
#FXML
private Accordion acccord;
public String getParemeter() {
return this.parameter;
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
ViewController has a private string and its own methods.
And Model.java :
public class Model extends Application {
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("View.fxml") );
Parent root = loader.load(); // Here the View is loaded and the Contoller is created along.
loader.getController(); // ?
//Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How can I access the ViewContoller parameters / methods (such as getPamareter() ) ?
I tried to get the controller with loader.getController() but it returns a generic type, what should I do with it, provided it has something to do with it? I went to the oracle documentation but I am not quite sure to understan, does getController() return an instance of my ViewController.java ?
HOw can I access the Model from the ViewController?
For instance a buton is triggered, the vieController would update a value in Model.java.
when I use this code the result is empty page:
public class Vaadin6biuApplication extends Application {
#Override
public void init() {
xx a = new xx();
Window w = new Window("aness conf");
w.addComponent(a);
setMainWindow(w);
}
}
public class xx extends CustomComponent {
#AutoGenerated
private AbsoluteLayout mainLayout;
#AutoGenerated
private Button button_1;
public xx() {
buildMainLayout();
setCompositionRoot(mainLayout);
}
#AutoGenerated
private AbsoluteLayout buildMainLayout() {
mainLayout = new AbsoluteLayout();
mainLayout.setImmediate(false);
button_1 = new Button();
mainLayout.addComponent(button_1, "top:100.0px;left:100.0px;");
return mainLayout;
}
}
how to add custom component to application?
thank you for your answers
Have you read the wiki tutorial for vaadin6 + spring? With spring > 2.5 it's fairly simple:
#Configurable(preConstruction = true)
public class SpringHelloWorld extends com.vaadin.Application {
#Autowired
private MyBeanInterface bean;
public void init() {
final Window main = new Window("Hello window");
setMainWindow(main);
main.addComponent(new Label( bean.myMethod() ));
}
}
Problem
I want to add custom made panels, built via javafx scene builder, to a gridpane at runtime. My custom made panel exsits of buttons, labels and so on.
My Attempt
I tried to extend from pane...
public class Celli extends Pane{
public Celli() throws IOException{
Parent root = FXMLLoader.load(getClass().getResource("Cell.fxml"));
this.getChildren().add(root);
}
}
... and then use this panel in the adding method of the conroller
#FXML
private void textChange(KeyEvent event) {
GridPane g = new GridPane();
for (int i=0 : i<100; i++){
g.getChildren().add(new Celli());
}
}
}
It works, but it performs very very poor.
What I am looking for
Is there a way to design panels via javafx scene builder (and as a result having this panels in fxml) and then add it to a gridpane at runtime without make use of this fxmlloader for each instance. I think it performs poor because of the fxml loader. When I add a standard button e.g. whitout fxml it is very much faster.
Short answer: No, it is not (as of JavaFX 2.x and 8.0). It may be in a future version (JFX >8)
Long answer:
The FXMLLoader is currently not designed to perform as a template provider that instantiates the same item over and over again. Rather it is meant to be a one-time-loader for large GUIs (or to serialize them).
The performance is poor because depending on the FXML file, on each call to load(), the FXMLLoader has to look up the classes and its properties via reflection. That means:
For each import statement, try to load each class until the class could successfully be loaded.
For each class, create a BeanAdapter that looks up all properties this class has and tries to apply the given parameters to the property.
The application of the parameters to the properties is done via reflection again.
There is also currently no improvement for subsequent calls to load() to the same FXML file done in the code. This means: no caching of found classes, no caching of BeanAdapters and so on.
There is a workaround for the performance of step 1, though, by setting a custom classloader to the FXMLLoader instance:
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class MyClassLoader extends ClassLoader{
private final Map<String, Class> classes = new HashMap<String, Class>();
private final ClassLoader parent;
public MyClassLoader(ClassLoader parent) {
this.parent = parent;
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = findClass(name);
if ( c == null ) {
throw new ClassNotFoundException( name );
}
return c;
}
#Override
protected Class<?> findClass( String className ) throws ClassNotFoundException {
// System.out.print("try to load " + className);
if (classes.containsKey(className)) {
Class<?> result = classes.get(className);
return result;
} else {
try {
Class<?> result = parent.loadClass(className);
// System.out.println(" -> success!");
classes.put(className, result);
return result;
} catch (ClassNotFoundException ignore) {
// System.out.println();
classes.put(className, null);
return null;
}
}
}
// ========= delegating methods =============
#Override
public URL getResource( String name ) {
return parent.getResource(name);
}
#Override
public Enumeration<URL> getResources( String name ) throws IOException {
return parent.getResources(name);
}
#Override
public String toString() {
return parent.toString();
}
#Override
public void setDefaultAssertionStatus(boolean enabled) {
parent.setDefaultAssertionStatus(enabled);
}
#Override
public void setPackageAssertionStatus(String packageName, boolean enabled) {
parent.setPackageAssertionStatus(packageName, enabled);
}
#Override
public void setClassAssertionStatus(String className, boolean enabled) {
parent.setClassAssertionStatus(className, enabled);
}
#Override
public void clearAssertionStatus() {
parent.clearAssertionStatus();
}
}
Usage:
public static ClassLoader cachingClassLoader = new MyClassLoader(FXMLLoader.getDefaultClassLoader());
FXMLLoader loader = new FXMLLoader(resource);
loader.setClassLoader(cachingClassLoader);
This significantly speeds up the performance. However, there is no workaround for step 2, so this might still be a problem.
However, there are already feature requests in the official JavaFX jira for this. It would be nice of you to support this requests.
Links:
FXMLLoader should be able to cache imports and properties between to load() calls:
https://bugs.openjdk.java.net/browse/JDK-8090848
add setAdapterFactory() to the FXMLLoader:
https://bugs.openjdk.java.net/browse/JDK-8102624
I have had a similar issue. I also had to load a custom fxml-based component several times, dynamically, and it was taking too long. The FXMLLoader.load method call was expensive, in my case.
My approach was to parallelize the component instantiation and it solved the problem.
Considering the example posted on the question, the controller method with multithread approach would be:
private void textChange(KeyEvent event) {
GridPane g = new GridPane();
// creates a thread pool with 10 threads
ExecutorService threadPool = Executors.newFixedThreadPool(10);
final List<Celli> listOfComponents = Collections.synchronizedList(new ArrayList<Celli>(100));
for (int i = 0; i < 100; i++) {
// parallelizes component loading
threadPool.execute(new Runnable() {
#Override
public void run() {
listOfComponents.add(new Celli());
}
});
}
// waits until all threads completion
try {
threadPool.shutdown();
threadPool.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// seems to be a improbable exception, but we have to deal with it
e.printStackTrace();
}
g.getChildren().addAll(listOfComponents);
}
Just adding code for "caching of already loaded classes" in #Sebastian sir given code. It is working for me. Please suggest changes in it for better performance.
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("In Class loader");
Class result;
System.out.println(" >>>>>> Load class : "+name);
result = (Class)classes.get(name);
if(result != null){
System.out.println(" >>>>>> returning cached class.");
return result;
}else{
Class<?> c = findClass(name);
if ( c == null ) {
throw new ClassNotFoundException( name );
}
System.out.println(" >>>>>> loading new class for first time only");
return c;
}
}