Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX: Right click on TableColumn disables resizing

Reproduced in OpenJFX 11.0.2 & 12.0.1 SDK (Windows 10, x64), not reproducible in JavaFX 8

Right-click on a table-column, then try to resize the column. No resize cursor is shown and column can't be resized until you manually click on the column again.

Any ideas for a workaround? I need to usecontextMenu for TableColumns, so potential workarounds that make the header ignore right mouse click aren't possible.

enter image description here

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class Foo extends Application {


    @Override
    public void start(Stage stage) throws Exception {
        TableView<Object> testView = new TableView<>();
        testView.getColumns().addAll(new TableColumn<Object, Object>("C1"), new TableColumn<Object, Object>("C2"), new TableColumn<Object, Object>("C3"));

        stage.setScene(new Scene(testView));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
like image 551
Lukas Rotter Avatar asked May 12 '19 17:05

Lukas Rotter


Video Answer


2 Answers

Ok I found the following (very, very dirty) workaround. I never tried this before because I assumend it would prevent the context menu from showing (as I noted in my original question), but apprently simply consuming the mouse event of every TableColumnHeader works and the context menu is still shown correctly (also works with TableColumns without context menus).

Not sure if anything internal could go wrong with this, but as the right click doesn't seem to be doing anything useful by default, I hope not.

Of course lookupAll needs to be called after it has been rendered.

Note 1: If you have TableMenuButtonVisible set to true, you need to do this every time a column is set to visible.

Note 2: Its getting dirtier and dirtier. Simply calling this again after a column has been set to visible (see note 1) doesn't always suffice (also not with a Platform.runLater call). I assume that's because the column header hasn't been rendered at that point. You either

  • need to wait until the Set<Node> is fully filled, i.e. the size of it must be amountOfVisibleColumns + 1. If its equal to the amount of visible columns, it won't work for the newly shown column.
  • or call layout() on the TableView before lookupAll
  • or if you have a class that extends TableView, override layoutChildren and execute the lookup if the amount of visible columns has changed

Note 3: You need to keep track of the old onMousePressed and execute it if the button isn't SECONDARY, otherwise the reordering of columns won't work.

Please let me know if you can think of any cleaner way.

enter image description here

import java.util.Set;

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.skin.TableColumnHeader;
import javafx.scene.input.MouseButton;
import javafx.stage.Stage;

public class Foo extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        TableView<Object> testView = new TableView<>();
        testView.getColumns().addAll(createColumn("C1"), createColumn("C2"), createColumn("C3"));

        stage.setOnShown(ev -> {
            Set<Node> headers = testView.lookupAll("TableColumnHeader");
            for (Node header : headers) {
                if (header != null) {
                    ((TableColumnHeader) header).setOnMousePressed(e -> {
                        if (e.getButton() == MouseButton.SECONDARY) {
                            e.consume();
                        }
                    });
                }
            }
        });

        stage.setScene(new Scene(testView));
        stage.show();
    }

    private TableColumn<Object, Object> createColumn(String text) {
        MenuItem item = new MenuItem("Context");
        item.setOnAction(e -> {
            System.out.println("Action");
        });

        ContextMenu contextMenu = new ContextMenu();
        contextMenu.getItems().add(item);

        TableColumn<Object, Object> column = new TableColumn<>(text);
        column.setContextMenu(contextMenu);

        return column;
    }

    public static void main(String[] args) {
        launch(args);
    }
}
like image 191
Lukas Rotter Avatar answered Sep 30 '22 12:09

Lukas Rotter


EDIT: Found the described bug in the Java bug tracker and filed a PR with the fix:
https://github.com/openjdk/jfx/pull/483
EDIT 2: My PR was accepted and merged back. The bug is fixed now, you can test it by using 17-ea+11. :-)

I have the same problem. This bug is caused by the mousePressedHandler added in TableColumnHeader. This class has even more problems, for example if I close a PopupControl with setConsumeAutoHidingEvents(true) by a click on a column, the sorting will be triggered. Those methods needs to be changed, maybe the addEventHandler methods should be used instead of the convenience setOn... methods.

I fixed it by consuming the event when I'm about to show my PopupControl:

public class MyTableColumnHeader extends TableColumnHeader {

    public MyTableColumnHeader(TableColumnBase tc) {
        super(tc);
        addEventHandler(MouseEvent.MOUSE_PRESSED, this::onMousePressed);
    }

    private void onMousePressed(MouseEvent mouseEvent) {
        if (mouseEvent.getButton() == MouseButton.SECONDARY) {
            showPopup();
            // Consume here, so the column won't get 'stuck'.
            mouseEvent.consume();
        }
    }

    private void showPopup() {
        ...
    }
}

Eventually, someone should open at least a bug. I may will also have a look in the not too distant future.

like image 20
Maran23 Avatar answered Sep 30 '22 13:09

Maran23