5

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);
    }
}
Lukas Rotter
  • 4,158
  • 1
  • 15
  • 35
  • 1
    I can not reproduce this using JavaFX 11.0.2 on Ubuntu 18.04 – Samuel Philipp May 12 '19 at 18:08
  • I’m surprised that program runs at all in Java 12, since JavaFX is no longer part of Java SE. A subclass of javafx.application.Application can no longer be a Java main class, because the native libraries required by JavaFX are not available at the time the JVM starts the main class. – VGR May 12 '19 at 18:27
  • @VGR That's not true so long as the JavaFX modules are on the modulepath—at least, I've never had a problem (even when omitting the `main(String[])` method). – Slaw May 12 '19 at 19:14
  • 1
    I _can_ reproduce the problem using JavaFX 12.0.1 on Windows 10. – Slaw May 12 '19 at 19:15
  • @Slaw You are correct. I should have clarified that my statement only holds true when using a classpath rather than a module path. – VGR May 12 '19 at 19:15

2 Answers2

3

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);
    }
}
Lukas Rotter
  • 4,158
  • 1
  • 15
  • 35
2

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.

Maran23
  • 226
  • 2
  • 9