JavaFX CustomControl<T>: Is this possible? - java

I want to create a simple reusable custom control in JavaFX that is nothing more than a ComboBox with a label over its head that can have the text set.
I would like for it to be usable in JavaFX Scene Builder.
I would also like for it to be able to take a single Generic Parameter <T> to be able to as closely as possible emulate the behavior of the standard ComboBox that is available.
The problem which I am encountering is that when I attempt to set the Controls Controller to Controller<T> in SceneBuilder, I get an error telling me: Controller<T> is invalid for Controller class.
This makes sense as when you call FXMLLoader.load() (after setting the root, classLoader, and Location), there is no way (that I can find) to tell the loader "Oh, and this is a CustomControl."
This is the code I have for the Control:
public class LabeledComboBox<T> extends VBox {
private final LCBController<T> Controller;
public LabeledComboBox(){
this.Controller = this.Load();
}
private LCBController Load(){
final FXMLLoader loader = new FXMLLoader();
loader.setRoot(this);
loader.setClassLoader(this.getClass().getClassLoader());
loader.setLocation(this.getClass().getResource("LabeledComboBox.fxml"));
try{
final Object root = loader.load();
assert root == this;
} catch (IOException ex){
throw new IllegalStateException(ex);
}
final LCBController ctrlr = loader.getController();
assert ctrlr != null;
return ctrlr;
}
/*Methods*/
}
This is the Controller class:
public class LCBController<T> implements Initializable {
//<editor-fold defaultstate="collapsed" desc="Variables">
#FXML private ResourceBundle resources;
#FXML private URL location;
#FXML private Label lbl; // Value injected by FXMLLoader
#FXML private ComboBox<T> cbx; // Value injected by FXMLLoader
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Initialization">
#Override public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
this.location = fxmlFileLocation;
this.resources = resources;
//<editor-fold defaultstate="collapsed" desc="Assertions" defaultstate="collapsed">
assert lbl != null : "fx:id=\"lbl\" was not injected: check your FXML file 'LabeledComboBox.fxml'.";
assert cbx != null : "fx:id=\"cbx\" was not injected: check your FXML file 'LabeledComboBox.fxml'.";
//</editor-fold>
}
//</editor-fold>
/*Methods*/
}
Clearly there is something that I am missing here. I am really hoping this is possible without having to come up with my own implementation of the FXMLLoader Class (REALLY, REALLY, REALLY REALLY hoping).
Can someone please tell me what I am missing, or if this is even possible?
EDIT 1:
After someone pointed me to a link I may have an idea of how to do this but I'm still not one hundred percent. To me it feels like the Controller class itself can not be created with a generic parameter (I.E.: public class Controller<T>{...} = No Good)
That's kind of annoying but I guess makes sense.
Then what about applying Generic parameters to the Methods inside the custom control controller, and making the control itself (not the controller) a generic: like so?
Control:
public class LabeledComboBox<T> extends VBox {...}
Controller:
public class LCBController implements Initializable {
/*Stuff...*/
/**
* Set the ComboBox selected value.
* #param <T>
* #param Value
*/
public <T> void setValue(T Value){
this.cbx.setValue(Value);
}
/**
* Adds a single item of type T to the ComboBox.
* #param <T> ComboBox Type
* #param Item
*/
public <T> void Add(T Item){
this.cbx.getItems().add(Item);
}
/**
* Adds a list of items of type T to the ComboBox.
* #param <T> ComboBox Type
* #param Items
*/
public <T> void Add(ObservableList<T> Items){
this.cbx.getItems().addAll(Items);
}
/**
* Removes an item of type T from the ComboBox.
* #param <T> ComboBox Type
* #param Item
* #return True if successful(?)
*/
public <T> boolean Remove(T Item){
return this.cbx.getItems().remove(Item);
}
}
Would that work? Is that more along the right track? Again, my desire is nothing more than a ComboBox with a Label on it to tell users what its all about.

This worked for me, and when I imported the library into SceneBuilder it worked fine:
(Very basic) FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ComboBox?>
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="VBox"
fx:controller="application.LabeledComboBoxController">
<Label fx:id="label" />
<ComboBox fx:id="comboBox" />
</fx:root>
Controller:
package application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;
public class LabeledComboBoxController<T> {
#FXML
private Label label ;
#FXML
private ComboBox<T> comboBox ;
public void setText(String text) {
label.setText(text);
}
public String getText() {
return label.getText();
}
public StringProperty textProperty() {
return label.textProperty();
}
public ObservableList<T> getItems() {
return comboBox.getItems();
}
public void setItems(ObservableList<T> items) {
comboBox.setItems(items);
}
public boolean isWrapText() {
return label.isWrapText();
}
public void setWrapText(boolean wrapText) {
label.setWrapText(wrapText);
}
public BooleanProperty wrapTextProperty() {
return label.wrapTextProperty();
}
public SingleSelectionModel<T> getSelectionModel() {
return comboBox.getSelectionModel();
}
}
Control:
package application;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.VBox;
public class LabeledComboBox<T> extends VBox {
private final LabeledComboBoxController<T> controller ;
public LabeledComboBox(ObservableList<T> items, String text) {
controller = load();
if (controller != null) {
setText(text);
setItems(items);
}
}
public LabeledComboBox(ObservableList<T> items) {
this(items, "");
}
public LabeledComboBox(String text) {
this(FXCollections.observableArrayList(), text);
}
public LabeledComboBox() {
this("");
}
private LabeledComboBoxController<T> load() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"LabeledComboBox.fxml"));
loader.setRoot(this);
loader.load();
return loader.getController() ;
} catch (Exception exc) {
Logger.getLogger("LabeledComboBox").log(Level.SEVERE,
"Exception occurred instantiating LabeledComboBox", exc);
return null ;
}
}
// Expose properties, but just delegate to controller to manage them
// (by delegating in turn to the underlying controls):
public void setText(String text) {
controller.setText(text);
}
public String getText() {
return controller.getText();
}
public StringProperty textProperty() {
return controller.textProperty();
}
public boolean isWrapText() {
return controller.isWrapText();
}
public void setWrapText(boolean wrapText) {
controller.setWrapText(wrapText);
}
public BooleanProperty wrapTextProperty() {
return controller.wrapTextProperty();
}
public ObservableList<T> getItems() {
return controller.getItems();
}
public void setItems(ObservableList<T> items) {
controller.setItems(items);
}
public SingleSelectionModel<T> getSelectionModel() {
return controller.getSelectionModel();
}
}
Test code:
package application;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
LabeledComboBox<String> comboBox = new LabeledComboBox<String>(
FXCollections.observableArrayList("One", "Two", "Three"), "Test");
root.setTop(comboBox);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}

I'm sure that this construction is not possible as FXML is evaluated at runtime. And generics are already deleted at runtime.
But what's possible to do is to assign a generic to the controller.
FXML implements the Model-View-Controller (MVC) design which is subject to the following topic:
What is MVC (Model View Controller)?
Your question ist also an issue in the following topic:
Setting TableView Generic Type from FXML

Related

Is there a way to change a Java property without firing a value changed event to it's listeners?

What I'm trying to do
I'm looking for a way to change a property, without a call to the listeners's changed method.
More specifically I'm trying to implement an undo/redo functionality. The way I've implemented it is as following, in an example with a BooleanProperty and a JavaFX CheckBox.
The selectedProperty of the CheckBox is changed by a mouse click.
A BooleanProperty (actually a JavaFX SimpleBooleanProperty) is changed because it is bound bidirectionally to the selectedProperty
The ChangeListener of the BooleanProperty registers this and adds a Command on the application's undoStack. The Command stores the property, the old and the new value.
The user clicks the undo button
Via the button the application takes that last Command from the stack and calls it's undo() method.
The undo() method changes the BooleanProperty back.
The ChangeListener registers this change again and creates a new Command
An endless cycle is created
My Hacky Solution
The way I did it is by passing the ChangeListener to the Command object. Then the undo() method first removes the ChangeListener, changes the BooleanProperty and then adds the ChangeListener again.
It feels wrong and hacky to pass the ChangeListener to the Command (in my actual implementation in the 3. step there are actually a few more classes between the ChangeListener and the Command which now all need to know about the ChangeListener)
My Question
Is this really the way to do it? Isn't there a way to change the property in step 6 and just tell it to not inform it's listeners? Or at least to get it's listeners?
There's no supported way of bypassing listeners, as you describe. You just need to build this logic into your undo/redo mechanism. The idea is basically to set a flag if you are performing an undo/redo, and not add the change to your stack if so.
Here's a very simple example: note this is not production quality - for example typing in a text control will add to the stack for every character change (keeping copies of the current text at each change). In real code, you should coalesce these changes together.
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
public class UndoManager {
private boolean performingUndoRedo = false ;
private Deque<Command<?>> undoStack = new LinkedList<>();
private Deque<Command<?>> redoStack = new LinkedList<>();
private Map<Property<?>, ChangeListener<?>> listeners = new HashMap<>();
public <T> void register(Property<T> property) {
// don't register properties multiple times:
if (listeners.containsKey(property)) {
return ;
}
// FIXME: should coalesce (some) changes on the same property, so, e.g. typing in a text
// control does not result in a separate command for each character
ChangeListener<? super T> listener = (obs, oldValue, newValue) -> {
if (! performingUndoRedo) {
Command<T> cmd = new Command<>(property, oldValue, newValue) ;
undoStack.addFirst(cmd);
}
};
property.addListener(listener);
listeners.put(property, listener);
}
public <T> void unregister(Property<T> property) {
listeners.remove(property);
}
public void undo() {
if (undoStack.isEmpty()) {
return ;
}
Command<?> command = undoStack.pop();
performingUndoRedo = true ;
command.undo();
redoStack.addFirst(command);
performingUndoRedo = false ;
}
public void redo() {
if (redoStack.isEmpty()) {
return ;
}
Command<?> command = redoStack.pop();
performingUndoRedo = true ;
command.redo();
undoStack.addFirst(command);
performingUndoRedo = false ;
}
private static class Command<T> {
private final Property<T> property ;
private final T oldValue ;
private final T newValue ;
public Command(Property<T> property, T oldValue, T newValue) {
super();
this.property = property;
this.oldValue = oldValue;
this.newValue = newValue;
}
private void undo() {
property.setValue(oldValue);
}
private void redo() {
property.setValue(newValue);
}
#Override
public String toString() {
return "property: "+property+", from: "+oldValue+", to: "+newValue ;
}
}
}
And here's a quick test harness:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class UndoExample extends Application {
#Override
public void start(Stage stage) throws Exception {
ComboBox<Color> textColor = new ComboBox<Color>();
textColor.getItems().addAll(Color.BLACK, Color.RED, Color.DARKGREEN, Color.BLUE);
textColor.setValue(Color.BLACK);
textColor.setCellFactory(lv -> new ColorCell());
textColor.setButtonCell(new ColorCell());
CheckBox italic = new CheckBox("Italic");
TextArea text = new TextArea();
updateStyle(text, textColor.getValue(), italic.isSelected());
ChangeListener<Object> listener = (obs, oldValue, newValue) ->
updateStyle(text, textColor.getValue(), italic.isSelected());
textColor.valueProperty().addListener(listener);
italic.selectedProperty().addListener(listener);
UndoManager undoMgr = new UndoManager();
undoMgr.register(textColor.valueProperty());
undoMgr.register(italic.selectedProperty());
undoMgr.register(text.textProperty());
Button undo = new Button("Undo");
Button redo = new Button("Redo");
undo.setOnAction(e -> undoMgr.undo());
redo.setOnAction(e -> undoMgr.redo());
HBox controls = new HBox(textColor, italic, undo, redo);
controls.setSpacing(5);
BorderPane root = new BorderPane(text);
root.setTop(controls);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private void updateStyle(TextArea text, Color textColor, boolean italic) {
StringBuilder style = new StringBuilder()
.append("-fx-text-fill: ")
.append(hexString(textColor))
.append(";")
.append("-fx-font: ");
if (italic) {
style.append("italic ");
}
style.append("13pt sans-serif ;");
text.setStyle(style.toString());
}
private String hexString(Color color) {
int r = (int) (color.getRed() * 255) ;
int g = (int) (color.getGreen() * 255) ;
int b = (int) (color.getBlue() * 255) ;
return String.format("#%02x%02x%02x", r, g, b);
}
private static class ColorCell extends ListCell<Color> {
private Rectangle rect = new Rectangle(25, 25);
#Override
protected void updateItem(Color color, boolean empty) {
super.updateItem(color, empty);
if (empty || color==null) {
setGraphic(null);
} else {
rect.setFill(color);
setGraphic(rect);
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
There is pretty much not a possibility to do this without "hacks"!
However, there is also a shorter solution, via using reflection:
/**
* Set the value of property without firing any change event.
* The value of property will be set via reflection.
* This property must be "Base" property such as {#link DoublePropertyBase}.
*
* #param property | Property to set!
* #param newValue | New value of property.
*/
public static <T> void setPropertyWithoutFiringEvent(Property<T> property, T newValue)
{
Class<?> cls = property.getClass();
while (cls != null) //While until helper variable is found
{
try
{
Field fieldH = cls.getDeclaredField("helper"), fieldV = cls.getDeclaredField("valid");
fieldH.setAccessible(true);
fieldV.setAccessible(true);
Object helper = fieldH.get(property), valid = fieldV.getBoolean(property); //Temporary values
fieldH.set(property, null); //Disabling ExpressionHelper by setting it on null;
property.setValue(newValue);
fieldH.set(property, helper); //Setting helper back!
fieldV.set(property, valid); //Important
return;
}
catch (Exception e)
{
cls = cls.getSuperclass(); //If not found go to super class of property next time!
}
}
System.err.println("Property " + property + " cant be set because variable \"helper\" was not found!");
}
This function temporarily disables ExpressionHelper what is an object responsible for firing change events, and then it will change the value of property and enable ExpressionHelper back! This will cause that one change will not be notified!
If the reflection is not friendly solution for you, then just use the solution above however this one is far shorter and simpler.

Displaying different ObservableList in a single TableView [JavaFX]

I have a layout which basically consists of ChoiceBox and TableView. What I'd like to achieve is to display different data in TableView basing on the selected option in ChoiceBox.
What I have so far is:
MainController class:
private void configureChoiceBox() {
choiceBox.getSelectionModel().selectedIndexProperty().addListener((v, oldValue, newValue) -> {
if(newValue.intValue() == 0) {
workshopList.setItems(data.getPipeCableList());
}
else if(newValue.intValue() == 1) {
workshopList.setItems(data.getElementList());
}
});
}
Data class:
private ObservableList<PipeCable> pipeCableList;
private ObservableList<Element> elementList;
/**/
private ObservableList<StoredItem> displayedList;
public Data() {
this.pipeCableList = FXCollections.observableArrayList();
this.elementList = FXCollections.observableArrayList();
/**/
this.displayedList = FXCollections.observableArrayList();
}
public ObservableList<StoredItem> getPipeCableList() {
displayedList.removeAll(elementList);
displayedList.addAll(pipeCableList);
return displayedList;
}
public ObservableList<StoredItem> getElementList() {
displayedList.removeAll(pipeCableList);
displayedList.addAll(elementList);
return displayedList;
}
The problem is: when I change between options in ChoiceBox the data from both elementList and pipeCableList are mixed together and changing option in ChoiceBox has no result whatsoever.
What I'd like to achieve: be able to display different data which is contained in elementList and pipeCableList according to option selected with ChoiceBox. What's more, when one option is selected (one list is displayed) all new items which are added to this list will be visible on TableView.
EDIT(added missing info): PipeCable and Element extends StoredItem and TableView takes items of type StoredItem.
What's going wrong
Get rid of displayList, the TableView already has a reference to the list of items it displays, so just set that to the appropriate list. Currently your display list values are getting out of synch with your underlying data values.
Assumptions
I assume your TableView takes items of type StoredItem and both PipeCable and Element are also of type StoredItem (though inheritance or interface implementation).
How to fix it
Usually, you could just do:
tableView.setItems(data.getPipeCableList())
and the same for the element list as appropriate when it is chosen. But due to some limitations of Java generics that I can't seem to easily get around, that does not compile. If both the element list and pipe cable list were the same types (rather than children of a common parent type), it would be no issue.
To get around the generics issue, you can do:
tableView.getItems().setAll(data.getPipeCableList())
Which works fine, but does not keep the table view items in synch with the data items if the data items change.
To keep these in synch, you can do:
Bindings.bindContent(tableView.getItems(), data.getPipeCableList());
which is a bit ugly, but appears to work.
Full Sample App
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.stream.IntStream;
public class MultiListTable extends Application {
enum ItemType {
PipeCable, Element
}
#Override public void start(Stage stage) throws IOException {
TableView<StoredItem> tableView = new TableView<>();
TableColumn<StoredItem, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setPrefWidth(120);
tableView.getColumns().add(nameColumn);
Data data = new Data();
ChoiceBox<ItemType> choiceBox = new ChoiceBox<>(
FXCollections.observableArrayList(ItemType.values())
);
choiceBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
switch (newValue) {
case PipeCable:
Bindings.bindContent(tableView.getItems(), data.getPipeCableList());
break;
case Element:
Bindings.bindContent(tableView.getItems(), data.getElementList());
break;
}
});
choiceBox.getSelectionModel().select(0);
Button addPipe = new Button("Add Pipe");
addPipe.setOnAction(event -> data.getPipeCableList().add(
new PipeCable("Pipe " + (data.getPipeCableList().size() + 1))
));
IntStream.range(0, 3).forEach(i -> addPipe.fire());
Button addElement = new Button("Add Element");
addElement.setOnAction(event -> data.getElementList().add(
new Element("Element " + (data.getElementList().size() + 1))
));
IntStream.range(0, 2).forEach(i -> addElement.fire());
HBox controls = new HBox(10, choiceBox, addPipe, addElement);
VBox layout = new VBox(10, controls, tableView);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class Data {
private ObservableList<PipeCable> pipeCableList = FXCollections.observableArrayList();
private ObservableList<Element> elementList = FXCollections.observableArrayList();
ObservableList<PipeCable> getPipeCableList() {
return pipeCableList;
}
ObservableList<Element> getElementList() {
return elementList;
}
}
static public class StoredItem {
private final ReadOnlyStringWrapper name;
public StoredItem(String name) {
this.name = new ReadOnlyStringWrapper(name);
}
public String getName() {
return name.get();
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
}
static public class PipeCable extends StoredItem {
public PipeCable(String name) {
super(name);
}
}
static public class Element extends StoredItem {
public Element(String name) {
super(name);
}
}
}

Is there a "best" way to invoke a class method from a static method?

I have multiple controllers, each associated to a different FXML file. There is an event in one node that requires synchronization across other nodes, so I decided to do this with another event, and event handlers in the various controller files.
To register the event handlers requires the event handler method to be static (i.e., addEventHandler(SomeEvent, ClassName::MethodName).
So, the controller looks something like...
public class MyController {
private static MyController selfRef = null;
public MyController() {
selfRef = this;
}
public static void someEventHandler(Event event) {
if (selfRef != null) {
selfRef.doSomethingUseful();
}
}
private void doSomethingUseful() { /* synch the nodes */ }
}
This works, but seems a bit of a hack. Is there a preferred mechanism to achieve the same end result?
You might have more flexibility with this if you get rid of all the static stuff and make the event handler a member of your controller class as demonstrated below.
Sample implementation without static members
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.*;
import java.io.IOException;
class CustomerDialogController {
#FXML
private Label customerName;
private EventHandler<Event> customEventHandler = event -> {
// handle the event...
};
void initData(Customer customer) {
customerName.setText(customer.getName());
}
public EventHandler<Event> getCustomEventHandler() {
return customEventHandler;
}
}
public class EventHandling {
public Stage showCustomerDialog(Customer customer) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("customerDialog.fxml"));
Stage stage = new Stage(StageStyle.DECORATED);
stage.setScene(new Scene(loader.load()));
CustomerDialogController controller = loader.getController();
controller.initData(customer);
stage.addEventHandler(Event.ANY, controller.getCustomEventHandler());
stage.show();
return stage;
}
}
class Customer {
private String name;
Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Notes on implementation options
In the example the event handler has been added to the stage, but it could equally have been added to any scene or node or anything that has the ability to handle events.
If desired, you could also add a setter for the event handler to allow changing of the event handling logic externally.
In addition to the setup above you might wish to have the controller self-register the event handler in it's initialize method. Whether you do so or not just depends on whether you want the ability to register event handlers exposed outside the controller or if you want to use encapsulation to hide all of the event handling logic local to the controller.
Notes on (perhaps superior) alternatives
As an alternate approach, rather than using the event handling system within JavaFX for your custom approach, you could make use of a third party system such as the Google Guava Event Bus.
You should also consider why you need to add custom event handling to your application. JavaFX supports very flexible binding and observer patterns. By exposing properties of your model objects as observable, it is often not necessary to have custom events. Often, your view controllers can observe any changes to associated model objects and modify the internal state of model objects based upon UI interactions. This is especially the case if you introduce a dependency injection based system for injecting models into your controllers, such as Guice, Spring, afterburner.fx or Gluon Ignite.
Maybe you could use some kind of registry, which takes care of the synchronisation. Here is a quick and dirty example:
public class Synchronizer {
private ObservableList<Node> nodes;
private boolean isSyncing;
public Synchronizer() {
nodes = FXCollections.observableArrayList();
}
public void addNode(Node node) {
nodes.add(node);
}
public void sync(Node sourceNode, Event event) {
if (isSyncing) {
return;
}
isSyncing = true;
for (Node node : nodes) {
if (node != sourceNode) {
node.fireEvent(event);
}
}
isSyncing = false;
}
}
In your Controller you can add the node, whose event you like to get synchronized, to the synchronizer, and call sync() in the eventListener.
public class Controller {
private StackPane root;
private Button button;
public Controller(Synchronizer synchronizer) {
button = new Button();
button.setOnAction(evt -> {
synchronizer.sync(button, evt);
//action
});
synchronizer.addNode(button);
root = new StackPane(button);
}
}
EDIT:
This should make for a cleaner version:
public class Starter extends Application {
#Override
public void start(Stage primaryStage) {
ViewController controller1 = new ViewController();
ViewController controller2 = new ViewController();
Synchronizer synchronizer = new Synchronizer();
synchronizer.add(controller1);
synchronizer.add(controller2);
VBox box = new VBox(controller1.root, controller2.root);
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public interface SyncTarget {
Node getSyncNode();
void triggerAction();
}
public class Synchronizer {
private ObservableList<SyncTarget> syncTargets;
private EventHandler<Event> eventHandler;
public Synchronizer() {
syncTargets = FXCollections.observableArrayList();
eventHandler = e -> sync();
}
public void add(SyncTarget target) {
syncTargets.add(target);
target.getSyncNode().addEventHandler(ActionEvent.ANY, eventHandler);
}
public void remove(SyncTarget target) {
syncTargets.remove(target);
target.getSyncNode().removeEventHandler(ActionEvent.ANY, eventHandler);
}
public void sync() {
for (SyncTarget target : syncTargets) {
target.triggerAction();
}
}
}
public class ViewController implements SyncTarget {
private StackPane root;
private Button button;
public ViewController() {
button = new Button();
root = new StackPane(button);
}
#Override
public Node getSyncNode() {
return button;
}
#Override
public void triggerAction() {
//action
}
}
}

javafx how to transfer variable values from one controller to another [duplicate]

This question already has answers here:
Passing Parameters JavaFX FXML
(10 answers)
Closed 8 years ago.
i am new to javafx and i want to transfer variable values from one controller to another and i have no idea how to do this. so please help me.
for example:
i want to display username from first login window to second dashboard window so what should i do to save userid in one variable and send it to second window and display there in a label.
code test:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
/**
* FXML Controller class
*
* #author wabcon
*/
public class AdmissionController implements Initializable {
int userid=0;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
userid=10001;
}
}
how could i send this userid to next window controller.
please help me.
Thank you.
I am assuming you are doing this for a custom component.
So, you create a class for your custom component and set that class as the controller:
public class CustomControl extends AnchorPane implements Initializable {
String customId;
public CustomControl() {
//if you want to set a FXML
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/res/customControl.fxml"));
//Defines this class as the controller
fxmlLoader.setRoot(this);
//this.getStylesheets().add("/res/style.css"); <- if you want to set a css
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public String getCustomId() {
return customId;
}
public void setCustomId(String customId) {
return this.customId = customId;
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
//Initializes the controller
}
}
On your MainController:
CustomControl c = new CustomControl();
c.setCustomId("StackOverflow");

Change Eclipse toolbar icon dynamically

I've got a toolbar item with its own icon, defined in the plugin.xml file like:
<action
class="MyActionClass"
id="MyActionID"
label="MyActionLabel"
menubarPath="MyActionMenuBarPath"
toolbarPath="MyActionToolBarPath"
icon="icon/myicon.png" <---- this one
...
</action>
How do I change this dynamically when needed? I mean changing it from code
Use org.eclipse.ui.menus extension point instead and add menuContribution with dynamic. The class of dynamic should subclass ControlContribution and implement createControl method to create a button.
You should implements IElementUpdater in your Handler class.
Please refer to : https://stackoverflow.com/a/23742598/2893073
Handler class
import java.util.Map;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.commands.IElementUpdater;
import org.eclipse.ui.menus.UIElement;
import com.packpub.e4.menu.Activator;
public class SampleHandler2 extends
AbstractHandler implements IElementUpdater{
private static ImageDescriptor image_enable =
Activator.getImageDescriptor("icons/btn_adapt_enable.png");
private static ImageDescriptor image_disable =
Activator.getImageDescriptor("icons/btn_adapt_disable.png");
/**
* The constructor.
*/
public SampleHandler2() {
}
/**
* the command has been executed, so extract extract the needed information
* from the application context.
*/
public Object execute(ExecutionEvent event) throws ExecutionException {
//...
return null;
}
#Override
public void updateElement(UIElement element, #SuppressWarnings("rawtypes") Map map) {
boolean condition = false;
//...
if( condition ) {
element.setIcon(image_disable);
}else{
element.setIcon(image_enable);
}
}
}
invoke this Handler using ICommandService:
IWorkbenchWindow window = part.getSite().getWorkbenchWindow();
ICommandService commandService = (ICommandService) window.getService(ICommandService.class);
if (commandService != null) {
commandService.refreshElements("com.packpub.e4.menu.commands.sampleCommand", null);
}
Thanks.

Categories

Resources