I am worried that I might end up with a lot of "undead" Objects inside my Listener List
that still get notified even though they could simply be removed.
Assume the following classes:
This is my ListView, a UI component where I use the addItem method to populate the list with objects.
After a while I might call clear to remove every Item in the List:
//Displays some Model objects
ListView
{
//Creates a new Cell via createCell and adds the Cell to the ItemList
public void addItem(MyModelObject obj) { ... }
//Simply cleares the ItemList
public void clear() { ... }
...
//Creates a new Cell to be added to the ListView
private ListCell createCell(MyModelObject obj)
{
//Create the Controller for the Cell and return the Cell
return new ListCellController(obj).getCellUI();
}
}
This is the ListCellController seen in the createCell method above. It holds the reference to a Model Object and is responsible for setting the correct contents in the ListCell it controls.
The Controller sets itself as a Listener in the Model Object:
//Handles the Content that is displayed in a ListCell
ListCellController implements MyModelObjectListener
{
private final ListCell _ui = ...
public ListCellController(MyModelObject obj)
{
obj.addListener(this); //Get informed if Model Object changes
_ui.setText(obj.getName()); //Control what is displayed in the Cell
}
//Returns the UI Object that is controlled by this Controller
public ListCell getCellUI() { return _ui; }
...
}
This is the Model Object, it does some stuff and occasionally notifies its listeners if it changed.
//My Model Object that informs listeners if it changed
MyModelObject
{
private List<MyModelObjectListener _listeners = ...;
...
public void addListener(MyModelObjectListener listener) { ... }
}
Now, what happens if I add some Items to the ListView and then clear it? The ListView never holds any references to Controller objects, only to ListCells. If the ListView is cleared, all the references to ListCells are discarded.
The ListCells do not know if they are currently displayed or already discarded and of course the Controller cannot know either.
I assume, that now, even though the ListView is empty, the Model Object still holds the references to the Controllers in the _listeners List. Because of that, the Controllers are not garbage-collected and thus, the ListCells are neither.
Does that mean that, if I add and remove a lot of Items from my ListView I will end up with a huge List of Listeners? How can I avoid this If I do not know if a ListCell is displayed or not?
TL;DR:
Will my _listeners List prevent garbage-collection of Controllers and ListCells?
I'm sorry, but I'm not understanding well what is your problem, but for what I know about garbage collection, even if two object refer to each other, if none of them is referenced in runtime, they will be deleted anyway.
So, if your problem is about this, if you have at least one listener referenced in runtime, it will not be garbage collected, therefore, you would have to remove it manually.
I hope I could be of any help.
Related
I'm trying to use a ListView as an Editor for Strings, that come out of a custom data model. I use TextFieldListCells with an appropriate StringConverter for the cells.
There is an add button next to the ListView that calls this method on action:
#FXML
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.getSelectionModel().select(wordListItems.indexOf(newItem));
wordListView.edit(wordListItems.indexOf(newItem));
wordListView.setEditable(false);
}
Where wordListView is the ListView and wordListItems is the ObservableList containing the data for the wordListView.
This does work, except for when the list is empty (not null), and I couldn't quite explain why, so I inspected the Java source code for help.
Here's what I found out so far: the edit(int) call on ListView changes the ListViews internal editIndex value, which is supposed to call the EDIT_START Event. The editIndex is an ReadOnlyIntegerWrapper in which I found some weird code that I can't quite understand and I'm not sure if thats actually producing a bug or I just can't see why they did it:
#Override
protected void fireValueChangedEvent() {
super.fireValueChangedEvent();
if (readOnlyProperty != null) {
readOnlyProperty.fireValueChangedEvent();
}
}
This method is called whenever the editIndex property of ListView is changed. The problem: readOnlyProperty is null, because it's not set anywhere. The only place I could find where it got set is in the getter:
public ReadOnlyIntegerProperty getReadOnlyProperty() {
if (readOnlyProperty == null) {
readOnlyProperty = new ReadOnlyPropertyImpl();
}
return readOnlyProperty;
}
(ReadOnlyIntegerImpl is an inner private class and readOnlyProperty is it's type)
Now to my actual question: Is this a bug or am I overseeing something? Is there a reason why I can't add and edit a newly created Element in my list like that when it's empty, or is it really just this getter not being called yet?
The source code you found just is code for lazy initializing the property.
Unless new value is assigned to the property or the property itself is requested, null can be used as the property to avoid unnecessary creation of property objects. This is not an issue here.
The issue seems to be the ListView cells not being updated before edit is called. This happens during layout, so "manually" calling layout before starting the edit should work:
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.layout();
wordListView.edit(wordListItems.size()-1);
wordListView.setEditable(false);
}
I have 2 classes, one class is a datamodel class and the other is a gui class.
class datamodel .........
Takes user selected data and creates a arraylist
class gui .........
Has a tableviewer that is using the array from the datamodel class
My issue is I need to fresh the tableviewer when the user has added more data to the array.
I created a updateTableViewer method in the gui class.
public void updateTableViewer() {
if(getViewer() != null) {
viewer.refresh();
{
Then I made a reference to the gui class in the datamodel class.
AplotBaseDialog abd = new AplotBaseDialog(null, null);
Then I added the method call to the method adding more data to the array
public void add(TCComponentItemRevision tcRevision, TCComponentDataset selectedDataset) {
AplotDatasetData pp = new AplotDatasetData(tcRevision, selectedDataset);
if (!dataArrayList.contains(pp)) {
dataArrayList.add(pp);
}
abd.updateTableViewer();
}// end add()
This does not work. The getViewer() call always returns null, even if the gui class is created and open.
So I create a boolean value;
Boolean hasViewerBeenCreated = false;
I am setting the value to true after the tableviewer is created.
viewer = new AplotDataTableViewer(parent, SWT.BORDER|SWT.V_SCROLL|SWT.FULL_SELECTION);
viewer.setInput(AplotDataModel.getInstance().getArrayData());
hasViewerBeenCreated = true;
Then I created a method to return the boolean value.
I am calling the method from a button on the dailog.
I also replaced the updateTableViewer method call in the datamodel class
if (!dataArrayList.contains(pp)) {
dataArrayList.add(pp);
}
abd.getBooleanValue();
}
Here are the results.
I execute the add method in the datamodel class -
It returns false - That makes sense because the dailog has not been created at this time
I execute and create the gui class
I click the button and it returns true - This makes sense because the viewer has been created
Here is where I get confused.
With the gui still open, I can execute the add method again and it stills returns a false value. Then I can click the button and see it is true value.
I would think that when the gui is created and the boolean value is set to true. I would be able to get the current value of the boolean in other classes.
I am not sure if I am not referencing the gui class correctly or when the gui is created I am not accessing the current thread or tableviewer?
I don't know if this is a thread issue or I am just not getting the current value from the gui correctly.
This is a big issue with my application right now. I have to be able to refresh the tableviewer any time new data is added to the array. I can not have the user having to manually refresh the table every time that select new data
In my current Apache Wicket project I have a search form for querying the database and displaying the query results in a ListView. The search input box is on the same page as the ListView with the results, and that ListView is filled with query results from a DAO, during invocation of the onSubmit() method of the form.
Everything works fine, but I need to display the number of search results. I tried to create a Label that is filled with the value of the size() method of the list got by the getList() method of the ListView instance, but no luck.
Thank you for any help in advance.
Depending on how you have built this form, you might only need to do label.setModelObject(listResults.size()). It's difficult to tell without seeing how are you doing it.
By what you're telling in your question, probably you're creating your Label like this new Label(labelId, listView.getList().size(). This won't work, you're setting the Label's Model at construction time with a constant value, that's the size of the list at construction time. You need to get that value inside a Model's getObject() to make the value "dynamic". Like, for instance,
AbstractReadOnlyModel sizeModel = new AbstractReadOnlyModel(){
public getObject(){
return listView.getList().getSize();
}
}
new Label(labelId, sizeModel);
With this, every time the page renders, sizeModel().getObejct() will be called to retrieve the value for the Label. In that other way, the Label has got a Model with a constant value.
You could even do label.setModelObject(list.size()) in the onSubmit() method.
From my ignorance on how you have built this form, I'll show you how would I do this. The List of results would be retrieved with a LoadableDetachableModel. That would be the Model of the ListView. Then, the Label can have for instance an AbstractReadOnlyModel that uses the ListViews modelObject to get its size.
public class MyForm extends Form {
private LoadableDetachableModel resultsModel;
private IModel searchModel;
public MyForm(){
searchModel = new Model();
TextField searchTextField = new TextField("search", searchModel);
resultsModel = new LoadableDetachableModel(){
protected Object load(){
return myService.get(searchModel.getModelObject());
}
}
ListView lv = new ListView("list", resultsModel){
// ...
}
Label resultsCount = new Label("count", new AbstractReadOnlyModel(){
public Object getObject(){
return ((List) resultsModel.getObject()).size();
}
})
SubmitButton button = new SubmitButton(){
public void onSubmit(){
//... No actions needed, really
}
}
// add's...
}
}
Using a LoadableDetachableModel for the ListView has the advantage of automatically detaching the Model, and therefore avoiding the whole List of results to get serialized into the Session.
I am trying to use the MVC design.
In the model I wanted a method like this
public boolean changeSomeData(...){
boolean b;
//create a dialog with an OK button
return b;
}
I want the method to return TRUE if the changes were actually made. The changes are done inside the actionPerformed method of the OK button.
My problem is that I can't write b=true; inside the actionPerform of the OK button, because I have to declare b as final in order to use it in the actionPerformed().
What I did is creating a class
private class MyBoolean {
boolean b;
}
and then
public boolean changeSomeData(...){
MyBoolean myBoolean;
//create a dialog with an OK button
actionPerformed(){
//make changes in the data
myBoolean.b=true;
}
boolean b = myBoolean.b;
return b;
}
But I don't feel good about this solution and I wanted to know if it is correct what I did and if there is a better solution.
Should I better throw an exception if the changes aren't made? (for example, if the user clicks "cancel" instead of "ok")
In the model I wanted a method like this ... //create a dialog with an OK button
I'd say this is a flaw already, since the model should not do anything with views directly.
A better approach would be to open the dialog (using the controller), register the controller for the ActionEvent of "OK" (and thus actionPerformed) and then do whatever changes should be done in that method.
Edit:
You might want to consider the following rough approach:
The views register themselves or associated classes to the model as listeners. Whenever the model is changed it fires events to notify the views of the change.
The controller registers itself on the views and is notified when the views change. If a user changes data, the controller then might open the dialog and only commit the changes of the user signals "OK". Thus the model has never to check itself if data needs to be changed. That is actually the controller's task and if the controller passes changes to the model, it should apply them.
A better way to achieve your task is to keep a variable on the dialog that indicates if a successful change was made. Then have a method that your model class calls to retrieve the value and return it.
Something like:
public boolean changeSomeData(...){
//create a dialog with an OK button
return dialog.isSuccess();
}
One way you could make this code a bit cleaner...
public boolean changeSomeData() {
// note that this is not a class boolean, no need to do extra autoboxing.
boolean dataChanged = false;
// check the old value against the new value
// for classes
if (oldvalue.equals(newValue)) {
oldValue = newValue;
dataChanged = true;
}
// for pimitives (built-ins)
if (oldvalue == newValue) {
oldValue = newValue;
dataChanged = true;
}
// odds are good that the above action performed was supposed to call this
// changeSomeData() and not the other way around.
// if you must fire actionPerformed() when data has changed, then do so
// like this, otherwise if it was added as part of the "solution" you can
// skip it.
if (dataChanged) {
actionPeformed();
}
return dataChanged;
}
Note that this code is Controller code, as it manipulates the model directly, and (possibly) updates views.
I've problem regarding GUI with one Menu and one Order Class.
I've created a variable to store how many items have been selected in the Menu Class.
private int totalSelected;
The var totalSelected is live updated. It can be changed anytime depending on actionPerformed() function.(Exp: totalSelected will add up all the selected items)
In the Order Class, how can I access to the live update variable totalSelected in order to retrieve the live update value? When I invoke getTotalSelected() function inside the Menu Class, I will only obtain a 0 value.
Thanks for your help ^^!
Please allow me to specify my question clearer.
public class MenuTab extends JPanel
{
private JLabel display;
private int totalSelected;
public MenuTab()
{
....
}
}
public getTotalSelected(){
return totalSelected;
}
private class SelectedListener implements ActionListener
{
public void actionPerformed()
{
.......
//Assume that totalSelected has been updated!
display = new JLabel("Total: " + totalSelected);
// OK to display totalSelected live value here.
}
}
// A new class is the confirmation of order
public class OrderConfirmedTab extends JPanel{
private JLabel displayTotal;
private MenuTab order = new MenuTab();
public OrderConfirmedTab()
{
......
int totalSelected = order.getTotalSelected();
displayTotal = new JLabel("Total: " + totalSelected);
// Problem to display totalSelected live value here.
// Will obtain 0;
// How can I obtain the live updated value from class MenuTab? Thanks!
}
}
If I read your code right, you need to make your variable be private static int totalSelected; You need to make it static so that it stays the same for all instances of the class.
I looks like your not updating the private int totalSelected; variable when a user makes a selection, so it is always 0.
Ya! I just realized that my JLabel
will not update the value
automatically. So how can I fix it?
Thanks! – Christine
If I understand you correctly you have two GUIs where changes in one (the MenuTab) will update the other (OrderConfirmedTab) in real time?
If so, you will need to increase the coupling between the two objects. If MenuTab has a reference back to OrderConfirmedTab then it can call methods to update the value as it changes.
For example, pass OrderConfirmedTab into MenuTabs constructor
MenuTab mt = new MenuTab(this); // from within OrderConfirmTabs costructor
Then when MenuTab has an actionPerformed event it can call back to OrderConfirmTab
orderConfirmTab.setTotalSelected(totalSelected); // you have to create this method in OrderConfirmTab
I hope this helps a little
You can use PropertyChangeListener and PropertyChangeSupport mechanisms to dispatch an event when the value is updated and to be notified when the variable has changed. Your JLabel is not going to update on its own; even if you were to use an object other than a primitive (note that primitives are merely values, while objects are actually implicit pointers); you will need to update your JLabel when the variable changes, since the JLabel simply stores a string, not a reference to the variables from which the string was constructed.
This is the concept of model-view-controller; your variable should be in some sort of class or classes that represent the model (the information) and which allow changes to be observed via property change events. Your view classes should simply provide display logic and no business or application-specific logic. It is the controller in which your application logic should reside; your controller should register for events on the model, and it should update the view whenever the model has changed, and it should likewise update the model when the view dispatches events that should result in the model being changed.