I try to create TableColumn with ChoiceBoxTableCell. Choices in this ChoiceBox are dynamically generated (and changed over time) based on item associate with current row. I tried different approaches, but nothing seems to work.
I would like to have something like this:
private DataProvider dataProvider;
private TableColumn<Phone, String> testColumn;
public void initialize() {
testColumn.setCellFactory(param, phone -> new ChoiceBoxTableCell<Phone, String>(dataProvicer.get(phone)));
}
Where:
public interface DataProvider {
ObservableList<String> get(Phone phone);
}
This is my ideal code I would like to have, but as you know setCallFactory takes Callback with TableColumn<S,T> as function parameter and there is no way to access it within CellFactory. I could probably do some dirty and ugly hacks to get why I want, but I would love to have some nice solution.
A reminder of the basic mechanism: a cellFactory is used to create any cell of for the given column. The calling code (that is the VirtualFlow deep inside the implementation of table's skin) isn't interested in or don't event know which row the cell is created for. Also, it will be re-used - that is setting a new item - quite often. In all, the moment of creating the cell is not the right time to configure the cell with row-related data. This has to be done later, once the row is known: the most obvious candidate is updateItem(T, boolean).
Now back to the concrete ChoiceBoxTableCell: unfortunately, its implementation is too dumb and simply doesn't support dynamic updates of its choice items. So you need a custom extension which does support the dynamics. On the bright side: ChoiceBoxTableCell exposes its items, thus allowing to change its contents as needed.
As noted in the code comment, it turned out that the obvious hook didn't work out nicely. So had to move the config into the startEdit method.
Some code:
public interface ChoiceItemProvider<S, T> {
ObservableList<T> getItems(S source);
}
public class DynamicChoiceBoxTableCell<S, T> extends ChoiceBoxTableCell<S, T> {
private ChoiceItemProvider<S, T> provider;
public DynamicChoiceBoxTableCell(ChoiceItemProvider<S, T> provider) {
super();
this.provider = provider;
}
/**
* Not so obvious hook: overridden to update the items of the
* choiceBox.
*/
#Override
public void startEdit() {
super.startEdit();
updateItems();
}
/**
* Obvious hook: override to update the items of the choiceBox.
* Not fully working - for some reason, the current item isn't
* selected after starting the edit.
*/
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
// updateItems();
}
/**
* Dynamically updates the items to current rowItem.
*/
#SuppressWarnings("unchecked")
protected void updateItems() {
TableRow<S> tableRow = getTableRow();
S rowItem = tableRow != null ? tableRow.getItem() : null;
if (provider == null || rowItem == null) return;
if (provider != null) {
getItems().setAll(provider.getItems(rowItem));
}
}
}
Addendum re:
no ideal, because items won't be updated when it is already expanded
If you need that, you can bind the choiceBox' items to the items returned by the provider, that is instead of calling setAll(provider.getItems()) do:
Bindings.bindContent(getItems(), provider.getItems());
testColumn.setCellFactory(ChoiceBoxTableCell.forTableCoulmn(<dynamic list>));
This should work.
Related
I am building a preference page in Eclipse by extending the FieldEditorPreferencePage class. this page contains 2 fields : 1 BooleanFieldEditor (checkbox) and 1 FileFieldEditor. I would like to disable/enable the file field following the checkbox value.
I went up to something like this (some obvious code is not displayed):
public class PreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
public static final String PREF_KEY_1 = "checkBoxPref";
public static final String PREF_KEY_2 = "filePref";
private FileFieldEditor pathField;
private BooleanFieldEditor yesOrNoField;
private Composite pathFieldParent;
#Override
protected void createFieldEditors() {
this.yesOrNoField = new BooleanFieldEditor(PREF_KEY_1, "Check this box!", getFieldEditorParent());
this.pathFieldParent = getFieldEditorParent();
this.pathField = new FileFieldEditor(PREF_KEY_2, "Path:", this.pathFieldParent);
addField(this.yesOrNoField);
addField(this.pathField);
boolean isChecked = getPreferenceStore().getBoolean(PREF_KEY_1);
updatePathFieldEnablement(! isChecked);
}
/**
* Updates the fields according to entered values
*/
private void updatePathFieldEnablement(boolean enabled) {
this.pathField.setEnabled(enabled, this.pathFieldParent);
}
#SuppressWarnings("boxing")
#Override
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(FieldEditor.VALUE) && event.getSource() == this.yesOrNoField) {
updatePathFieldEnablement(! (boolean) event.getNewValue());
}
super.propertyChange(event);
}
}
My question is about this second parameter in FieldEditor#setEnabled. This parameter is the parent composite of the FieldEditor's controls ("Used to create the controls if required" says the javadoc) . At first, I set the value with the return of getFieldEditorParent but then I got an exception "Different parent". So I ended storing it (cf. this.pathFieldParent) and give it back to setEnabled and it works (or it seems to work).
But I am not sure I am doing well, especially because I had to create a member in my class that means nothing to it (and I would have to create many of them if I had many fields to enable/disable).
Do you think I am doing well or is there a better way to provide this parent ? And could you explain to me why *setEnabled" needs it ?
Thanks.
You are using the default FLAT layout for the preference page. When this layout is used each call to getFieldEditorParent generates a new Composite so you have to make just one call and remember the correct parent. Using the GRID layout getFieldEditorParent always returns the same parent. This is the actual code:
protected Composite getFieldEditorParent() {
if (style == FLAT) {
// Create a new parent for each field editor
Composite parent = new Composite(fieldEditorParent, SWT.NULL);
parent.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
return parent;
}
// Just return the parent
return fieldEditorParent;
}
setEnabled does sometimes create a new Label control so it needs to know the correct parent Composite.
I have an observable list of type Playlist.
I have a dropdown menu (connected to a MenuButton) with a MenuItem for each item in the list (plus a few default items I hard code that don't change).
For a TableView, I am able to link the data to an ObservableList, and the table's rows automatically update based on the contents of the List.
Is there a way to do the same thing for a MenuButton and its list of MenuItems?
I am not sure if there is built in support but either way, you can write a wrapper for any element you want based off an ObservableList by writing a listener. For example,
public class BoundMenuButton extends MenuButton {
ObservableList<MenuItem> items;
public BoundMenuButton(items) {
super(); // Not sure if needed
this.items = items;
// Listen for changes
items.addListener((ListChangeListener.Change<? extends MenuItem> change) -> {
updateItems();
});
}
public void updateItems() {
Platform.runLater( () -> {
// Do updates
});
}
}
This is just the very basics. You can extend this to have the same methods as a TableView fairly easily. (getItems(), setItems(), etc.).
P.S - This code is untested as I don't have access to a compiler currently.
I am trying to have a custom ListView made of custom Cell based on a list of custom objects.
The custom object is class name called Message which contains a few fields for the message content, recipient, timestamp and status (read, sent etc.).
After looking at this question : Customize ListView in JavaFX with FXML I have successfully :
created a ListView with custom cells where the cell design is defined in a FXML file ;
associated a controller so that each cell data can be filled with the current item of the collection ;
However, I failed to link both : I cannot seem to find a way so that the current item of the ListView is sent the Cell Controller.
Here is my code for the cell factory and the ListView filling of items:
final ObservableList observableList = FXCollections.observableArrayList();
observableList.setAll(myMessages); //assume myMessage is a ArrayList<Message>
conversation.setItems(observableList); //the listview
conversation.setCellFactory(new Callback<ListView<Message>, ListCell<Message>>() {
#Override
public ConversationCell<Message> call(ListView<Message> listView) {
return new ConversationCell();
}
});
And now, the ConversationCell class :
public final class ConversationCell<Message> extends ListCell<Message> {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
ConversationCellController ccc = new ConversationCellController(null);
setGraphic(ccc.getView());
}
}
I cannot show the ConversationCellController but all I can say, this is where (in its constructor) I load the FXML file that designs the cell and then I can fill the values with the given Message item.
The getView() method returns the root pane that contains the now-filled-and-designed cell.
As I previously say, the designing work, but I cannot seem to link the ListView items with the CellFactory because in method
protected void updateItem(Message item, boolean empty)
empty is set to true and item is indeed null.
What can I do to make this work ?
All custom cell implementations that override updateItem(...) need to deal with the case where the cell is empty in that method. So you could do a naïve fix of this with
public final class ConversationCell<Message> extends ListCell<Message> {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
// did you mean to pass null here, or item??
ConversationCellController ccc = new ConversationCellController(null);
setGraphic(ccc.getView());
}
}
}
However, this is not a good solution from the point of view of performance. You are loading the FXML every time updateItem(...) is called with a non-empty cell, and that's a pretty expensive operation (potentially involving file i/o, unzipping the FXML file from a jar file, parsing the file, lots of reflection, creating new UI elements, etc). You don't want to be asking the FX Application Thread to be doing all that work every time the user scrolls the list view by a few pixels. Instead, your cell should cache the node and should update it in the updateItem method:
public final class ConversationCell<Message> extends ListCell<Message> {
private final ConversationCellController ccc = new ConversationCellController(null);
private final Node view = ccc.getView();
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
ccc.setItem(item);
setGraphic(view);
}
}
}
You should define a setItem(...) method in the ConversationCellController that updates the view (sets text on labels, etc etc) accordingly.
I know that by using JTable the column is sorted when we click on the column heading, but what I want is that, when I right-click on the column name a function name 'sort' should be displayed. Any suggestion in doing it?
Start by adding a MouseListener to the table. See How to write mouse listeners
You will need to translate the click point to a column, see JTable#columnAtPoint.
You will then need to update the SortKey for the table. Check out Sorting and Filtering for an example
If I understand you correctly, you want to sort by some explicit action (triggered f.i. in a popup) instead of by the normal left-click.
If so, the tricky part is to force the ui-delegate to do nothing. There are two options:
hook into the default mouse listener installed by the ui delegate, as described in a recent QA
let the ui do its stuff, but fool it by a sorter implementation that doesn't follow the rules (beware: that's as dirty as the first approach!)
The mis-behaving sorter:
public class MyTableRowSorter extends TableRowSorter {
public MyTableRowSorter(TableModel model) {
super(model);
}
/**
* Implemented to do nothing to fool tableHeader internals.
*/
#Override
public void toggleSortOrder(int column) {
}
/**
* The method that really toggles, called from custom code.
*
* #param column
*/
public void realToggleSortOrder(int column) {
super.toggleSortOrder(column);
}
}
// usage
final JTable table = new JXTable(new AncientSwingTeam());
table.setRowSorter(new MyTableRowSorter(table.getModel()));
Action toggle = new AbstractAction("toggleSort") {
#Override
public void actionPerformed(ActionEvent e) {
JXTableHeader header = SwingXUtilities.getAncestor(
JXTableHeader.class, (Component) e.getSource());
Point trigger = header.getPopupTriggerLocation();
int column = trigger != null ? header.columnAtPoint(trigger) : -1;
if (column < 0) return;
int modelColumn = header.getTable().convertColumnIndexToModel(column);
((MyTableRowSorter) header.getTable().getRowSorter())
.realToggleSortOrder(modelColumn);
}
};
JPopupMenu menu = new JPopupMenu();
menu.add(toggle);
table.getTableHeader().setComponentPopupMenu(menu);
Yeah, couldn't resist throwing in some SwingX api, lazy me :-) With plain Swing, you'll have to write some lines more but the basics are the same: install the tricksy sorter and use its custom toggle sort to really sort whereever needed, f.i. in a mouseListener.
Say you have the following java bean:
public class MyBean
{
private List<String> names = new ArrayList<String>();
public void addName(String name)
{
names.add(name);
fireNamesPropertyChange(name);
}
}
How would you normally implement a property change event for a collection? Do you try and use the index property which seems to be more for arrays than collections?
(NOTE: I updated this post after realizing a few mistakes of my own so this isn't the original but a more refined one instead)
For this purpose I'd do two new interfaces, ListListener and Listenable and then I would create a new class like ListenableArrayList which would wrap every List method with a call to one (or more) relevant methods defined in ListListener. In code it'd be something like this:
public class ListenableArrayList<T> extends ArrayList<T>
implements Listenable<T> {
private ArrayList<T> internalList;
private ListListener<T> listener;
/* .. */
public void add(T item) {
listener.beforeAdd(T item);
internalList.add(item);
listener.afterAdd(T item);
}
/* .. */
public void setListener(ListListener<T> listener) {
this.listener = listener;
}
}
public interface ListListener<T> {
/* .. */
void beforeAdd(T item);
void afterAdd(T item);
/* .. */
}
public interface Listenable<T> {
/* .. */
void setListener(ListListener<T> listener);
/* .. */
}
The reason I'd do it this way would be to allow for creating truly ad-hoc listeners on the fly instead of tying the ListenableArrayList to some specific implementation. For example with this the following would be possible:
Listenable<String> list = new ListenableArrayList<String>();
list.setListener(new ListListener<String>() {
#Override
public void beforeAdd(String item) {
System.out.println("About to add element "+item+"...");
}
#Override
public void afterAdd(String item) {
System.out.println("...element "+item+" has been added.");
}
});
A bit cluttered, maybe but on the other hand this would allow for easy extension to Collections, Sets and whatnot rather easily.
Take a look at Glazed Lists library, which has support for observable collections.
If I were to do it myself, I would likely create custom Listener interface with elementsAdded, elementsRemoved methods, or similar :-) (also depending on my needs)
You can use an Observable Collection: https://commons.apache.org/dormant/events/apidocs/org/apache/commons/events/observable/ObservableCollection.html
Normally I'd do the following:
public class MyBean {
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private List<String> names = new ArrayList<String>();
public void addName(String name) {
names.add(name);
pcs.firePropertyChange("names", null, Collections.unmodifiableList(names));
}
public void addPropertyChangeListener(PropertyChangeListener l) {
pcs.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
pcs.removePropertyChangeListener(l);
}
}
PropertyChangeSupport manages the listeners and fires the events on your behalf.
By passing null as the "old value" it forces the event to be fired. (It's likely that listeners won't really care about the old value anyway)
JDK 7+ solution:
import javafx.collections.*;
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("s1");
list.add("s2");
ObservableList<String> observableList = FXCollections.observableList(list);
observableList.addListener(new ListChangeListener<String>() {
#Override
public void onChanged(Change<? extends String> change) {
while(change.next()){
System.out.println("added: " + change.getAddedSubList());
}
}
});
observableList.add("s3");
}
}
For a swing GUI event I'd normally just use an EventListenerList to do the work for me.
EDIT: on the rephrase of the questions: how do you treat collections, I'd usually use an event similar to the collections type, so for example a TreeModel event usually takes a TreePath argument, or for something in a map I'd indicate the key.
However for simple JavaBeans the most common is assume a list/array and just use the index.
Methinks you will need fireNamesPropertyAdd, fireNamesProperyDelete. A list level notification will IMHO not work, even if it was an array and an index was added as it can't handle deletes. If the element at some index can be changed, you will also need fireNamesProperyChange. It might be useful to have index as parameter in addition to the string value.
Are you perhaps looking for java.beans.PropertyChangeSupport?
In my opinion, you should avoid PropertyChangeEvent. IndexedPropertyChangeEvent is worse, and very infrequently used by Swing anyway. It's better to narrow the focus of your types, and fire a javax.swing.event.ChangeEvent or similar (even just call a Runnable).
For certain types (like lists!), Glazed Lists (or equivalent) mentioned in another post by Peter Štibraný seem like a good way to go.