java-9 では、Skins はパブリック スコープになりましたが、Behaviors は不明のままですが、すべての入力バインディングに InputMap を使用するようになり、大幅に変更されました。
CellBehaviorBase は、次のようなマウス バインディングをインストールします。
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 は、動作を非公開でインストールします。
final private BehaviorBase behavior;
public TableCellSkin(TableCell control) {
super(control);
behavior = new TableCellBehavior(control);
....
}
要件は、mousePressed 動作を置き換えることです (jdk9 コンテキストで)。スーパーのフィールドをリフレクティブに取得し、そのすべてのマッピングを破棄し、カスタム動作をインストールするという考え方です。理解できない何らかの理由で、古いバインディングがまだアクティブであり (古いマッピングは空ですが!) 、新しいバインディングの前に呼び出されます。
以下は実行可能な例です。mousePressed へのマッピングは、何もしないように、特に super を呼び出さないように単純に実装されています。古いバインディングが動作していることを確認するために、CellBehaviorBase.mousePressed に条件付きデバッグ ブレークポイントを設定しました (Eclipse の場合):
System.out.println("mousePressed super");
new RuntimeException("whoIsCalling: " + getNode().getClass()).printStackTrace();
return false;
デバッグを実行し、任意のセルをクリックすると、出力は次のようになります。
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
最後の部分、つまり私のカスタム動作による出力のみが表示されることを期待しています。どういうわけか根本的にずれているように感じますが、それを釘付けにすることはできません. アイデア?
実行可能なコード (長くて申し訳ありませんが、ほとんどはボイラープレートです):
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());
}
編集
提案は、具体的な XXSkin の代わりに抽象的なスキン XXSkinBase から継承することです (その後、必要な動作を自由にインストールできます:-) は非常に合理的であり、最初のオプションにする必要があります。XX が TableCell である特定のケースでは、基本クラスに抽象パッケージ プライベート メソッドが含まれているため、現在のところそれは不可能です。また、抽象ベースを持たない XX もあります (fi ListCell など)。