Sto progettando un'API per un controllo JavaFX da inviare alla libreria ControlsFX. Questo controllo è chiamato ViewManager
, e il suo scopo è quello di contenere una raccolta di View
istanze, tutte alimentate dallo stesso insieme di dati (elementi da visualizzare, stato di selezione, impostazioni di visualizzazione, ecc ...)
Il mio tentativo originale era di avere View
una classe astratta che definisca tutte le cose di cui ha bisogno - cose come i modelli di selezione e messa a fuoco, una collezione di elementi da visualizzare e alcune altre cose (alcune delle quali Non ho ancora concettualizzato).
Le viste possono esistere isolate da ViewManager
, ma voglio progettare ViewManager
per avere molte delle stesse funzioni di View
in modo che tutte le viste nel gestore possano alimentare i dati del gestore e rimanere sincronizzati l'uno con l'altro. Il problema che sto incontrando, tuttavia, è che un'implementazione corretta di View
(come GridView
o ListView
) ha caratteristiche aggiuntive che non hanno senso per un ViewManager
(come un riferimento alla sua ViewManager
e la sua fabbrica di celle). Per questo motivo, non è corretto rendere ViewManager
estendere View
.
Un altro pensiero che ho avuto è stato quello di astrarre View
in un'interfaccia e poi cambiare la mia classe in AbstractView
. Questo potrebbe aver alleviato il problema precedente, ma poi ho scoperto che avevo bisogno di questa interfaccia per definire le firme per le cose che sono specifiche dell'implementazione - cose come itemsProperty()
usate dalla mia classe del modello di selezione per collegare un listener.
L'unica soluzione che posso ricavare è che ViewManager
sovrascriva le funzioni che non usa, e queste lanciano qualcosa come UnsupportedOperationException
. C'è anche il fatto che un ViewManager
non deve contenere un altro ViewManager
, quindi dovrei anche verificare quel caso speciale quando aggiungo una vista al gestore e rispondo di conseguenza.
Ciò che mi sembrava un'idea abbastanza semplice ora si sta trasformando in un pasticcio di cattivo design. C'è un modo migliore per aggirare questo?
Per riferimento, ecco le mie implementazioni che ho finora:
Visualizza
/**
* Abstract base class for all Views that will be usable by the {@link ViewManager}
*
* @param <T> The base type of the object that this view will display information about
* @param <C> The base type of the {@link IndexedCell} that this view uses to display individual records
*/
public abstract class View<T, C extends AbstractViewCell<T>> extends ControlsFXControl {
/***************************************************************************
*
* Properties
*
**************************************************************************/
// --- Cell Factory
private ObjectProperty<Callback<View<T, C>, C>> cellFactory = new SimpleObjectProperty<>(this, "cellFactory"); //$NON-NLS-1$
public ObjectProperty<Callback<View<T, C>, C>> cellFactoryProperty() { return cellFactory; }
public Callback<View<T, C>, C> getCellFactory() { return cellFactory.get(); }
public void setCellFactory(Callback<View<T, C>, C> cellFactory) { this.cellFactory.set(cellFactory); }
// --- Checkbox
private final BooleanProperty showCheckbox = new SimpleBooleanProperty(this, "showCheckbox", Boolean.FALSE); //$NON-NLS-1$
public BooleanProperty showCheckboxProperty() { return showCheckbox; }
public boolean getShowCheckbox() { return showCheckbox.get(); }
public void setShowCheckbox(boolean showCheckbox) { this.showCheckbox.set(showCheckbox); }
// --- Items
private ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); //$NON-NLS-1$
public ListProperty<T> itemsProperty() { return items; }
public ObservableList<T> getItems() { return items.get(); }
public void setItems(ObservableList<T> items) { this.items.set(items); }
// --- Focus Model
private ObjectProperty<FocusModel<T>> focusModel = new SimpleObjectProperty<>(this, "focusModel"); //$NON-NLS-1$
public ObjectProperty<FocusModel<T>> focusModelProperty() { return focusModel; }
public FocusModel<T> getFocusModel() { return focusModel.get(); }
public void setFocusModel(FocusModel<T> focusModel) { this.focusModel.set(focusModel); }
// --- Selection Model
private ObjectProperty<MultipleSelectionModel<T>> selectionModel = new SimpleObjectProperty<>(this, "selectionModel"); //$NON-NLS-1$
public ObjectProperty<MultipleSelectionModel<T>> selectionModelProperty() { return selectionModel; }
public MultipleSelectionModel<T> getSelectionModel() { return selectionModel.get(); }
public void setSelectionModel(MultipleSelectionModel<T> selectionModel) { this.selectionModel.set(selectionModel); }
// --- View Manager
private ObjectProperty<ViewManager<T>> viewManager = new SimpleObjectProperty<>(this, "viewManager");
public ObjectProperty<ViewManager<T>> viewManagerProperty() { return viewManager; }
public ViewManager<T> getViewManager() { return viewManager.get(); }
public void setViewManager(ViewManager<T> viewManager) { this.viewManager.set(viewManager); }
/***************************************************************************
*
* Listeners
*
**************************************************************************/
/**
* Here we make sure that if, for whatever reason, the ViewManager changes, we un-bind and re-bind
* the appropriate properties.
*/
ChangeListener<ViewManager<T>> VIEW_MANAGER_PROPERTY_LISTENER = (o, oldManager, currentManager) -> {
if (oldManager != null) {
focusModel.unbindBidirectional(oldManager.focusModelProperty());
selectionModel.unbindBidirectional(oldManager.selectionModelProperty());
items.unbindBidirectional(oldManager.itemsProperty());
showCheckbox.unbindBidirectional(oldManager.showCheckboxProperty());
}
if (currentManager != null) {
focusModel.bindBidirectional(currentManager.focusModelProperty());
selectionModel.bindBidirectional(currentManager.selectionModelProperty());
items.bindBidirectional(currentManager.itemsProperty());
showCheckbox.bindBidirectional(currentManager.showCheckboxProperty());
}
};
WeakChangeListener<ViewManager<T>> WEAK_VIEW_MANAGER_PROPERTY_LISTENER = new WeakChangeListener<>(VIEW_MANAGER_PROPERTY_LISTENER);
/***************************************************************************
*
* Constructors
*
**************************************************************************/
public View() {
viewManager.addListener(WEAK_VIEW_MANAGER_PROPERTY_LISTENER);
}
}
ViewManager
public class ViewManager<T> extends ControlsFXControl {
/***************************************************************************
*
* Listeners
*
**************************************************************************/
/***************************************************************************
*
* Properties
*
**************************************************************************/
// --- Checkbox
private final BooleanProperty showCheckbox = new SimpleBooleanProperty(this, "showCheckbox", Boolean.FALSE); //$NON-NLS-1$
public BooleanProperty showCheckboxProperty() { return showCheckbox; }
public boolean getShowCheckbox() { return showCheckbox.get(); }
public void setShowCheckbox(boolean showCheckbox) { this.showCheckbox.set(showCheckbox); }
// --- Items
private final ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList()); //$NON-NLS-1$
public ListProperty<T> itemsProperty() { return items; }
public ObservableList<T> getItems() { return items.get(); }
// --- Focus Model
private ObjectProperty<FocusModel<T>> focusModel = new SimpleObjectProperty<>(this, "focusModel"); //$NON-NLS-1$
public ObjectProperty<FocusModel<T>> focusModelProperty() { return focusModel; }
public FocusModel<T> getFocusModel() { return focusModel.get(); }
public void setFocusModel(FocusModel<T> focusModel) { this.focusModel.set(focusModel); }
// --- Selection Model
private ObjectProperty<MultipleSelectionModel<T>> selectionModel = new SimpleObjectProperty<>(this, "selectionModel"); //$NON-NLS-1$
public ObjectProperty<MultipleSelectionModel<T>> selectionModelProperty() { return selectionModel; }
public MultipleSelectionModel<T> getSelectionModel() { return selectionModel.get(); }
public void setSelectionModel(MultipleSelectionModel<T> selectionModel) { this.selectionModel.set(selectionModel); }
// -- Views
private final ListProperty<View<T, ? extends IndexedCell<T>>> views
= new SimpleListProperty<View<T, ? extends IndexedCell<T>>>(FXCollections.observableArrayList()) {
@Override
protected void invalidated() {
get().forEach(view -> view.setViewManager(ViewManager.this));
}
@Override
public Object getBean() {
return ViewManager.this;
}
@Override
public String getName() {
return "views";
}
};
public ListProperty<View<T, ? extends IndexedCell<T>>> viewsProperty() { return views; }
public ObservableList<View<T, ? extends IndexedCell<T>>> getViews() { return views.get(); }
public void setViews(ObservableList<View<T, ? extends IndexedCell<T>>> views) { this.views.set(views); }
/***************************************************************************
*
* Private Implementation
*
**************************************************************************/
private void updateAllSelectionModels(ListChangeListener.Change<? extends Integer> change) {
while (change.next()) {
if (change.wasAdded()) {
for (Integer added : change.getAddedSubList()) {
views.forEach(view -> view.getSelectionModel().select(added));
}
}
if (change.wasRemoved()) {
for (Integer removed : change.getRemoved()) {
views.forEach(view -> view.getSelectionModel().clearSelection(removed));
}
}
}
}
}
Ci sono molti altri pezzi coinvolti, ma non sono rilevanti per il problema discusso qui.