2015-02-23 73 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在小区的位置显示,如果该小区没有一个文本菜单上它自己。无法找到一种方法来取代事件,如果单元格未使用,可能仍然在事件派发中缺少明显的(?)。