如何覆盖行为的已安装映射? [英] How to override installed mappings of Behavior?

查看:70
本文介绍了如何覆盖行为的已安装映射?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在java-9中,Skins将其设置为公共范围,而行为"则被保留在黑暗中-尽管有了很大的变化,但现在对所有输入绑定都使用InputMap.

In java-9 Skins made it into public scope, while Behaviors are left in the dark - nevertheless changed considerably, in now using InputMap for all input bindings.

CellBehaviorBase安装鼠标绑定,例如:

CellBehaviorBase installs mouse bindings like:

InputMap.MouseMapping pressedMapping, releasedMapping;
addDefaultMapping(
    pressedMapping = new InputMap.MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed),
    releasedMapping = new InputMap.MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased),
    new InputMap.MouseMapping(MouseEvent.MOUSE_DRAGGED, this::mouseDragged)
);

具体的XXSkin现在可以私下安装行为:

A concrete XXSkin now installs the behavior privately:

final private BehaviorBase behavior; 
public TableCellSkin(TableCell control) {
    super(control);
    behavior = new TableCellBehavior(control);
    .... 
}

要求是替换mousePressed行为(在jdk9上下文中).这个想法是反射性地抓住super的领域,配置其所有映射并安装自定义行为.由于某些我不了解的原因,旧的绑定仍然有效(尽管旧的映射为空!),并且在新的绑定之前 被调用.

The requirement is replace the mousePressed behavior (in jdk9 context). The idea is to grab super's field reflectively, dispose all its mappings and install the custom behavior. For some reason that I don't understand, the old bindings are still active (though the old mappings are empty!) and are invoked before the new bindings.

下面是一个可运行的示例:只需简单地实现对mousePressed的映射即可执行任何操作,尤其是对于 not 调用super而言.要查看工作中的旧绑定,我在CellBehaviorBase.mousePressed之类的(在Eclipse中)设置了条件调试断点:

Below is a runnable example to play with: the mapping to mousePressed is simply implemented to do nothing, particularly to not invoke super. To see the old bindings at work, I set a conditional debug breakpoint at CellBehaviorBase.mousePressed like (in Eclipse):

System.out.println("mousePressed super");
new RuntimeException("whoIsCalling: " + getNode().getClass()).printStackTrace();
return false;

运行调试并单击任何单元格,然后输出为:

Run a debug and click into any cell, then the output is:

mousePressed super
java.lang.RuntimeException: whoIsCalling: class de.swingempire.fx.scene.control.cell.TableCellBehaviorReplace$PlainCustomTableCell
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mousePressed(CellBehaviorBase.java:169)
    at com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)

//... lots more of event dispatching

// until finally the output in my custom cell behavior

Feb. 02, 2016 3:14:02 NACHM. de.swingempire.fx.scene.control.cell.TableCellBehaviorReplace$PlainCustomTableCellBehavior mousePressed
INFORMATION: short-circuit super: Bulgarisch

我希望只能看到最后一部分,这是我的自定义行为的打印输出.感觉上我根本就离开了,但不能钉牢.想法?

I would expect to only see the very last part, that is the printout by my custom behavior. It feels like I'm somehow fundamentally off - but can't nail it. Ideas?

可运行的代码(很抱歉,它的长度,大多数是样板代码):

The runnable code (sorry for its length, most is boiler-plate, though):

public class TableCellBehaviorReplace extends Application {

    private final ObservableList<Locale> locales =
            FXCollections.observableArrayList(Locale.getAvailableLocales());

    private Parent getContent() {
        TableView<Locale> table = createLocaleTable();
        BorderPane content = new BorderPane(table);
        return content;
    }

    private TableView<Locale> createLocaleTable() {
        TableView<Locale> table = new TableView<>(locales);

        TableColumn<Locale, String> name = new TableColumn<>("Name");
        name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
        name.setCellFactory(p -> new PlainCustomTableCell<>());

        TableColumn<Locale, String> lang = new TableColumn<>("Language");
        lang.setCellValueFactory(new PropertyValueFactory<>("displayLanguage"));
        lang.setCellFactory(p -> new PlainCustomTableCell<>());

        table.getColumns().addAll(name, lang);
        return table;
    }

    /**
     * Custom skin that installs custom Behavior. Note: this is dirty!
     * Access super's behavior, dispose to get rid off its handlers, install
     * custom behavior.
     */
    public static class PlainCustomTableCellSkin<S, T> extends TableCellSkin<S, T> {

        private BehaviorBase<?> replacedBehavior;
        public PlainCustomTableCellSkin(TableCell<S, T> control) {
            super(control);
            replaceBehavior();
        }

        private void replaceBehavior() {
            BehaviorBase<?> old = (BehaviorBase<?>) invokeGetField(TableCellSkin.class, this, "behavior");
            old.dispose();
            // at this point, InputMap mappings are empty:
            // System.out.println("old mappings: " + old.getInputMap().getMappings().size());
            replacedBehavior = new PlainCustomTableCellBehavior<>(getSkinnable());
        }

        @Override
        public void dispose() {
            replacedBehavior.dispose();
            super.dispose();
        }

    }

    /**
     * Custom behavior that's meant to override basic handlers. Here: short-circuit
     * mousePressed.
     */
    public static class PlainCustomTableCellBehavior<S, T> extends TableCellBehavior<S, T> {

        public PlainCustomTableCellBehavior(TableCell<S, T> control) {
            super(control);
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (true) {
                LOG.info("short-circuit super: " + getNode().getItem());
                return;
            }
            super.mousePressed(e);
        }

    }


    /**
     * C&P of default tableCell in TableColumn. Extended to install custom
     * skin.
     */
    public static class PlainCustomTableCell<S, T> extends TableCell<S, T> {

        public PlainCustomTableCell() {
        }

        @Override protected void updateItem(T item, boolean empty) {
            if (item == getItem()) return;

            super.updateItem(item, empty);

            if (item == null) {
                super.setText(null);
                super.setGraphic(null);
            } else if (item instanceof Node) {
                super.setText(null);
                super.setGraphic((Node)item);
            } else {
                super.setText(item.toString());
                super.setGraphic(null);
            }
        }

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

    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getContent(), 400, 200));
        primaryStage.setTitle(FXUtils.version());
        primaryStage.show();
    }

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

    /**
     * Reflectively access super field.
     */
    public static Object invokeGetField(Class source, Object target, String name) {
        try {
            Field field = source.getDeclaredField(name);
            field.setAccessible(true);
            return field.get(target);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(TableCellBehaviorReplace.class.getName());
}

修改

从抽象皮肤XXSkinBase而不是具体的XXSkin 继承的建议(当时您可以自由安装任何所需的行为,伙计:-)是非常合理的,应该是第一个选择.在XX是TableCell的特定情况下,目前不可能作为基础类包含抽象的包专用方法.另外,还有XX没有抽象基础(例如f.i. ListCell).

The suggestion inherit from the abstract skin XXSkinBase instead of the concrete XXSkin (then you are free to install whatever behavior you want, dude :-) is very reasonable and should be the first option. In the particular case of XX being TableCell, that's currently not possible, as the base class contains abstract package-private methods. Also, there are XX that don't have an abstract base (like f.i. ListCell).

推荐答案

可能是InputMap中的错误:

Might be a bug in InputMap:

深入研究源代码,我发现一些内部的簿记(eventTypeMappings)与映射(这些是处理程序)并行. InputMap正在侦听映射中的更改,并根据更改更新内部簿记

Digging into the sources I found some internal book-keeping (eventTypeMappings) parallel to mappings (these are the handlers). InputMap is listening to changes in mappings and updates the internal book-keeping on changes

mappings.addListener((ListChangeListener<Mapping<?>>) c -> {
    while (c.next()) {
        // TODO handle mapping removal
        if (c.wasRemoved()) {
            for (Mapping<?> mapping : c.getRemoved()) {
                removeMapping(mapping);
            }
        }

// removeMapping
private void removeMapping(Mapping<?> mapping) {
    // TODO
}

表示永远不会清除内部结构,尤其是在behavior.dispose()中删除映射时不会清除.在按inputMap.handle(e)查找eventHandlers时,请参阅问题中显示的调试stacktrace-在内部簿记结构中可以找到旧的处理程序.

Meaning that the internal structure is never cleaned, particularly not when the mappings are removed in behavior.dispose(). When looking up eventHandlers - by inputMap.handle(e), see debug stacktrace shown in the question - the old handler is found in the internal book-keeping structure.

早期实验的乐趣...;-)

Joys of early experiments ... ;-)

最后,一个(非常肮脏,很hacky!)解决方案是接管InputMap的工作并强制清理内部:

At the end, a (very dirty, very hacky!) solution is to take over InputMap's job and force a cleanup of the internals:

private void replaceBehavior() {
    BehaviorBase<?> old = (BehaviorBase<?>) invokeGetField(TableCellSkin.class, this, "behavior");
    old.dispose();
    cleanupInputMap(old.getInputMap());
    // at this point, InputMap mappings are empty:
    // System.out.println("old mappings: " + old.getInputMap().getMappings().size());
    replacedBehavior = new PlainCustomTableCellBehavior<>(getSkinnable());
}

/**
 * This is a hack around InputMap not cleaning up internals on removing mappings.
 * We remove MousePressed/MouseReleased/MouseDragged mappings from the internal map.
 * Beware: obviously this is dirty!
 * 
 * @param inputMap
 */
private void cleanupInputMap(InputMap<?> inputMap) {
    Map eventTypeMappings = (Map) invokeGetField(InputMap.class, inputMap, "eventTypeMappings");
    eventTypeMappings.remove(MouseEvent.MOUSE_PRESSED);
    eventTypeMappings.remove(MouseEvent.MOUSE_RELEASED);
    eventTypeMappings.remove(MouseEvent.MOUSE_DRAGGED);
}

顺便说一句:万一有人想知道wtf –没有,我在修改单元格时围绕丢失的commitOnFocusLost的技术在Java-9中停止工作.

BTW: just in case anybody is wondering wtf - without, my hack around the missing commitOnFocusLost when editing a cell stopped working in java-9.

这篇关于如何覆盖行为的已安装映射?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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