Search code examples
javainheritancejavafxsuperclass

How to ensure code is executed in a specific order on inheritance


I have this super class extending TableView, that every table I want to make is supposed to inherit from.

This line of code needs to be executed for every class that inherits from the superclass and needs a ContextMenu:

// Sets the context menu for each specific row. This way no
// context menu will appear if we click on a row that is
// empty.
setRowFactory(r -> {
    TableRow<ObservableList<String>> row = new TableRow<ObservableList<String>>() {
        public void updateItem(ObservableList<String> item, boolean empty) {
            super.updateItem(item, empty);
            if (isEmpty()) {
                setContextMenu(null);
            } else {
                setContextMenu(contextMenu);
            }
        }
    };
});

The problem is that the ContextMenu needs to be instantiated before this code can be executed, hence I can't put in the constructor of the super class.

How do I ensure that every class inheriting from the super class does the following steps in this order if the table needs a ContextMenu (not always the case):

  1. Instantiate the ContextMenu with MenuItem instances (I guess the MenuItem instances could be instantiated and added later on, theoretically).
  2. Invoke the "setRowFactory" code above with the context menu instantiated in step 1.

I've had a few iterations on solving this issue, and still failed to come up with a satisfying solution. Maybe I'm missing something obvious.

Edit: Would be perfect if you could somehow disable the user from using the originalsetContextMenu method of TableView, to ensure that `ContextMenu is being added in the correct way. But I guess this isn't possible.


Solution

  • It looks like you are simply trying to suppress showing the context menu on empty rows in the table. The simpler way to do this is

    public class MyTableView<T> extends TableView<T> {
    
        public MyTableView() {
            setRowFactory(tv -> {
                TableRow<T> row = new TableRow<>();
                row.setOnContextMenuRequested(e -> {
                    if (row.isEmpty()) {
                        e.consume();
                    }
                }
                return row ;
            });
        }
    
        // ...
    }
    

    To answer the question you asked (ensuring you only (and always) call setRowFactory after a call to setContextMenu):

    You can either do

    public class MyTableView<T> extends TableView<T> {
    
        private final ContextMenu contextMenu ;
    
        public MyTableView(ContextMenu contextMenu) {
            this.contextMenu = contextMenu ;
    
            setRowFactory(r -> {
                TableRow<ObservableList<String>> row = new TableRow<ObservableList<String>>() {
                    public void updateItem(ObservableList<String> item, boolean empty) {
                        super.updateItem(item, empty);
                        if (isEmpty()) {
                            setContextMenu(null);
                        } else {
                            setContextMenu(contextMenu);
                        }
                    }
                };
            });
    
        }
    
        // ...
    }
    

    or

    public class MyTableView<T> extends TableView<T> {
    
        private ContextMenu rowContextMenu ;
    
        public ContextMenu getRowContextMenu() {
            return rowContextMenu ;
        }
    
        public void setRowContextMenu(ContextMenu contextMenu) {
            this.rowContextMenu = contextMenu ;
    
            setRowFactory(r -> new TableRow<ObservableList<String>>() {
                public void updateItem(ObservableList<String> item, boolean empty) {
                    super.updateItem(item, empty);
                    if (isEmpty()) {
                        setContextMenu(null);
                    } else {
                        setContextMenu(rowContextMenu);
                    }
                }
            });
        }
    
        // ...
    }
    

    or even

    public class MyTableView<T> extends TableView<T> {
    
        public MyTableView() {
            contextMenuProperty().addListener((obs, oldContextMenu, newContextMenu) -> {
                if (newContextMenu == null) {
                    setRowFactory(r -> new TableRow<>());
                } else 
                    setRowFactory(r -> new TableRow<ObservableList<String>>() {
                        public void updateItem(ObservableList<String> item, boolean empty) {
                            super.updateItem(item, empty);
                            if (isEmpty()) {
                                setContextMenu(null);
                            } else {
                                setContextMenu(newContextMenu);
                            }
                        }
                    });
                }
            });
        }
    
        // ...
    }