2015-01-02 8 views
4

ПроблемаАдаптировать кнопку меню Tableview

setTableMenuButtonVisible из TableView обеспечивает механизм для изменения видимости столбца таблицы. Однако эта функциональность оставляет желать лучшего:

  • Меню должно оставаться открытым. У меня есть e. г. 15 столбцов таблицы, и это боль, чтобы щелкнуть меню открыто -> щелкните столбец -> нажмите меню открыто -> щелкните следующий столбец -> ... Боль в изменении видимости нескольких столбцов

  • Должен быть выбран все/отменить все функции

  • там должна быть способом расширить меню с настраиваемыми элементами

  • После сняты все столбцов нет никакого способа повторно колонок, потому что заголовок ушел и с ним меню таблицы

Другими словами: Текущая реализация меню таблицы бесполезна.

Вопрос

Кто-нибудь знает, как о том, чтобы заменить существующее меню Tableview с соответствующей одной? Я видел решение с поиском стиля «.show-hide-columns-button» и добавлением фильтра событий. Однако это было 2 года назад, возможно, все изменилось.

спасибо!

Это, как я хотел бы, чтобы он продемонстрировал через ContextMenu (я е щелчку правой кнопкой мыши на столе.).:

public class TableViewSample extends Application { 

    private final TableView table = new TableView(); 
    public static void main(String[] args) { 
     launch(args); 
    } 

    @Override 
    public void start(Stage stage) { 
     Scene scene = new Scene(new Group()); 
     stage.setTitle("Table View Sample"); 
     stage.setWidth(300); 
     stage.setHeight(500); 

     // create table columns 
     TableColumn firstNameCol = new TableColumn("First Name"); 
     TableColumn lastNameCol = new TableColumn("Last Name"); 
     TableColumn emailCol = new TableColumn("Email"); 

     table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 

     // add context menu 
     CustomMenuItem cmi; 
     ContextMenu cm = new ContextMenu(); 

     // select all item 
     Label selectAll = new Label("Select all"); 
     selectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 

      @Override 
      public void handle(MouseEvent event) { 
       for(Object obj: table.getColumns()) { 
        ((TableColumn) obj).setVisible(true); 
       }   } 

     }); 

     cmi = new CustomMenuItem(selectAll); 
     cmi.setHideOnClick(false); 
     cm.getItems().add(cmi); 

     // deselect all item 
     Label deselectAll = new Label("Deselect all"); 
     deselectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 

      @Override 
      public void handle(MouseEvent event) { 
       for (Object obj : table.getColumns()) { 
        ((TableColumn) obj).setVisible(false); 
       } 
      } 

     }); 

     cmi = new CustomMenuItem(deselectAll); 
     cmi.setHideOnClick(false); 
     cm.getItems().add(cmi); 

     // separator 
     cm.getItems().add(new SeparatorMenuItem()); 

     // menu item for all columns 
     for(Object obj: table.getColumns()) { 

      TableColumn tableColumn = (TableColumn) obj; 

      CheckBox cb = new CheckBox(tableColumn.getText()); 
      cb.selectedProperty().bindBidirectional(tableColumn.visibleProperty()); 

      cmi = new CustomMenuItem(cb); 
      cmi.setHideOnClick(false); 

      cm.getItems().add(cmi); 
     } 

     // set context menu 
     table.setContextMenu(cm); 

     final VBox vbox = new VBox(); 
     vbox.setSpacing(5); 
     vbox.setPadding(new Insets(10, 0, 0, 10)); 
     vbox.getChildren().addAll(table); 

     ((Group) scene.getRoot()).getChildren().addAll(vbox); 

     stage.setScene(scene); 
     stage.show(); 
    } 
} 

ответ

4

Вдохновленные решением ControlsFX я решил проблему сам с помощью отражения. Если у кого-то есть лучшая идея и более чистый способ без размышлений, я все уши. Я создал класс utils, чтобы отличить его от образца кода. Использование

import java.lang.reflect.Field; 

import javafx.collections.ObservableList; 
import javafx.event.EventHandler; 
import javafx.scene.Node; 
import javafx.scene.control.CheckBox; 
import javafx.scene.control.ContextMenu; 
import javafx.scene.control.CustomMenuItem; 
import javafx.scene.control.Label; 
import javafx.scene.control.SeparatorMenuItem; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.input.MouseEvent; 

import com.sun.javafx.scene.control.skin.TableHeaderRow; 
import com.sun.javafx.scene.control.skin.TableViewSkin; 

public class TableViewUtils { 

    /** 
    * Make table menu button visible and replace the context menu with a custom context menu via reflection. 
    * The preferred height is modified so that an empty header row remains visible. This is needed in case you remove all columns, so that the menu button won't disappear with the row header. 
    * IMPORTANT: Modification is only possible AFTER the table has been made visible, otherwise you'd get a NullPointerException 
    * @param tableView 
    */ 
    public static void addCustomTableMenu(TableView tableView) { 

     // enable table menu 
     tableView.setTableMenuButtonVisible(true); 

     // get the table header row 
     TableHeaderRow tableHeaderRow = getTableHeaderRow((TableViewSkin) tableView.getSkin()); 

     // get context menu via reflection 
     ContextMenu contextMenu = getContextMenu(tableHeaderRow); 

     // setting the preferred height for the table header row 
     // if the preferred height isn't set, then the table header would disappear if there are no visible columns 
     // and with it the table menu button 
     // by setting the preferred height the header will always be visible 
     // note: this may need adjustments in case you have different heights in columns (eg when you use grouping) 
     double defaultHeight = tableHeaderRow.getHeight(); 
     tableHeaderRow.setPrefHeight(defaultHeight); 

     // modify the table menu 
     contextMenu.getItems().clear(); 

     addCustomMenuItems(contextMenu, tableView); 

    } 

    /** 
    * Create a menu with custom items. The important thing is that the menu remains open while you click on the menu items. 
    * @param cm 
    * @param table 
    */ 
    private static void addCustomMenuItems(ContextMenu cm, TableView table) { 

     // create new context menu 
     CustomMenuItem cmi; 

     // select all item 
     Label selectAll = new Label("Select all"); 
     selectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 

      @Override 
      public void handle(MouseEvent event) { 
       for (Object obj : table.getColumns()) { 
        ((TableColumn<?, ?>) obj).setVisible(true); 
       } 
      } 

     }); 

     cmi = new CustomMenuItem(selectAll); 
     cmi.setHideOnClick(false); 
     cm.getItems().add(cmi); 

     // deselect all item 
     Label deselectAll = new Label("Deselect all"); 
     deselectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 

      @Override 
      public void handle(MouseEvent event) { 

       for (Object obj : table.getColumns()) { 
        ((TableColumn<?, ?>) obj).setVisible(false); 
       } 
      } 

     }); 

     cmi = new CustomMenuItem(deselectAll); 
     cmi.setHideOnClick(false); 
     cm.getItems().add(cmi); 

     // separator 
     cm.getItems().add(new SeparatorMenuItem()); 

     // menu item for each of the available columns 
     for (Object obj : table.getColumns()) { 

      TableColumn<?, ?> tableColumn = (TableColumn<?, ?>) obj; 

      CheckBox cb = new CheckBox(tableColumn.getText()); 
      cb.selectedProperty().bindBidirectional(tableColumn.visibleProperty()); 

      cmi = new CustomMenuItem(cb); 
      cmi.setHideOnClick(false); 

      cm.getItems().add(cmi); 
     } 

    } 

    /** 
    * Find the TableHeaderRow of the TableViewSkin 
    * 
    * @param tableSkin 
    * @return 
    */ 
    private static TableHeaderRow getTableHeaderRow(TableViewSkin<?> tableSkin) { 

     // get all children of the skin 
     ObservableList<Node> children = tableSkin.getChildren(); 

     // find the TableHeaderRow child 
     for (int i = 0; i < children.size(); i++) { 

      Node node = children.get(i); 

      if (node instanceof TableHeaderRow) { 
       return (TableHeaderRow) node; 
      } 

     } 
     return null; 
    } 

    /** 
    * Get the table menu, i. e. the ContextMenu of the given TableHeaderRow via 
    * reflection 
    * 
    * @param headerRow 
    * @return 
    */ 
    private static ContextMenu getContextMenu(TableHeaderRow headerRow) { 

     try { 

      // get columnPopupMenu field 
      Field privateContextMenuField = TableHeaderRow.class.getDeclaredField("columnPopupMenu"); 

      // make field public 
      privateContextMenuField.setAccessible(true); 

      // get field 
      ContextMenu contextMenu = (ContextMenu) privateContextMenuField.get(headerRow); 

      return contextMenu; 

     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } 

     return null; 
    } 

} 

Пример:

import javafx.application.Application; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.geometry.Insets; 
import javafx.scene.Scene; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.control.cell.PropertyValueFactory; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.VBox; 
import javafx.scene.text.Text; 
import javafx.stage.Stage; 

public class CustomTableMenuDemo extends Application { 

    private final ObservableList<Person> data = 
      FXCollections.observableArrayList( 
      new Person("Jacob", "Smith", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Michael", "Brown", "[email protected]")); 

    public static void main(String[] args) { 
     launch(args); 
    } 

    @Override 
    public void start(Stage stage) { 

     stage.setTitle("Table Menu Demo"); 
     stage.setWidth(500); 
     stage.setHeight(550); 

     // create table columns 
     TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name"); 
     firstNameCol.setMinWidth(100); 
     firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName")); 

     TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name"); 
     lastNameCol.setMinWidth(100); 
     lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName")); 

     TableColumn<Person, String> emailCol = new TableColumn<Person, String>("Email"); 
     emailCol.setMinWidth(180); 
     emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email")); 


     TableView<Person> tableView = new TableView<>(); 
     tableView.setPlaceholder(new Text("No content in table")); 
     tableView.setItems(data); 
     tableView.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 

     final VBox vbox = new VBox(); 
     vbox.setSpacing(5); 
     vbox.setPadding(new Insets(10, 10, 10, 10)); 

     BorderPane borderPane = new BorderPane(); 
     borderPane.setCenter(tableView); 

     vbox.getChildren().addAll(borderPane); 

     Scene scene = new Scene(vbox); 


     stage.setScene(scene); 
     stage.show(); 

     // enable table menu button and add a custom menu to it 
     TableViewUtils.addCustomTableMenu(tableView); 
    } 


    public static class Person { 

     private final SimpleStringProperty firstName; 
     private final SimpleStringProperty lastName; 
     private final SimpleStringProperty email; 

     private Person(String fName, String lName, String email) { 
      this.firstName = new SimpleStringProperty(fName); 
      this.lastName = new SimpleStringProperty(lName); 
      this.email = new SimpleStringProperty(email); 
     } 

     public String getFirstName() { 
      return firstName.get(); 
     } 

     public void setFirstName(String fName) { 
      firstName.set(fName); 
     } 

     public String getLastName() { 
      return lastName.get(); 
     } 

     public void setLastName(String fName) { 
      lastName.set(fName); 
     } 

     public String getEmail() { 
      return email.get(); 
     } 

     public void setEmail(String fName) { 
      email.set(fName); 
     } 
    } 

} 

Скриншоты:

Пользовательские меню таблицы в действии, меню остается открытым во время нажатия кнопки:

Custom table menu in action

Пользовательские таблицы меню все еще доступно, даже если никакие столбцы не являются видимым:

Custom table menu still available, even though no columns are visible

Edit: А вот версия, что вместо отражения использует некоторую эвристику и заменяет обработчик внутреннего события мыши (смотрите источник класса TableHeaderRow JavaFX, если вы хотите узнать больше):

import javafx.collections.ObservableList; 
import javafx.event.EventHandler; 
import javafx.geometry.Side; 
import javafx.scene.Node; 
import javafx.scene.control.CheckBox; 
import javafx.scene.control.ContextMenu; 
import javafx.scene.control.CustomMenuItem; 
import javafx.scene.control.Label; 
import javafx.scene.control.SeparatorMenuItem; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.input.MouseEvent; 

import com.sun.javafx.scene.control.skin.TableHeaderRow; 
import com.sun.javafx.scene.control.skin.TableViewSkin; 

public class TableViewUtils { 

    /** 
    * Make table menu button visible and replace the context menu with a custom context menu via reflection. 
    * The preferred height is modified so that an empty header row remains visible. This is needed in case you remove all columns, so that the menu button won't disappear with the row header. 
    * IMPORTANT: Modification is only possible AFTER the table has been made visible, otherwise you'd get a NullPointerException 
    * @param tableView 
    */ 
    public static void addCustomTableMenu(TableView tableView) { 

     // enable table menu 
     tableView.setTableMenuButtonVisible(true); 

     // replace internal mouse listener with custom listener 
     setCustomContextMenu(tableView); 

    } 

    private static void setCustomContextMenu(TableView table) { 

     TableViewSkin<?> tableSkin = (TableViewSkin<?>) table.getSkin(); 

     // get all children of the skin 
     ObservableList<Node> children = tableSkin.getChildren(); 

     // find the TableHeaderRow child 
     for (int i = 0; i < children.size(); i++) { 

      Node node = children.get(i); 

      if (node instanceof TableHeaderRow) { 

       TableHeaderRow tableHeaderRow = (TableHeaderRow) node; 

       // setting the preferred height for the table header row 
       // if the preferred height isn't set, then the table header would disappear if there are no visible columns 
       // and with it the table menu button 
       // by setting the preferred height the header will always be visible 
       // note: this may need adjustments in case you have different heights in columns (eg when you use grouping) 
       double defaultHeight = tableHeaderRow.getHeight(); 
       tableHeaderRow.setPrefHeight(defaultHeight); 

       for(Node child: tableHeaderRow.getChildren()) { 

        // child identified as cornerRegion in TableHeaderRow.java 
        if(child.getStyleClass().contains("show-hide-columns-button")) { 

         // get the context menu 
         ContextMenu columnPopupMenu = createContextMenu(table); 

         // replace mouse listener 
         child.setOnMousePressed(me -> { 
          // show a popupMenu which lists all columns 
          columnPopupMenu.show(child, Side.BOTTOM, 0, 0); 
          me.consume(); 
         }); 
        } 
       } 

      } 
     } 
    } 

    /** 
    * Create a menu with custom items. The important thing is that the menu remains open while you click on the menu items. 
    * @param cm 
    * @param table 
    */ 
    private static ContextMenu createContextMenu(TableView table) { 

     ContextMenu cm = new ContextMenu(); 

     // create new context menu 
     CustomMenuItem cmi; 

     // select all item 
     Label selectAll = new Label("Select all"); 
     selectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 

      @Override 
      public void handle(MouseEvent event) { 
       for (Object obj : table.getColumns()) { 
        ((TableColumn<?, ?>) obj).setVisible(true); 
       } 
      } 

     }); 

     cmi = new CustomMenuItem(selectAll); 
     cmi.setHideOnClick(false); 
     cm.getItems().add(cmi); 

     // deselect all item 
     Label deselectAll = new Label("Deselect all"); 
     deselectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { 

      @Override 
      public void handle(MouseEvent event) { 

       for (Object obj : table.getColumns()) { 
        ((TableColumn<?, ?>) obj).setVisible(false); 
       } 
      } 

     }); 

     cmi = new CustomMenuItem(deselectAll); 
     cmi.setHideOnClick(false); 
     cm.getItems().add(cmi); 

     // separator 
     cm.getItems().add(new SeparatorMenuItem()); 

     // menu item for each of the available columns 
     for (Object obj : table.getColumns()) { 

      TableColumn<?, ?> tableColumn = (TableColumn<?, ?>) obj; 

      CheckBox cb = new CheckBox(tableColumn.getText()); 
      cb.selectedProperty().bindBidirectional(tableColumn.visibleProperty()); 

      cmi = new CustomMenuItem(cb); 
      cmi.setHideOnClick(false); 

      cm.getItems().add(cmi); 
     } 

     return cm; 
    } 
} 
2

Спасибо, Роланд за ваше решение. Это было здорово. Я обобщил свое решение для немного, чтобы решить некоторые проблемы:

  • избегают иметь, чтобы присвоить TableView с новым контекстного меню после окно показано на рисунке (это может вызвать трудности при showAndWait должны быть использованы(). это решает проблему путем регистрации onShown события вмещающего окна.
  • исправляет mispositioning ошибку при нажатии кнопку + в то время как меню уже. (Щелчок + в то время как
    меню видно будет скрыть меню .)
  • работы с помощью клавиатуры
  • возможность добавить дополнительные пункты меню

Использование:

contextMenuHelper = new TableViewContextMenuHelper(tableView); 
// Adding additional menu options 
MenuItem exportMenuItem = new MenuItem("Export..."); 
contextMenuHelper.getAdditionalMenuItems().add(exportMenuItem); 

Может быть кто-то будет полезно, вот моя реализация:

import java.util.ArrayList; 
import java.util.List; 
import java.util.function.Function; 

import com.sun.javafx.scene.control.skin.TableHeaderRow; 
import com.sun.javafx.scene.control.skin.TableViewSkin; 

import javafx.collections.ObservableList; 
import javafx.event.Event; 
import javafx.geometry.Side; 
import javafx.scene.Node; 
import javafx.scene.control.CheckBox; 
import javafx.scene.control.ContextMenu; 
import javafx.scene.control.CustomMenuItem; 
import javafx.scene.control.Label; 
import javafx.scene.control.MenuItem; 
import javafx.scene.control.SeparatorMenuItem; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.input.KeyCode; 
import javafx.scene.input.KeyEvent; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.layout.Region; 

/** 
* Helper class to replace default column selection popup for TableView. 
* 
* <p> 
* The original idea credeted to Roland and was found on 
* {@link http://stackoverflow.com/questions/27739833/adapt-tableview-menu-button} 
* </p> 
* <p> 
* This improved version targets to solve several problems: 
* <ul> 
* <li>avoid to have to assign the TableView with the new context menu after the 
* window shown (it could cause difficulty when showAndWait() should be used. It 
* solves the problem by registering the onShown event of the containing Window. 
* </li> 
* <li>corrects the mispositioning bug when clicking the + button while the menu 
* is already on.</li> 
* <li>works using keyboard</li> 
* <li>possibility to add additional menu items</li> 
* </ul> 
* </p> 
* <p> 
* Usage from your code: 
* 
* <pre> 
* contextMenuHelper = new TableViewContextMenuHelper(this); 
* // Adding additional menu items 
* MenuItem exportMenuItem = new MenuItem("Export..."); 
* contextMenuHelper.getAdditionalMenuItems().add(exportMenuItem); 
* </pre> 
* </p> 
* 
* @author Roland 
* @author bvissy 
* 
*/ 
public class TableViewContextMenuHelper { 

    private TableView<?> tableView; 
    private ContextMenu columnPopupMenu; 

    private boolean showAllColumnsOperators = true; 

    private List<MenuItem> additionalMenuItems = new ArrayList<>(); 

    // Default key to show menu: Shortcut + Shift + Space 
    private Function<KeyEvent, Boolean> showMenuByKeyboardCheck = 
     ke -> ke.getCode().equals(KeyCode.SPACE) && ke.isShortcutDown() && ke.isShiftDown(); 


    public TableViewContextMenuHelper(TableView<?> tableView) { 
     super(); 
     this.tableView = tableView; 

     // Hooking at the event when the whole window is shown 
     // and then implementing the event handler assignment 
     tableView.sceneProperty().addListener(i -> { 

      tableView.getScene().windowProperty().addListener(i2 -> { 
       tableView.getScene().getWindow().setOnShown(i3 -> { 
        tableView.tableMenuButtonVisibleProperty().addListener((ob, o, n) -> { 
         if (n == true) { 
          registerListeners(); 
         } 
        }); 
        if (tableView.isTableMenuButtonVisible()) { 
         registerListeners(); 
        } 

       }); 

      }); 
     }); 
    } 

    /** 
    * Registers the listeners. 
    */ 
    private void registerListeners() { 
     final Node buttonNode = findButtonNode(); 

     // Keyboard listener on the table 
     tableView.addEventHandler(KeyEvent.KEY_PRESSED, ke -> { 
      if (showMenuByKeyboardCheck.apply(ke)) { 
       showContextMenu(); 
       ke.consume(); 
      } 
     }); 

     // replace mouse listener on "+" node 
     buttonNode.setOnMousePressed(me -> { 
      showContextMenu(); 
      me.consume(); 

     }); 

    } 

    protected void showContextMenu() { 
     final Node buttonNode = findButtonNode(); 

     setFixedHeader(); 

     // When the menu is already shown clicking the + button hides it. 
     if (columnPopupMenu != null) { 
      columnPopupMenu.hide(); 
     } else { 
      // Show the menu 
      final ContextMenu newColumnPopupMenu = createContextMenu(); 
      newColumnPopupMenu.setOnHidden(ev -> { 
       columnPopupMenu = null; 
      }); 
      columnPopupMenu = newColumnPopupMenu; 
      columnPopupMenu.show(buttonNode, Side.BOTTOM, 0, 0); 
      // Repositioning the menu to be aligned by its right side (keeping inside the table view) 
      columnPopupMenu.setX(
       buttonNode.localToScreen(buttonNode.getBoundsInLocal()).getMaxX() 
       - columnPopupMenu.getWidth()); 
     } 
    } 



    private void setFixedHeader() { 
     // setting the preferred height for the table header row 
     // if the preferred height isn't set, then the table header would disappear if there are no visible columns 
     // and with it the table menu button 
     // by setting the preferred height the header will always be visible 
     // note: this may need adjustments in case you have different heights in columns (eg when you use grouping) 
     Region tableHeaderRow = getTableHeaderRow(); 
     double defaultHeight = tableHeaderRow.getHeight(); 
     tableHeaderRow.setPrefHeight(defaultHeight); 
    } 

    private Node findButtonNode() { 
     TableHeaderRow tableHeaderRow = getTableHeaderRow(); 
     if (tableHeaderRow == null) { 
      return null; 
     } 

     for (Node child : tableHeaderRow.getChildren()) { 

      // child identified as cornerRegion in TableHeaderRow.java 
      if (child.getStyleClass().contains("show-hide-columns-button")) { 
       return child; 
      } 
     } 
     return null; 
    } 

    private TableHeaderRow getTableHeaderRow() { 
     TableViewSkin<?> tableSkin = (TableViewSkin<?>) tableView.getSkin(); 
     if (tableSkin == null) { 
      return null; 
     } 

     // get all children of the skin 
     ObservableList<Node> children = tableSkin.getChildren(); 

     // find the TableHeaderRow child 
     for (int i = 0; i < children.size(); i++) { 

      Node node = children.get(i); 

      if (node instanceof TableHeaderRow) { 
       return (TableHeaderRow) node; 
      } 
     } 
     return null; 
    } 


    /** 
    * Create a menu with custom items. The important thing is that the menu 
    * remains open while you click on the menu items. 
    * 
    * @param cm 
    * @param table 
    */ 
    private ContextMenu createContextMenu() { 

     ContextMenu cm = new ContextMenu(); 

     // create new context menu 
     CustomMenuItem cmi; 

     if (showAllColumnsOperators) { 
      // select all item 
      Label selectAll = new Label("Select all"); 
      selectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> doSelectAll(event)); 

      cmi = new CustomMenuItem(selectAll); 
      cmi.setOnAction(e -> doSelectAll(e)); 
      cmi.setHideOnClick(false); 
      cm.getItems().add(cmi); 

      // deselect all item 
      Label deselectAll = new Label("Deselect all"); 
      deselectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> doDeselectAll(event)); 

      cmi = new CustomMenuItem(deselectAll); 
      cmi.setOnAction(e -> doDeselectAll(e)); 
      cmi.setHideOnClick(false); 
      cm.getItems().add(cmi); 

      // separator 
      cm.getItems().add(new SeparatorMenuItem()); 
     } 

     // menu item for each of the available columns 
     for (Object obj : tableView.getColumns()) { 

      TableColumn<?, ?> tableColumn = (TableColumn<?, ?>) obj; 

      CheckBox cb = new CheckBox(tableColumn.getText()); 
      cb.selectedProperty().bindBidirectional(tableColumn.visibleProperty()); 

      cmi = new CustomMenuItem(cb); 
      cmi.setOnAction(e -> { 
       cb.setSelected(!cb.isSelected()); 
       e.consume(); 
      }); 
      cmi.setHideOnClick(false); 

      cm.getItems().add(cmi); 
     } 

     if (!additionalMenuItems.isEmpty()) { 
      cm.getItems().add(new SeparatorMenuItem()); 
      cm.getItems().addAll(additionalMenuItems); 
     } 

     return cm; 
    } 

    protected void doDeselectAll(Event e) { 
     for (Object obj : tableView.getColumns()) { 
      ((TableColumn<?, ?>) obj).setVisible(false); 
     } 
     e.consume(); 
    } 

    protected void doSelectAll(Event e) { 
     for (Object obj : tableView.getColumns()) { 
      ((TableColumn<?, ?>) obj).setVisible(true); 
     } 
     e.consume(); 
    } 

    public boolean isShowAllColumnsOperators() { 
     return showAllColumnsOperators; 
    } 

    /** 
    * Sets whether the Select all/Deselect all buttons are visible 
    * 
    * @param showAllColumnsOperators 
    */ 
    public void setShowAllColumnsOperators(boolean showAllColumnsOperators) { 
     this.showAllColumnsOperators = showAllColumnsOperators; 
    } 

    public List<MenuItem> getAdditionalMenuItems() { 
     return additionalMenuItems; 
    } 

    public Function<KeyEvent, Boolean> getShowMenuByKeyboardCheck() { 
     return showMenuByKeyboardCheck; 
    } 

    /** 
    * Overrides the keypress check to show the menu. Default is Shortcut + 
    * Shift + Space. 
    * 
    * <p> 
    * To disable keyboard shortcut use the <code>e -> false</code> function. 
    * </p> 
    * 
    * @param showMenuByKeyboardCheck 
    */ 
    public void setShowMenuByKeyboardCheck(Function<KeyEvent, Boolean> showMenuByKeyboardCheck) { 
     this.showMenuByKeyboardCheck = showMenuByKeyboardCheck; 
    } 

} 
0

Update

Концерн что при отмене всех столбцов заголовок все еще отображается, а также кнопка меню. JDK 8u72

0

Я попытался реализовать решение Balage1551.

Для моего приложения мне пришлось изменить слушателей в TableViewContextMenuHelper (...).

Без этих изменений я получал исключение NullPointerException каждый раз, когда я менял реальную сцену и возвращался после этого на свой экран, содержащий табличное представление.

Я надеюсь, что кто-то может найти это полезным!

// Hooking at the event when the whole window is shown 
    // and then implementing the event handler assignment 
    /*tableView.sceneProperty().addListener(i -> { 

     tableView.getScene().windowProperty().addListener(i2 -> { 
      tableView.getScene().getWindow().setOnShown(i3 -> { 
       tableView.tableMenuButtonVisibleProperty().addListener((ob, o, n) -> { 
        if (n == true) { 
         registerListeners(); 
        } 
       }); 
       if (tableView.isTableMenuButtonVisible()) { 
        registerListeners(); 
       } 

      }); 

     }); 
    });*/ 

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ OLD! ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvNEW! vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

tableView.skinProperty().addListener((a, b, newSkin) -> { 
     tableView.tableMenuButtonVisibleProperty().addListener((ob, o, n) -> { 
      if (n == true) { 
       registerListeners(); 
      } 
     }); 
     if (tableView.isTableMenuButtonVisible()) { 
      registerListeners(); 
     } 
    }); 

Это приспособление позволяет снова инициализировать TableViewContextMenuHelper, когда вам открыть другую сцену:

javafx.stage.Stage.setScreen(...); 
0

Первый раз плакат, пожалуйста, быть нежным со мной ...

У меня есть таблица (на самом деле куча таблиц), где столбцы не фиксированы. Каждый раз, когда столбцы меняются, вышеупомянутое решение переустанавливало список столбцов. Поэтому, если столбец «Размер воротника» был скрыт, он снова появится, когда таблица будет обновлена ​​с новым набором данных.

Это может быть грубо, но я добавил набор, чтобы хранить имена столбцов, которые были скрыты последний раз, а затем повторно скрыть их на этот раз.

Суть является Set:

private Set<String> turnedOff = new HashSet<>(); 

, а затем управление добавления и удаления элементов из множества. Мне нужно было добавить слушателя в столбцы таблицы, чтобы скрыть новые столбцы, которые совпадали с ранее скрытым именем.

Другие идеи о том, как это сделать, будут оценены.

import java.util.ArrayList; 
import java.util.HashSet; 
import java.util.List; 
import java.util.Set; 
import java.util.function.Function; 

import com.sun.javafx.scene.control.skin.TableHeaderRow; 
import com.sun.javafx.scene.control.skin.TableViewSkin; 

import javafx.collections.ListChangeListener; 
import javafx.collections.ObservableList; 
import javafx.event.Event; 
import javafx.geometry.Side; 
import javafx.scene.Node; 
import javafx.scene.control.CheckBox; 
import javafx.scene.control.ContextMenu; 
import javafx.scene.control.CustomMenuItem; 
import javafx.scene.control.Label; 
import javafx.scene.control.MenuItem; 
import javafx.scene.control.SeparatorMenuItem; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.input.KeyCode; 
import javafx.scene.input.KeyEvent; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.layout.Region; 

public class TableViewContextMenuHelper { 

    private Set<String> turnedOff = new HashSet<>(); 

    private TableView<?> tableView; 
    private ContextMenu columnPopupMenu; 

    private boolean showAllColumnsOperators = true; 

    private List<MenuItem> additionalMenuItems = new ArrayList<>(); 

    // Default key to show menu: Shortcut + Shift + Space 
    private Function<KeyEvent, Boolean> showMenuByKeyboardCheck = 
     ke -> ke.getCode().equals(KeyCode.SPACE) && ke.isShortcutDown() && ke.isShiftDown(); 


    public TableViewContextMenuHelper(TableView<?> tableView) { 
     super(); 
     this.tableView = tableView; 

     tableView.skinProperty().addListener((a, b, newSkin) -> { 
     tableView.tableMenuButtonVisibleProperty().addListener((ob, o, n) -> { 
      if (n == true) { 
       registerListeners(); 
      } 
     }); 
     if (tableView.isTableMenuButtonVisible()) { 
      registerListeners(); 
     } 
    }); 

    } 

    /** 
    * Registers the listeners. 
    */ 
    private void registerListeners() { 
     final Node buttonNode = findButtonNode(); 

     // Keyboard listener on the table 
     tableView.addEventHandler(KeyEvent.KEY_PRESSED, ke -> { 
      if (showMenuByKeyboardCheck.apply(ke)) { 
       showContextMenu(); 
       ke.consume(); 
      } 
     }); 

     // replace mouse listener on "+" node 
     buttonNode.setOnMousePressed(me -> { 
      showContextMenu(); 
      me.consume(); 

     }); 

     tableView.getColumns().addListener(new ListChangeListener<TableColumn<?,?>>(){ 

     @Override 
     public void onChanged(javafx.collections.ListChangeListener.Change<? extends TableColumn<?, ?>> c) { 
      while(c.next()){ 
      if(c.getAddedSize()>0){ 
      // hide "turned off" columns 
      for(TableColumn<?, ?> tc:c.getAddedSubList()){    
       if(turnedOff.contains(tc.getText())){ 
       tc.setVisible(false); 
       } 

      } 
      } 
     } 
     } 
     }); 

    } 

    protected void showContextMenu() { 
     final Node buttonNode = findButtonNode(); 

     setFixedHeader(); 

     // When the menu is already shown clicking the + button hides it. 
     if (columnPopupMenu != null) { 
      columnPopupMenu.hide(); 
     } else { 
      // Show the menu 
      final ContextMenu newColumnPopupMenu = createContextMenu(); 
      newColumnPopupMenu.setOnHidden(ev -> { 
       columnPopupMenu = null; 
      }); 
      columnPopupMenu = newColumnPopupMenu; 
      columnPopupMenu.show(buttonNode, Side.BOTTOM, 0, 0); 
      // Repositioning the menu to be aligned by its right side (keeping inside the table view) 
      columnPopupMenu.setX(
       buttonNode.localToScreen(buttonNode.getBoundsInLocal()).getMaxX() 
       - columnPopupMenu.getWidth()); 
     } 
    } 



    private void setFixedHeader() { 
     // setting the preferred height for the table header row 
     // if the preferred height isn't set, then the table header would disappear if there are no visible columns 
     // and with it the table menu button 
     // by setting the preferred height the header will always be visible 
     // note: this may need adjustments in case you have different heights in columns (eg when you use grouping) 
     Region tableHeaderRow = getTableHeaderRow(); 
     double defaultHeight = tableHeaderRow.getHeight(); 
     tableHeaderRow.setPrefHeight(defaultHeight); 
    } 

    private Node findButtonNode() { 
     TableHeaderRow tableHeaderRow = getTableHeaderRow(); 
     if (tableHeaderRow == null) { 
      return null; 
     } 

     for (Node child : tableHeaderRow.getChildren()) { 

      // child identified as cornerRegion in TableHeaderRow.java 
      if (child.getStyleClass().contains("show-hide-columns-button")) { 
       return child; 
      } 
     } 
     return null; 
    } 

    private TableHeaderRow getTableHeaderRow() { 
     TableViewSkin<?> tableSkin = (TableViewSkin<?>) tableView.getSkin(); 
     if (tableSkin == null) { 
      return null; 
     } 

     // get all children of the skin 
     ObservableList<Node> children = tableSkin.getChildren(); 

     // find the TableHeaderRow child 
     for (int i = 0; i < children.size(); i++) { 
      Node node = children.get(i); 
      if (node instanceof TableHeaderRow) { 
       return (TableHeaderRow) node; 
      } 
     } 
     return null; 
    } 


    /** 
    * Create a menu with custom items. The important thing is that the menu 
    * remains open while you click on the menu items. 
    * 
    * @param cm 
    * @param table 
    */ 
    private ContextMenu createContextMenu() { 

     ContextMenu cm = new ContextMenu(); 

     // create new context menu 
     CustomMenuItem cmi; 

     if (showAllColumnsOperators) { 
      // select all item 
      Label selectAll = new Label("Select all"); 
      selectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> doSelectAll(event)); 

      cmi = new CustomMenuItem(selectAll); 
      cmi.setOnAction(e -> doSelectAll(e)); 
      cmi.setHideOnClick(false); 
      cm.getItems().add(cmi); 

      // deselect all item 
      Label deselectAll = new Label("Deselect all"); 
      deselectAll.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> doDeselectAll(event)); 

      cmi = new CustomMenuItem(deselectAll); 
      cmi.setOnAction(e -> doDeselectAll(e)); 
      cmi.setHideOnClick(false); 
      cm.getItems().add(cmi); 

      // separator 
      cm.getItems().add(new SeparatorMenuItem()); 
     } 

     // menu item for each of the available columns 
     for (Object obj : tableView.getColumns()) { 

      TableColumn<?, ?> tableColumn = (TableColumn<?, ?>) obj; 

      CheckBox cb = new CheckBox(tableColumn.getText()); 
      cb.selectedProperty().bindBidirectional(tableColumn.visibleProperty()); 

      cmi = new CustomMenuItem(cb); 
      if(turnedOff.contains(cb.getText())){ 
      cb.setSelected(false); 
      } 
      cmi.setOnAction(e -> { 
       cb.setSelected(!cb.isSelected()); 
       if(cb.isSelected()){ 
       turnedOff.remove(cb.getText()); 
       } else { 
       turnedOff.add(cb.getText()); 
       } 
       e.consume(); 
      }); 
      cmi.setHideOnClick(false); 

      cm.getItems().add(cmi); 
     } 

     if (!additionalMenuItems.isEmpty()) { 
      cm.getItems().add(new SeparatorMenuItem()); 
      cm.getItems().addAll(additionalMenuItems); 
     } 

     return cm; 
    } 

    protected void doDeselectAll(Event e) { 
     for (TableColumn<?, ?> obj : tableView.getColumns()) { 
     turnedOff.add(obj.getText()); 
      obj.setVisible(false); 
     } 
     e.consume(); 
    } 

    protected void doSelectAll(Event e) { 
     for (TableColumn<?, ?> obj : tableView.getColumns()) {   
     turnedOff.remove(obj.getText()); 
     obj.setVisible(true);   
     } 
     e.consume(); 
    } 

    public boolean isShowAllColumnsOperators() { 
     return showAllColumnsOperators; 
    } 

    /** 
    * Sets whether the Select all/Deselect all buttons are visible 
    * 
    * @param showAllColumnsOperators 
    */ 
    public void setShowAllColumnsOperators(boolean showAllColumnsOperators) { 
     this.showAllColumnsOperators = showAllColumnsOperators; 
    } 

    public List<MenuItem> getAdditionalMenuItems() { 
     return additionalMenuItems; 
    } 

    public Function<KeyEvent, Boolean> getShowMenuByKeyboardCheck() { 
     return showMenuByKeyboardCheck; 
    } 

    /** 
    * Overrides the keypress check to show the menu. Default is Shortcut + 
    * Shift + Space. 
    * 
    * <p> 
    * To disable keyboard shortcut use the <code>e -> false</code> function. 
    * </p> 
    * 
    * @param showMenuByKeyboardCheck 
    */ 
    public void setShowMenuByKeyboardCheck(Function<KeyEvent, Boolean> showMenuByKeyboardCheck) { 
     this.showMenuByKeyboardCheck = showMenuByKeyboardCheck; 
    } 

} 
+0

Вы отправили это как ответ на существующий вопрос. Пожалуйста, разместите его как свой вопрос, чтобы вы могли получить ответы. –