Javafx Link/Bind Treeview Items to ObservableList - java

I'm trying to find an easy way of linking a TreeView of type Download to an ObservableList of the same type.
MainController.java
public class MainController {
private ObservableList<Download> downloads = FXCollections.observableArrayList();
#FXML private TreeView<Download> $TreeDownloads;
#FXML
public void initialize() {
$TreeDownloads.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
$TreeDownloads.setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
$TreeDownloads.setShowRoot(false);
downloads.addListener(new ListChangeListener<Download>() {
#Override
public void onChanged(Change<? extends Download> c) {
if (c.wasAdded()) {
addDownloads(c.getAddedSubList());
}
if (c.wasRemoved()) {
//
}
}
});
downloads.add(new Download("3847"));
downloads.add(new Download("3567"));
downloads.add(new Download("2357"));
}
private void addDownloads(List<? extends Download> downloads) {
downloads.forEach(download -> {
TreeItem<Download> treeItem = new TreeItem<>(download);
$TreeDownloads.getRoot().getChildren().add(treeItem);
new Thread(download::start).start();
});
}
private void removeDownloads(List<? extends Download> downloads) {
// remove treeitems from the treeview that hold these downloads
}
}
Download.java
public class Download {
private DoubleProperty progress = new SimpleDoubleProperty(0D);
private StringProperty id = new SimpleStringProperty("");
public Download(String id) {
this.id.set(id);
}
public void start() {
while (progress.getValue() < 1) {
try {
Thread.sleep(1000);
progress.add(0.1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
public String toString() {
return id.getValue();
}
}
How do i implement a remove by Object(Download) mechanism, and is there an easier way to bind observablelist's items to a treeview?

Still not entirely certain what the exact problem is, all pretty straightforward:
First off, your list change listener implementation is incorrect, it must advance the subChanges before accessing its state (you did run your posted code, or not ;)
downloads.addListener(new ListChangeListener<Download>() {
#Override
public void onChanged(Change<? extends Download> c) {
// this while was missing
while (c.next()) {
if (c.wasAdded()) {
addDownloads(c.getAddedSubList());
}
if (c.wasRemoved()) {
// accessing the list of removed elements is .. plain standard api
removeDownloads(c.getRemoved());
}
}
}
});
Now implement the removal of the corresponding treeItems:
private void removeDownloads(List<? extends Download> downloads) {
// remove treeitems from the treeview that hold these downloads
List<TreeItem<Download>> treeItemsToRemove = treeDownloads.getRoot().getChildren().stream()
.filter(treeItem -> downloads.contains(treeItem.getValue()))
.collect(Collectors.toList());
treeDownloads.getRoot().getChildren().removeAll(treeItemsToRemove);
}
Asides:
java naming conventions use lowercase letters for members: treeDownloads (not $TreeDownloads)
the "verifiable" in MCVE implies being runnable as-is: the poster should be the first to verify that ;) yours wasn't due to incorrect implementation of the listener
the "minimal" in MCVE means leaving out everything that's not needed: f.i. calling the threading code - which in your first snippet was particularly distracting because violating fx' threading rule is a rather common error

Related

JavaFX custom dialog has references after removing froom scene

I have a custom dialog that is added to the scene and then removed again. Doing profiling with VisualVM, I noticed that even after a GC run the instance of this dialog is still retained.
I know that this means that there must be a reference to that object somewhere so I had a look at the references:
As seen in the image there are a lot of references from this$ which means inner classes, in this case they are bindings or ChangeListeners. The change listener can be replaced with WeakChangeListener. I'm not quite sure how I should handle the Bindings however.
Furthermore there are some references that do not make much sense at first glance:
bean of type SimpleStringProperty or SimpleObjectProperty
oldParent and value of type Node$1
So here are the concrete questions:
How to get around these strong references, so the object can actually be garbage collected? Would the use of lambda expressions instead of anonymous inner classes have any effect in this respect? How to figure out where the object is references by bean, oldParent and value.
EDIT1:
The bean references of type SimpleStringProperty are used in the super class and therefore should not cause an issue here, I guess. One SimpleObjectProperty bean reference comes from a utility method that provides an EventHandler. How would I resolve that, is there something similar for EventHandler as for ChangeListeners?
EDIT2:
I tried to come up with a simple application to reproduce the same thing. I could manage it and saw that I have basically the same fields listed in the heap dump, but then noticed that I have retained a reference to the component that is removed from the scene in my application. Once I let go of that reference it was cleaned up. The only noticeable difference is in my small example there is no reference in an Object array.
EDIT3:
I did some digging and found two places in the code that when commented out or not used, will not cause the object become eligible for garbage collection. The first one is this ChangeListener:
sailorState.numberOfSailorsProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue,
Number oldValue, Number newValue) {
int inTavern = newValue.intValue()-sailorsAdditionalOnShip.get();
if (inTavern < 0) {
sailorsAdditionalOnShip.set(Math.max(sailorsAdditionalOnShip.get() + inTavern, 0));
inTavern = 0;
}
sailorsInTavern.set(inTavern);
}
});
The second one is a bit more complex. The component is a Dialog that has a close button. On pressing that one the dialog closes. This is the code of the button, I do not think that with this part is the problem, but for completeness sake:
public class OpenPatricianButton extends Control {
protected final StringProperty text;
protected final ReadOnlyObjectProperty<Font> currentFont;
protected final ObjectProperty<EventHandler<MouseEvent>> onAction;
public OpenPatricianButton(String text,
final Font font) {
super();
this.text = new SimpleStringProperty(this, "text", text);
this.currentFont = new ReadOnlyObjectPropertyBase<Font>() {
#Override
public Object getBean() {
return this;
}
#Override
public String getName() {
return "currentFont";
}
#Override
public Font get() {
return font;
}
};
this.onAction = new SimpleObjectProperty<EventHandler<MouseEvent>>(this, "onAction");
this.getStyleClass().add(this.getClass().getSimpleName());
}
#Override
public String getUserAgentStylesheet() {
URL cssURL = getClass().getResource("/ch/sahits/game/javafx/control/"+getClass().getSimpleName()+".css");
return cssURL.toExternalForm();
}
public StringProperty textProperty() {
return text;
}
public String getText() {
return text.get();
}
public void setText(String text) {
this.text.set(text);
}
public Font getFont() {
return currentFont.get();
}
public ObjectProperty<EventHandler<MouseEvent>> onActionProperty() {
return onAction;
}
public EventHandler<MouseEvent> getOnAction() {
return onAction.get();
}
public void setOnAction(EventHandler<MouseEvent> onAction) {
this.onAction.set(onAction);
}
}
public class OpenPatricianSmallWaxButton extends OpenPatricianButton {
public OpenPatricianSmallWaxButton(String text,
final Font font) {
super(text, font);
}
#Override
protected Skin<?> createDefaultSkin() {
return new OpenPatricianSmallWaxButtonSkin(this);
}
public OpenPatricianSmallWaxButton(String text) {
this(text, Font.getDefault());
}
}
public class OpenPatricianSmallWaxButtonSkin extends SkinBase<OpenPatricianSmallWaxButton> {
public OpenPatricianSmallWaxButtonSkin(final OpenPatricianSmallWaxButton button) {
super(button);
InputStream is = getClass().getResourceAsStream("sealingWaxFlattend.png");
Image img = new Image(is);
final ImageView imageView = new ImageView(img);
final Label label = new Label();
label.textProperty().bind(button.textProperty());
label.getStyleClass().add("OpenPatricianSmallWaxButtonLabeled");
label.setFont(button.getFont());
label.onMouseClickedProperty().bind(button.onActionProperty());
label.textProperty().bind(button.textProperty());
imageView.onMouseReleasedProperty().bind(button.onActionProperty());
StackPane stack = new StackPane();
stack.getChildren().addAll(imageView, label);
Group group = new Group(stack);
group.setManaged(false);
button.setPrefHeight(img.getHeight());
button.setPrefWidth(img.getWidth());
getChildren().add(group);
}
}
And here is the code fragment where the button is instantiated:
closeButton = new OpenPatricianSmallWaxButton("X", font);
closeButton.setLayoutX(WIDTH - CLOSE_BUTTON_WIDTH - CLOSE_BUTTON_PADDING);
closeButton.setLayoutY(CLOSE_BTN_Y_POS);
closeButton.setOnAction(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
executeOnCloseButtonClicked();
}
});
closeButton.getStyleClass().add("buttonLabel");
getContent().add(closeButton);
The call to remove the button is done through Guava AsyncEventBus. Therefore the code is a bit length. It starts in the Application thread and then gets posted to the event bus thread which then eventually has to call Platform.runLater:
protected void executeOnCloseButtonClicked() {
ViewChangeEvent event = new ViewChangeEvent(MainGameView.class, EViewChangeEvent.CLOSE_DIALOG);
clientEventBus.post(event);
}
public void handleViewChange(ViewChangeEvent event) {
if (event.getAddresse().equals(MainGameView.class)) {
if (event.getEventNotice() instanceof DialogTemplate) {
setNewDialog((DialogTemplate) event.getEventNotice());
} else {
sceneEventHandlerFactory.getSceneEventHandler().handleEvent(event.getEventNotice());
}
}
}
public void handleEvent(Object eventNotice) {
Preconditions.checkNotNull(dialogContoller, "Dialog controller must be initialized first");
if (eventNotice == EViewChangeEvent.CLOSE_DIALOG) {
dialogContoller.closeDialog();
}
....
public void closeDialog() {
if (Platform.isFxApplicationThread()) {
closeDialogUnwrapped();
} else {
Platform.runLater(() -> closeDialogUnwrapped());
}
}
private void closeDialogUnwrapped() {
if (dialog != null) {
new Exception("Close dialog").printStackTrace();
getChildren().remove(dialog);
dialog = null;
dialogScope.closeScope();
}
}
The really peculiar thing is that the dialog can be cleaned up by the GC (provided the first issue with the ChangeListener is commented out) when I call closeDialog from a timer. In other words this behaviour does only happen if I close the dialog with a mouse click.

JavaFX8 TableView with custom control

I want to use a custom control (ClientControl in the code) in my TableView. Therefore I created a class ClientCell:
public class NewClientCell extends TableCell<Client, Client> {
private final ClientControl cc;
public NewClientCell(ObservableList<Client> suggestions) {
cc = new ClientControl(this.getItem(), suggestions);
this.setAlignment(Pos.CENTER_LEFT);
this.setGraphic(cc);
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Client c, boolean empty) {
super.updateItem(c, empty);
if(!empty){
setGraphic(cc);
}
}
}
In the main program I use the following code to fill the table:
TableColumn<Client, Client> clmClients = new TableColumn<>("Klient");
clmClients.setCellFactory(new Callback<TableColumn<Client, Client>, TableCell<Client, Client>>() {
#Override
public TableCell<Client, Client> call(TableColumn<Client, Client> p) {
return new NewClientCell(suggestions);
};
});
clmClients.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Client, Client>, ObservableValue<Client>>() {
#Override
public ObservableValue<Client> call(TableColumn.CellDataFeatures<Client, Client> p) {
return new SimpleObjectProperty<Client>(p.getValue());
}
});
getColumns().add(clmClients);
The data in the table comes from an ObservableList and is initialized correct.
My problem now is that the custom control needs an Client-Object which it should get out of the ObservableList, but "this.getItem()" always returns null.
How do I get the Client objects correctly into the custom control?
Thanks!
EDIT
Here's the constructor of ClientControl:
public ClientControl(Client client, ObservableList<Client> suggestions) {
setClient(client);
setSuggestions(suggestions);
FXMLLoader loader = new FXMLLoader(getClass().getResource("ClientControl.fxml"));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
initTextField();
setLabelText(client.toString());
}
The method setClient is a simple setter method (this.client = client;). The variables client and suggestions are this simple defined:
private ObservableList<Client> suggestions;
private Client client;
AFAIK, you should instantiate any controls in the constructor as you did, so that they are only created once (remember that cells get reused for different locations).
But then you need to override one or more of the other methods such as updateItem to get the data from the current item to render.
EDIT
Well, you're assigning the same control without changing it over and over again. Rather than setting the graphics in the updateItem method, set the item property of the client control:
#Override
protected void updateItem(Client c, boolean empty) {
super.updateItem(c, empty);
if(!empty){
cc.setClient(c);
} else {
cc.setClient(null);
}
}
Edit 2
The ClientControl should provide the client item as a property instead of a constructor argument and set it in the updateItem method, not in the constructor.
E.g. something like this (untested!):
private final ObjectProperty<Client> client = new SimpleObjectProperty<>(this, "client");
public final Client getClient(){
return clientProperty().get();
}
public final void setClient(Client client){
clientProperty().set(client);
}
public ObjectProperty<Client> clientProperty(){
return client;
}
And in the constructor: listen for changes of this property to set the labelText etc.
You also might want to provide a constructor without a client argument, as it is not available when you instantiate it in the TableCell constructor.
So I found the solution for my problem. Thank you so much Puce for your help! :-)
Now I set Client via the property like that:
private ObjectProperty<Client> clientProperty = new SimpleObjectProperty<Client>();
Additionally I added a ChangeListener in the constructor of ClientControl:
public ClientControl(ObservableList<Client> suggestions) {
clientProperty.addListener(new ChangeListener<Client>() {
#Override
public void changed(ObservableValue<? extends Client> observable, Client oldValue,
ClientnewValue) {
if(newValue != null) {
setLabelText(newValue.toString());
}
}
});
setSuggestions(suggestions);
FXMLLoader loader = new FXMLLoader(getClass().getResource("ClientControl.fxml"));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
initTextField();
}
My ClientCell class needed only some simple changes because of the changes in ClientControl:
public class NewClientCell extends TableCell<Client, Client> {
private final ClientControl cc;
public NewClientCell(ObservableList<Client> suggestions) {
cc = new ClientControl(suggestions);
this.setAlignment(Pos.CENTER_LEFT);
this.setGraphic(cc);
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Client c, boolean empty) {
super.updateItem(c, empty);
if(!empty){
cc.setClient(c);
}
}
}
In the main program nothing changed.
In conclusion I would like to thank Puce one more time, I stuck at this problem for many days...

Event between actors

I'm trying to use events to do something on actor but i don't understand how to do it properly.
I have a button on my screen and a text (just for example). they are both actors in a stage
my purpose is: if i click on the button, i would like to change text
I add listener on my button, i get the click but i don't know how to send event (or anything else) to my text to set it.
Main class with stage definition and his
public class AGame implements ApplicationListener {
private WorldRendererTouchPad renderer;
private Stage stage;
private static Vector3 cameraVelocity=new Vector3(0,0,0);
private ButtonJump button;
public static final int SCREEN_WIDTH=800;
public static final int SCREEN_HEIGHT=480;
public void create() {
stage = new Stage();
stage.setViewport(SCREEN_WIDTH, SCREEN_HEIGHT, true);
stage.getCamera().translate(-stage.getGutterWidth(), -stage.getGutterHeight(), 0);
renderer = new MyRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);
stage.addActor(renderer);
renderer.create();
button=new ButtonJump();
stage.addActor(button);
button.create();
Gdx.input.setInputProcessor(stage);
}
....
resize and other methods
}
MyRenderer class (contains text actor):
public class MyRenderer {
private TextTest text;
public MyRenderer(float screenWidth, float screenHeight) {
setBounds(0, 0, screenWidth, screenHeight);
}
public void create() {
this.initActors();
}
private void initActors() {
text=new TextTest("Hello world!");
addActor(text);
}
// is it usefull?
public void setText(String newText) {
text.setText(newText);
}
}
and the ButtonJump class (extends MyButton just here for define Skin and ButtonStyle)
public class ButtonJump extends MyButton {
public boolean isJump=false;
private static InputListener buttonListener=new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
Gdx.app.log("event" , "="+event.toString());
// do something to update text
return true;
}
};
public ButtonJump() {
super();
}
public void create() {
this.setPosition(getStage().getWidth()-60, 30);
this.addCaptureListener(buttonListener);
}
public void capture() {
if (this.isJump)
Gdx.app.log("jump button", "Jump is set");
else
Gdx.app.log("jump button", "No jump");
}
}
If you use the clicklistener you need to let the other actor hold an reference to it to call a method on click. It is not that good to let all Actor know of each other. Use an anonymous way.
There is a "common" system for it in games.
If you really want to use Events, do implement an Event-System. Therefore you have an interface Listen and an Interface Event_Handler. At the start of your game you init one Implementation of the Eventhandler. The interface should at least look like this:
public interface Interface_EventHandler extends Disposable
{
public void handleEvent(final Event... e);
public void registerListener(final Interface_Listen listener,
final Event_Type... type);
public void unregisterListener(final Interface_Listen... listener);
public void unregisterAllListener();
public void unregisterAllListener(final Event_Type... type);
public void processEvents();
public void processEvents(final int maxTimeInMS);
}
Okay so now how does it work. The handler has an hashmap with all eventtypes as Key and an list of listeners as Value. So if someone want to notice an event he registers with the registerListerner at the handler for the right Event_Type (Enum). It need to have the interface Listen to get events. Everyone can now push an Event into the handler with the handleEvent(...) method. Or even more than one.. (varargs) ..
Okay that still does not explain how it work. We now have a registered listener (actor for example) and we have events that get into the handler.
Every Rendercycle you call the processEvents() at the hanlder once. That mean that every event that get pushed in at a frame get handled at the next frame. (Asynchronus) While that he iterates over all events and push them to the listeners. Moreover the listener should have a queue too where they put all events and when they are at their .act() they handle the events. (more asynchronus).
Okay here is an Handler i use:
package com.portaaenigma.eventsystem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import com.badlogic.gdx.utils.TimeUtils;
import com.portaaenigma.managers.Logger;
public class EventHandler implements Interface_EventHandler
{
private HashMap> listeners;
private LinkedList events;
public EventHandler()
{
listeners = new HashMap<Event_Type, ArrayList<Interface_Listen>>();
// add the arraylist for every Eventtype
for (Event_Type e : Event_Type.values())
{
listeners.put(e, new ArrayList<Interface_Listen>());
}
events = new LinkedList<Event>();
}
#Override
public void handleEvent(final Event... e)
{
for (Event event : e)
{
events.push(event);
}
}
#Override
public void unregisterListener(final Interface_Listen... listener)
{
for (Event_Type e : Event_Type.values())
{
for (Interface_Listen interface_Listen : listener)
{
listeners.get(e).remove(interface_Listen);
}
}
}
#Override
public void processEvents()
{
while (events.size() != 0)
{
// get the first element and delete it
Event e = events.pop();
for (Interface_Listen l : listeners.get(e.getType()))
{
l.handleEvent(e);
}
}
}
#Override
public void processEvents(final int maxTimeInMS)
{
int startSize = 0;
if (events.size() != 0)
{
startSize = events.size();
Logger.log("Processing Events: " + events.size());
}
long startTime = TimeUtils.millis();
while (events.size() != 0)
{
// get the first element and delete it
Event e = events.pop();
for (Interface_Listen l : listeners.get(e.getType()))
{
l.handleEvent(e);
}
// stop handling if time is up
if (startTime - TimeUtils.millis() > maxTimeInMS)
{
Logger.log("Handled " + (events.size() - startSize) + " Events");
break;
}
}
}
#Override
public void registerListener(final Interface_Listen listener,
Event_Type... type)
{
for (Event_Type event_Type : type)
{
listeners.get(event_Type).add(listener);
}
}
#Override
public void unregisterAllListener()
{
Logger.log("UnregisterAll");
for (Event_Type e : Event_Type.values())
{
listeners.get(e).clear();
}
}
#Override
public void unregisterAllListener(final Event_Type... type)
{
for (Event_Type event_Type : type)
{
listeners.get(event_Type).clear();
}
}
#Override
public void dispose()
{
unregisterAllListener();
events.clear();
listeners.clear();
}
}
The interface for all listeners is simple it's just this:
public interface Interface_Listen
{
public void handleEvent(final Event e);
}
Last but not least the event. How can you now send different data? Quiet simple. Have an hashmap out of Strings and Strings and for sure the EventType.
public class Event
{
private Event_Type type;
private HashMap<String, String> m_messages;
public Event(final Event_Type e, final Event_Message... m)
{
m_messages = new HashMap<String, String>();
for (Event_Message message : m)
{
m_messages.put(message.m_key, message.m_value);
}
type = e;
}
public Event_Type getType()
{
return type;
}
public void addMessages(final Event_Message... m)
{
for (Event_Message event_Message : m)
{
m_messages.put(event_Message.m_key, event_Message.m_value);
}
}
public String getMessage(final String name)
{
if (m_messages.get(name) == null)
{
Logger.error("Message not found: " + name);
}
// if null return an empty string
return m_messages.get(name) != null ? m_messages.get(name) : "";
}
public void clearMessages()
{
m_messages.clear();
}
}
Okay i hope this does explain how to implement an EventSystem at a Game. This meight not be the regular way at other Software but in games you queue up the events and handle them once in a Gameloop cycle. Also the listeners do the same.
So in your case. Implement such an handler and register the actors as listener. Sure they need to implement the listener interface and do something with the event. Let the one actor push an event into the handler which directs to the other actor and your are done. And they event dont need to know of each other and it does work for as much actors as you whish. You can even create 1 event for different classes different actors and so on. Usefull for example at mapchange. You push one event with the notice.. "changemap".. and every actor knows he need to stop moving and every subsystem knows that it does need to stop because of an mapchange and so on ...
It seems to be a bit overkill but it has alot of advantages and worth to use even at early stages. I made the misstake and started using it laterly and now i regret it.
Sorry for the bracing. It's not the regular java standart but more clear i think... Sorry for alot of varargs just like it at that point. Meight be confusing.
Literatur:
Game Coding Complete, Fourth Edition Chapter 11

Is it possible - to template this method?

I am new in Java and i have a few questions for more advanced developers.
I have Swing-based GUI application in which I have several AbstractActions.
A large group of AbstractActions creates new tab based on JPanel. For example:
// opens "Documents" tab
documentsAction = new AbstractAction(DOCUMENTS) {
#Override
public void actionPerformed(ActionEvent e) {
try {
int index = getTabIndex(DOCUMENTS);
if (index >= 0) {
// Tab exists, just open it.
tabbedPane.setSelectedIndex(index);
} else {
// No tab. Create it and open
newCatalogTab(new DocumentService(), DOCUMENTS);
}
} catch (ServiceException ex) {
printError(ex.getMessage());
}
}
};
documentsItem.setAction(documentsAction);
Where getTabIndex is:
private int getTabIndex(String tabName) {
int result = -1;
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
if (tabName.equals(tabbedPane.getTitleAt(i))) {
result = i;
break;
}
}
return result;
}
and newCatalogTab is:
private void newCatalogTab(ICatalog service, String Name) throws ServiceException {
CatalogPanel panel = new CatalogPanel(service);
tabbedPane.add(Name, panel);
tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
checkTabs(); // changes activity of buttons like "edit" and "delete"
}
So, many AbstractAction do the similar work:
Create instance of class, that extends AbstractPanel;
Pass data access interface (DocumentService in example) to instance;
Create a new tab with instance.
Can I somehow template this if data access interfaces will use different POJO's?
Can I create Generic interface and use it?
Can you show me right direction for thinking?
Thanks for wasting your time.
There are no templates in Java, so there will be some code duplication in any case. However, you can cut some of the boilerplate code by using factories. For example:
interface CatalogFactory {
public ICatalog makeCatalog();
}
class DocumentServiceFactory implements CatalogFactory {
#Override
public ICatalog makeCatalog() {
return new DocumentService();
}
}
class TabAction extends AbstractAction {
private final String name;
private final CatalogFactory factory;
//Appropriate constructor...
#Override
public void actionPerformed(ActionEvent e) {
//...
newCatalogTab(factory.makeCatalog(), name);
//...
}
}
Then you can do
documentsItem.setAction(new TabAction(DOCUMENTS, new DocumentServiceFactory()));
without having to create a separate anonymous AbstractAction for each tab.
Similarly for panels and possibly other objects where this pattern fits.

GWT Editors - how to add N sub-editors of the same type based on a Collection

I have an object, Supply, that can either be an ElecSupply or GasSupply (see related question).
Regardless of which subclass is being edited, they all have a list of BillingPeriods.
I now need to instantiate N number of BillingPeriodEditors based on the contents of that list, and am pretty baffled as to how I should do it.
I am using GWTP. Here is the code of the SupplyEditor I have just got working:
public class SupplyEditor extends Composite implements ValueAwareEditor<Supply>
{
private static SupplyEditorUiBinder uiBinder = GWT.create(SupplyEditorUiBinder.class);
interface SupplyEditorUiBinder extends UiBinder<Widget, SupplyEditor>
{
}
#Ignore
final ElecSupplyEditor elecSupplyEditor = new ElecSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor> elecSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor>(
elecSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof ElecSupply);
if(!(value instanceof ElecSupply))
{
showGasFields();
}
else
{
showElecFields();
}
}
};
#Ignore
final GasSupplyEditor gasSupplyEditor = new GasSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor> gasSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor>(
gasSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof GasSupply);
if(!(value instanceof GasSupply))
{
showElecFields();
}
else
{
showGasFields();
}
}
};
#UiField
Panel elecPanel, gasPanel, unitSection;
public SupplyEditor()
{
initWidget(uiBinder.createAndBindUi(this));
gasPanel.add(gasSupplyEditor);
elecPanel.add(elecSupplyEditor);
}
// functions to show and hide depending on which type...
#Override
public void setValue(Supply value)
{
if(value instanceof ElecSupply)
{
showElecFields();
}
else if(value instanceof GasSupply)
{
showGasFields();
}
else
{
showNeither();
}
}
}
Now, as the list of BillingPeriods is a part of any Supply, I presume the logic for this should be in the SupplyEditor.
I got some really good help on the thread How to access PresenterWidget fields when added dynamically, but that was before I had implemented the Editor Framework at all, so I think the logic is in the wrong places.
Any help greatly appreciated. I can post more code (Presenter and View) but I didn't want to make it too hard to read and all they do is get the Supply from the datastore and call edit() on the View.
I have had a look at some examples of ListEditor but I don't really get it!
You need a ListEditor
It depends of how you want to present them in your actual view, but the same idea apply:
public class BillingPeriodListEditor implements isEditor<ListEditor<BillingPeriod,BillingPeriodEditor>>, HasRequestContext{
private class BillingPeriodEditorSource extends EditorSource<BillingPeriodEditor>{
#Override
public EmailsItemEditor create(final int index) {
// called each time u add or retrive new object on the list
// of the #ManyToOne or #ManyToMany
}
#Override
public void dispose(EmailsItemEditor subEditor) {
// called each time you remove the object from the list
}
#Override
public void setIndex(EmailsItemEditor editor, int index) {
// i would suggest track the index of the subeditor.
}
}
private ListEditor<BillingPeriod, BillingPeriodEditor> listEditor = ListEditor.of(new BillingPeriodEditorSource ());
// on add new one ...
// apply or request factory
// you must implement the HasRequestContext to
// call the create.(Proxy.class)
public void createNewBillingPeriod(){
// create a new one then add to the list
listEditor.getList().add(...)
}
}
public class BillingPeriodEditor implements Editor<BillingPeriod>{
// edit you BillingPeriod object
}
Then in you actual editor edit as is in the path Example getBillingPeriods();
BillingPeriodListEditor billingPeriods = new BillingPeriodListEditor ();
// latter on the clickhandler
billingPeriods.createNewBillingPeriod()
You are done now.

Categories