Menu と PupupMenu

JavaFX 1.3 では API に待望のメニューが加わりました。ただし、preview ということなので、あくまでもお試しです。

Menu

Menu というか、MenuBar は使うのは簡単です。Swing の JMenuBar とそれほど変わりません。

ただ、JFrame#setJMenuBar のようなメソッドやそれに相当するプロパティもないので、普通のコンポーネントと同様に配置します。

var bar = MenuBar {
    menus: [
        Menu {
            text: "Menu1"
            items: for (item in ["Item1", "Item2"]) {
                MenuItem {
                    text: item
                }
            }
        },
        Menu {
            text: "Menu2"
            items: for (item in ["Item3", "Item4"]) {
                MenuItem {
                    text: item
                }
            }
        },
    ]
};
 
Stage {
    title: "MenuBar Sample"
    scene: Scene {
        width: 200 height: 200
        content: VBox {
            content: bar
        }
    }
}

f:id:skrb:20100617225547j:image

入れ子のメニューも簡単です。

Menu の items に Menu を加えれば、メニューが入れ子になります。

var menu3 = Menu {
    text: "Menu3"
    items: for (item in ["Item31", "Item32"]) {
        MenuItem {
            text: item
        }
    }
}

var bar = MenuBar {
    menus: [
        Menu {
            text: "Menu1"
            items: [
                MenuItem {
                    text: "Item1"
                },
                menu3
            ]
        },
        Menu {
            text: "Menu2"
            items: for (item in ["Item3", "Item4"]) {
                MenuItem {
                    text: item
                }
            }
        },
    ]
};
 
Stage {
    title: "MenuBar Sample"
    scene: Scene {
        width: 200 height: 200
        content: VBox {
            content: bar
        }

    }
}

f:id:skrb:20100617225714j:image

PopupMenu

Menu は簡単に使えたんですけど、PopupMenu はちょっとくせがあります。

Swing でポップアップメニューを表示する場合、コンテナに追加する必要はなく、必要に応じてJPopupMenu#show をコールするだけでした。


しかし、JavaFX では事前に PopupMenu をコンテナに貼っておく必要があります。これはかなりうざいです。

また、ポップアップメニューを表示するがどうかを調べるための、MouseEvent の popupTrigger プロパティは onMouseReleased の時しか有効にならないようです。

Swing では Clicked でも使用できるので、この点は注意が必要です。

では、次のスクリプトを試してみます。

var popup: PopupMenu = PopupMenu {
    items: for (item in  ["Item1", "Item2"]) {
        MenuItem {
            text: item
        }
    }
};

// ポップアップメニューを表示するノード
var node: Node;

Stage {
    title: "Popumenu Sample"
    scene: Scene {
        width: 300 height: 300
        content: [
            node = Rectangle {
                x: 50 y: 50 width: 100 height: 100
                fill: Color.RED
                onMouseReleased: function (event: MouseEvent): Void {
                    if (event.popupTrigger) { // pupupTriggerが聞くのはReleasedの時だけ?
                        // ポップアップメニューの表示 
                        popup.show(node, HPos.RIGHT, VPos.BOTTOM, 0, 0);
                    }
                }
            },
            popup // 表示しなくても、contentに追加しておく
        ]
    }
}

このスクリプトでは、赤い四角を右クリックすると、ポップアップメニューを表示します。

しかし、実行するとメニューの位置がおかしいことが分ります。

f:id:skrb:20100617230604p:image

PopuMenu#show 関数の第1引数はポップアップメニューを表示するノード、第2、第3引数がノードに対するポップアップメニューの表示位置を指定します。第5, 第6引数は位置の調整に使います。

ここでは、ノードの右下に表示するようにしています。実行してみると、なんとほんとに四角の右下にポップアップメニューが表示されてしまいます。

しかし、この位置ではマウスカーソルの位置と乖離してしまいます。これは絶対に変!!!

しかたないので、第5, 第6引数で調整することを考えます。

ここでも、2つの問題があります。

  • MosueEvent の x, y はコンテナに対する座標を返す
  • ノードの位置を表すプロパティがない

1 つめのマウスイベントの x と y がコンテナの座標を返すというのは絶対にバグだと思います。仕様だとしたら、すごい変!!

ノードの位置は Node クラスに x, y というプロパティがないためです。JavaFX ではtranslateX と layoutX があるので、x は表しにくいとは思いますが、レイアウトをした結果の位置を read-only で x と y に代入するようにすればいいと思います。

ただ、すでに Rectangle クラスのように x と y があるクラスもあるので、プロパティ名は考えないといけないと思いますけど。

で、今回の場合はレイアウトはしておらず、しかも Rectangle なのでノードの位置はすぐに分ります。なので、popup.show を次のように変更すれば、マウスカーソルの直下にポップアップメニューを表示します。

        // node の位置によって位置を修正する
        // 50 は scene における node の位置
        popup.show(node, HPos.LEFT, VPos.TOP,
                   event.x - 50 + popup.width,
                   event.y - 50 + popup.height);

f:id:skrb:20100617230155j:image

しかし、これでもまだ問題があります。

今の仕様ではノードに対する位置を指定する必要があります。

たとえば、VPos.BOTTOM で指定している時に、ノードが画面の下の方にあり、ポップアップメニューを表示するための十分な領域がない場合です。

この場合、ポップアップメニューがノードの上部に表示するようにしたいのですが、そうはなりません。中途半端に上にずれて表示されます。

f:id:skrb:20100617230546j:image

このようにノードに対する位置を指定する方法は問題が多すぎ。JPopupMenu のように位置だけを指定するようにした方がいいと思いますね。

ということで、現状のポップアップメニューはまったく使えないものになっています。どうにかならないかなぁ。