1

I'm designing an API for a JavaFX control to be submitted to the ControlsFX library. This control is called the ViewManager, and its purpose is to hold a collection of View instances, all of which feed off of the same set of data (items to display, selection state, view settings, etc...)

My original attempt was to have View be an abstract class that defines all of the things it needs -- things such as selection and focus models, a collection of items to visualize, and a few other things (some of which I haven't conceptualized yet).

Views are able to exist in isolation from a ViewManager, but I want to design the ViewManager to have many of the same functions of a View so that all views in the manager can feed off the manager's data and stay in sync with eachother. The problem I'm running into, however, is that a proper implementation of View (such as GridView or ListView) has additional features that makes no sense to a ViewManager (such as a reference to its ViewManager and its cell factory). For this reason, it feels improper to make ViewManager extend View.

One other thought I had was to abstract View away into an interface, and then change my class to AbstractView. This may have alleviated the issue above, but then I discovered that I needed this interface to define signatures for things that are implementation specific -- things such as itemsProperty() used by my selection model class to attach a listener.

The only solution I can come up with is to have ViewManager override the functions that it does not use, and have these throw something like UnsupportedOperationException. There's also the fact that a ViewManager should not contain another ViewManager, so I would also have to check for that special case when adding a view to the manager, and respond accordingly.

What seemed to me like a fairly straightforward idea is now turning into a mess of bad design. Is there a better way I can get around this?

For reference, here are my implementations I have so far:

View

/**
 * 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));
                }
            }
        }
    }
}

There are many more pieces involved, but they're not relevant to the problem discussed here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.