0

I want to have a TabPane with multiple tabs, with a context menu different for every tab.

Setting the ContextMenu on the TabPane obviously results in the same ContextMenu used regardless of selected tab. I tried setting different ContextMenus on different Tabs, but this has two unwanted effects:

  1. The context menu is only opened when right-clicking on the tab header (whereas setting it on the TabPane allows right-clicking anywhere inside the tab). I want the user to be able to access the Context Menu from anywhere inside the tab, not just the header.

  2. Right-clicking on one tab header while another is selected still opens the context menu of the tab clicked on - I want the context menu to depend on the tab currently selected.

The only way I could think of how to do it is to catch onContextMenuRequested, see which tab is currently selected, and set various MenuItems visible/invisible, but that seems pretty silly. Is there a better way of doing this?

Edit: Calrification - the tab contents are Panes (VBox, GridPane, etc.) so setting the ContextMenu directly on the content is sadly impossible.

Itai
  • 6,641
  • 6
  • 27
  • 51
  • Why not set the context menu on the tab content? – James_D Nov 30 '15 at 22:15
  • @James_D Ah, I should have mentioned - the tab content is a pane (usually VBox or GridPane), and JavaFX doesn't allow ContextMenu on Panes (only on Controls) – Itai Nov 30 '15 at 22:16
  • You can use a context menu with any node. Just set a `comtextMenuRequested` handler and call `show()` on the context menu – James_D Nov 30 '15 at 22:43
  • Ok, that looks like a workaround that can work (too bad it's not supported better by JavaFX). The only problem with it is that then I need to reuse this: http://stackoverflow.com/questions/31732583/pass-context-menu-shortcuts-up-from-editing-control in every content, not only on the TabPane. – Itai Nov 30 '15 at 22:49
  • 1
    The other option of course would be to use a single context menu on the `TabPane`, and update the contents of the context menu with a listener on the selected tab. – James_D Nov 30 '15 at 22:50
  • I think I'll do just this! Good idea, thanks. – Itai Nov 30 '15 at 22:52
  • Using the `onContextMenuRequested` handler seems easier to me. It's basically a one-liner to set it up. (One long line, but one line nevertheless). See posted answer – James_D Nov 30 '15 at 23:25

1 Answers1

2

I can see two solutions to this. One is to set a single context menu on the tab pane. Register a listener with the selected tab, and repopulate the context menu when the selection changes.

The other solution is just to set the context menu on the content of the tabs. Note that you can set a context menu on any node, by registering a handler for the contextMenuRequested event, and show the context menu. You can set the same context menu on the tab.

This example demonstrates both techniques:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class TabPanesWithContextMenu extends Application {

    @Override
    public void start(Stage primaryStage) {
        TabPane tabPane1 = new TabPane();
        ContextMenu contextMenu = new ContextMenu();
        tabPane1.setContextMenu(contextMenu);

        List<MenuItem> tab1Items = new ArrayList<>();
        tab1Items.add(new MenuItem("Choice 1"));
        tab1Items.add(new MenuItem("Choice 2"));

        List<MenuItem> tab2Items = new ArrayList<>();
        tab2Items.add(new MenuItem("Choice 3"));
        tab2Items.add(new MenuItem("Choice 4"));

        Tab tab1 = new Tab("Tab 1");
        tab1.setContent(new Pane());
        Tab tab2 = new Tab("Tab 2");
        tab2.setContent(new Pane());
        tabPane1.getTabs().addAll(tab1, tab2);

        Map<Tab, List<MenuItem>> itemsByTab = new HashMap<>();
        itemsByTab.put(tab1, tab1Items);
        itemsByTab.put(tab2, tab2Items);

        tabPane1.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> 
           contextMenu.getItems().setAll(itemsByTab.get(newTab)) );

        contextMenu.getItems().addAll(tab1Items);

        TabPane tabPane2 = new TabPane();

        Tab tab3 =  createTabWithContextMenu("Tab 3", new MenuItem("Choice 5"), new MenuItem("Choice 6"));
        Tab tab4 =  createTabWithContextMenu("Tab 4", new MenuItem("Choice 7"), new MenuItem("Choice 8"));

        tabPane2.getTabs().addAll(tab3, tab4);

        HBox root = new HBox(10, tabPane1, tabPane2);

        primaryStage.setScene(new Scene(root, 600, 600));
        primaryStage.show();
    }

    private Tab createTabWithContextMenu(String title, MenuItem... items) {
        Tab tab = new Tab(title);
        ContextMenu contextMenu = new ContextMenu(items);
        tab.setContextMenu(contextMenu);

        Pane content = new Pane();
        content.setOnContextMenuRequested(e -> 
            contextMenu.show(content, e.getScreenX(), e.getScreenY()));
        tab.setContent(content);

        return tab ;
    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322