2015-02-23 68 views
4

A cell contextMenu can't be activated by keyboard:它的根本原因是contextMenuEvent被調度到焦點節點 - 這是包含表格,而不是單元格。喬納森的錯誤評估對如何解決它的輪廓:單元格:如何通過鍵盤激活contextMenu?

的「正確」的方式做到這一點是可能覆蓋TableView中的buildEventDispatchChain和包括TableViewSkin(如果它實現了此事件),並隨時轉發這下降到表格行中的單元格。

試圖遵循該路徑(下面是ListView的一個例子,只是因爲只有一個級別的皮膚來實現兩個TableView)。它正在工作,種類:單元格contextMenu由鍵盤彈出式觸發器激活,但相對於表格相對於單元格定位。

問題:如何掛鉤到調度鏈中,使其位於相對於單元?

可運行的代碼示例:

package de.swingempire.fx.scene.control.et; 

import javafx.application.Application; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.event.EventDispatchChain; 
import javafx.event.EventTarget; 
import javafx.scene.Node; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.control.Cell; 
import javafx.scene.control.ContextMenu; 
import javafx.scene.control.ListCell; 
import javafx.scene.control.ListView; 
import javafx.scene.control.MenuItem; 
import javafx.scene.control.Skin; 
import javafx.stage.Stage; 

import com.sun.javafx.event.EventHandlerManager; 
import com.sun.javafx.scene.control.skin.ListViewSkin; 

/** 
* Activate cell contextMenu by keyboard, quick shot on ListView 
* @author Jeanette Winzenburg, Berlin 
*/ 
public class ListViewETContextMenu extends Application { 

    private Parent getContent() { 
     ObservableList<String> data = FXCollections.observableArrayList("one", "two", "three"); 
//  ListView<String> listView = new ListView<>(); 
     ListViewC<String> listView = new ListViewC<>(); 
     listView.setItems(data); 
     listView.setCellFactory(p -> new ListCellC<>(new ContextMenu(new MenuItem("item")))); 
     return listView; 
    } 

    /** 
     * ListViewSkin that implements EventTarget and 
     * hooks the focused cell into the event dispatch chain 
     */ 
     private static class ListViewCSkin<T> extends ListViewSkin<T> implements EventTarget { 
      private EventHandlerManager eventHandlerManager = new EventHandlerManager(this); 

      @Override 
      public EventDispatchChain buildEventDispatchChain(
        EventDispatchChain tail) { 
       int focused = getSkinnable().getFocusModel().getFocusedIndex(); 
       if (focused > - 1) { 
        Cell<?> cell = flow.getCell(focused); 
        tail = cell.buildEventDispatchChain(tail); 
       } 
       // returning the chain as is or prepend our 
       // eventhandlermanager doesn't make a difference 
       // return tail; 
       return tail.prepend(eventHandlerManager); 
      } 

      // boiler-plate constructor 
      public ListViewCSkin(ListView<T> listView) { 
       super(listView); 
      } 

     } 

    /** 
    * ListView that hooks its skin into the event dispatch chain. 
    */ 
    private static class ListViewC<T> extends ListView<T> { 

     @Override 
     public EventDispatchChain buildEventDispatchChain(
       EventDispatchChain tail) { 
      if (getSkin() instanceof EventTarget) { 
       tail = ((EventTarget) getSkin()).buildEventDispatchChain(tail); 
      } 
      return super.buildEventDispatchChain(tail); 
     } 

     @Override 
     protected Skin<?> createDefaultSkin() { 
      return new ListViewCSkin<>(this); 
     } 

    } 

    private static class ListCellC<T> extends ListCell<T> { 

     public ListCellC(ContextMenu menu) { 
      setContextMenu(menu); 
     } 

     // boiler-plate: copy of default implementation 
     @Override 
     public void updateItem(T item, boolean empty) { 
      super.updateItem(item, empty); 

      if (empty) { 
       setText(null); 
       setGraphic(null); 
      } else if (item instanceof Node) { 
       setText(null); 
       Node currentNode = getGraphic(); 
       Node newNode = (Node) item; 
       if (currentNode == null || ! currentNode.equals(newNode)) { 
        setGraphic(newNode); 
       } 
      } else { 
       /** 
       * This label is used if the item associated with this cell is to be 
       * represented as a String. While we will lazily instantiate it 
       * we never clear it, being more afraid of object churn than a minor 
       * "leak" (which will not become a "major" leak). 
       */ 
       setText(item == null ? "null" : item.toString()); 
       setGraphic(null); 
      } 
     } 

    } 
    @Override 
    public void start(Stage primaryStage) throws Exception { 
     Scene scene = new Scene(getContent()); 
     primaryStage.setScene(scene); 
     primaryStage.show(); 
    } 

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

入住這裏,http://stackoverflow.com/a/23420064/2855515 – brian 2015-02-23 13:29:16

+0

@布賴恩是的,所見到的 - 感謝參考反正:-)但黑客(另一個是跟蹤的該顯示屬性contextMenu並手動定位)不是我所追求的 - 我想掛鉤到事件調度中,以便默認的顯示/定位在沒有進一步努力的情況下在部分客戶端代碼上工作 – kleopatra 2015-02-23 13:59:53

回答

1

發現了一些事實:

  • 的的ContextMenuEvent的建立與scene.processMenuEvent(...)
  • 鍵盤觸發事件發了,該方法計算場景/屏幕座標相對於目標節點(這是當前焦點所有者)中間的某個地方
  • 這些(場景/屏幕)a bsolute座標不能改變:event.copyFor(...)只有它們映射到新的目標局部座標

因此,任何希望一些AUTOMAGIC沒有發揮出來,我們必須重新計算位置。一個(暫定的)做這個事情的地方是一個自定義的EventDispatcher。原始的(閱讀:缺少所有完整性檢查,未經過正式測試,可能會產生不必要的副作用!)下面的示例只是在委託給注入的EventDispatcher之前,用一個新鍵盤替換鍵盤觸發的contextMenuEvent。客戶端代碼(如f.i.ListViewSkin)必須先傳入targetCell,然後再預先添加到EventDispatchChain。

/** 
* EventDispatcher that replaces a keyboard-triggered ContextMenuEvent by a 
* newly created event that has screen coordinates relativ to the target cell. 
* 
*/ 
private static class ContextMenuEventDispatcher implements EventDispatcher { 

    private EventDispatcher delegate; 
    private Cell<?> targetCell; 

    public ContextMenuEventDispatcher(EventDispatcher delegate) { 
     this.delegate = delegate; 
    } 

    /** 
    * Sets the target cell for the context menu. 
    * @param cell 
    */ 
    public void setTargetCell(Cell<?> cell) { 
     this.targetCell = cell; 
    } 

    /** 
    * Implemented to replace a keyboard-triggered contextMenuEvent before 
    * letting the delegate dispatch it. 
    * 
    */ 
    @Override 
    public Event dispatchEvent(Event event, EventDispatchChain tail) { 
     event = handleContextMenuEvent(event); 
     return delegate.dispatchEvent(event, tail); 
    } 

    private Event handleContextMenuEvent(Event event) { 
     if (!(event instanceof ContextMenuEvent) || targetCell == null) return event; 
     ContextMenuEvent cme = (ContextMenuEvent) event; 
     if (!cme.isKeyboardTrigger()) return event; 
     final Bounds bounds = targetCell.localToScreen(
       targetCell.getBoundsInLocal()); 
     // calculate screen coordinates of contextMenu 
     double x2 = bounds.getMinX() + bounds.getWidth()/4; 
     double y2 = bounds.getMinY() + bounds.getHeight()/2; 
     // instantiate a contextMenuEvent with the cell-related coordinates 
     ContextMenuEvent toCell = new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED, 
       0, 0, x2, y2, true, null); 
     return toCell; 
    } 

} 

// usage (f.i. in ListViewSkin) 
/** 
* ListViewSkin that implements EventTarget and hooks the focused cell into 
* the event dispatch chain 
*/ 
private static class ListViewCSkin<T> extends ListViewSkin<T> implements 
     EventTarget { 

    private ContextMenuEventDispatcher contextHandler = 
      new ContextMenuEventDispatcher(new EventHandlerManager(this)); 

    @Override 
    public EventDispatchChain buildEventDispatchChain(
      EventDispatchChain tail) { 
     int focused = getSkinnable().getFocusModel().getFocusedIndex(); 
     Cell cell = null; 
     if (focused > -1) { 
      cell = flow.getCell(focused); 
      tail = cell.buildEventDispatchChain(tail); 
     } 
     contextHandler.setTargetCell(cell); 
     // the handlerManager doesn't make a difference 
     return tail.prepend(contextHandler); 
    } 

    // boiler-plate constructor 
    public ListViewCSkin(ListView<T> listView) { 
     super(listView); 
    } 

} 

編輯

只注意到在輕微的(?)毛刺鍵盤激活的ListView控件的ContextMenu在小區的位置顯示,如果該小區沒有一個文本菜單上它自己。無法找到一種方法來取代事件,如果單元格未使用,可能仍然在事件派發中缺少明顯的(?)。