Cell:如何通过键盘激活contextMenu? [英] Cell: how to activate a contextMenu by keyboard?

查看:149
本文介绍了Cell:如何通过键盘激活contextMenu?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

单元格contextMenu无法通过键盘激活:它的根本原因因为contextMenuEvent被分派到聚焦节点 - 这是包含表,而不是单元格。 Jonathan的错误评估概述了如何解决它:

A cell contextMenu can't be activated by keyboard: it's underlying reason being that the contextMenuEvent is dispatched to the focused node - which is the containing table, not the cell. The bug evaluation by Jonathan has an outline of how solve it:


正确的方法是覆盖buildEventDispatchChain in TableView并包含TableViewSkin(如果它实现EventDispatcher),并将其转发到表行中的单元格。

The 'proper' way to do this is to probably override the buildEventDispatchChain in TableView and include the TableViewSkin (if it implements EventDispatcher), and to keep forwarding this down to the cells in the table row.

尝试遵循该路径(下面是ListView的一个示例,因为只有一个级别的皮肤要实现,而对于TableView只有两个。)它的工作方式是:单元格contextMenu由键盘弹出触发器激活,但相对于表格相对于单元格定位。

Tried to follow that path (below is an example for ListView, simply because there's only one level of skins to implement vs. two for a TableView). It's working, kind of: the cell contextMenu is activated by the keyboard popup trigger, but positioned relative to the table vs. relative to the cell.

问题:如何挂钩进入调度链,使其相对于单元格?

Question: how to hook into the dispatch chain such that it's located relative to the cell?

可运行代码示例:

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);
    }
}


推荐答案

挖掘一些事实:


  • scene.processMenuEvent(...)中创建并启动了contextMenuEvent

  • 对于键盘触发事件,该方法计算相对于目标节点中间某处的场景/屏幕坐标(当前焦点所有者)

  • 这些(场景/屏幕)绝对坐标无法更改: event.copyFor(...)仅将它们映射到新的目标本地坐标

  • the contextMenuEvent is created and fired off in scene.processMenuEvent(...)
  • for keyboard triggered events, the method calculates scene/screen coordinates relative to somewhere in the middle of the target node (which is the current focus owner)
  • these (scene/screen) absolute coordinates can't be changed: event.copyFor(...) only maps them to the new target local coordinates

所以对某些autogic的任何希望都没有成功,我们必须重新计算位置。一个(暂定的)地方是一个自定义的EventDispatcher。原始(读取:缺少所有健全性检查,未经过正式测试,可能会产生不必要的副作用!)下面的示例只是在委托给注入的EventDispatcher之前用新的键替换键盘触发的contextMenuEvent。客户端代码(如ListViewSkin)必须先传入targetCell,然后再添加到EventDispatchChain。

So any hope for some automagic didn't work out, we have to re-calculate the location. A (tentative) place to do this is a custom EventDispatcher. The raw (read: missing all sanity checks, not formally tested, might have unwanted side-effects!) example below simply replaces a keyboard-triggered contextMenuEvent by a new one before delegating to an injected EventDispatcher. Client code (like f.i. the ListViewSkin) must pass-in the targetCell before prepending to the 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);
    }

}

编辑

注意到一个轻微的(?)故障,如果单元格上没有contextMenu,listView上的键盘激活的contextMenu会显示在单元格位置它自己的。如果单元格未使用,则无法找到替换事件的方法,可能仍然在事件发送中遗漏了明显的事情(?)。

Just noticed a slight (?) glitch in that a keyboard-activated contextMenu on the listView is shown at the cell location if the cell doesn't have a contextMenu on its own. Couldn't find a way to not replace the event if unused by the cell, probably still missing something obvious (?) in the event dispatch.

这篇关于Cell:如何通过键盘激活contextMenu?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆