LoginSignup
1
1

More than 5 years have passed since last update.

Action とショートカットを設定できる Label を作る

Posted at

概要

JavaFX の標準で用意されている Label には Action やキーボードショートカットが設定できません。文字を表示するためだけの Control なので当たり前といえばそうなのですが、どうしても設定したい事情(後述)があったので、どうにかならないかと調べてみたところ、自分で Label を継承して定義するしかないとわかりました。

実行環境

Java SE 1.8.0_102
OS Windows 10
Eclipse Mars 4.5.2
JFoenix 1.0.0

実装

2つクラスを作る必要があります。実装全体はリンク先(GitHub Repository)をご覧ください。

  1. Label を継承するクラス……MenuLabel
  2. 1 の Skin を定義するクラス……MenuLabelSkin

以下、ポイントを紹介します。

1. Label を継承するクラス……MenuLabel

2つのコピペ

コピペなので何も工夫しておらずコメントのしようもないです。

Skin の指定

設定されるキーボードショートカットを scene のキーボードショートカット一覧に登録するため、2のCustomスキンを指定します。

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

引数なしコンストラクタ

FXML からは引数なしコンストラクタが呼ばれるので、忘れずに定義しておきます。

    public MenuLabel() {
        this("");
    }

    public MenuLabel(final String text) {
        super(text);
        setOnMouseClicked(event -> getOnAction().handle(new ActionEvent()));
    }

2. 1 の Skin を定義するクラス……MenuLabelSkin

表示するテキストの調整(ショートカットの追加表示)と、キーボードショートカットの登録に必要でした。

下記のコードをご覧になるとわかる通り、Skin を設定する label オブジェクトが Accelerator(キーボードショートカット)を持っている場合は表示テキストにショートカットを追加し、持っているショートカットを scene オブジェクトの Accelerators という Map に登録します。

キーボードショートカット関連の処理
if (label.getAccelerator() == null) {
    return;
}

label.setText(label.getText() + "\t|\t" + label.getAccelerator().getDisplayText());
getSkinnable().getScene().getAccelerators()
             .put(label.getAccelerator(), () -> label.getOnAction().handle(new ActionEvent()));

ショートカットの表示は、 MenuItem のコードを辿っていくとよりきれいに表示させる実装方法が見つかったのですが、今回やった限りでは上手くいかなかったので、上記の実装で妥協しています。

おもちゃ

今回作成した MenuLabel を使ったおもちゃのスクリーンショットは下記の通りです。

スクリーンショット

screenshot1.png

起動直後はこんな感じで、Hello の領域をクリックするか、 Ctrl+H を入力すると

screenshot2.png

このようにポップアップが出ます。

このやり方を知っていれば、どんな Control にも Action とキーボードショートカットを設定できるようになります。どんな時に役立つのかはさっぱりわかりません。

なぜこんなものを作ってしまったのか ~ どうしても設定したい事情

普通に考えたら Button や MenuItem を使えばよいので、今回やったことはまったく無意味に思えるでしょう。

JavaFX で Material design を実装するためのライブラリで JFoenix というものがあります。先ほどのサンプルアプリケーションで使ったポップアップはこのライブラリの JFXSnackbar というコンポーネントを使いました。この JFoenix には ListView を拡張した JFXListView というものがありまして、これは Labeled を継承した Control を要素に用いることができます。これと JFXTabPane(TabPane の拡張)を組み合わせて、 Material designっぽい Menu を作ってみようと考えて、実装してみたところ、2つ問題が発生しました。そう、Action を設定できないこと、キーボードショートカットを設定できないことです。Menu の代替として使うにはその2つが必須でした。

Action だけなら Button を使えば、見た目はおかしいですが何とか実現できます。しかし Button はキーボードショートカットを設定できないので、それであれば Label を拡張してしまおう、という判断に至りました。

screenshot3.png

備考

この JFXTabPane を、例えば Drawer の下に入れた(隠した)場合、起動直後はキーボードショートカットが使えません。どうやら Skin オブジェクトの初期化はそのコンポーネントが最初に表示されたタイミングで実行されるようです。それでは Menu の代替として使えることにならないので、対策を考えてみましたが、どうもなさそうでしたので、手動で JFXTabPane が持っている MenuLabel を全件調べて Accelerator を put する、という方法を採りました。

@FXML
private TabPane menuTabs;

......

final ObservableMap<KeyCombination, Runnable> accelerators
            = this.stage.getScene().getAccelerators();
menuTabs.getTabs().forEach(tab -> {
        @SuppressWarnings("unchecked")
        final JFXListView<ActionLabel> labels = (JFXListView<ActionLabel>) tab.getContent();
        labels.getItems().forEach(l -> {
            if (l.getAccelerator() == null) {
                return;
            }
            accelerators.put(l.getAccelerator(), () -> l.getOnAction().handle(new ActionEvent()));
        });
});

リンク

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1