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 } } }
入れ子のメニューも簡単です。
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 } } }
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に追加しておく ] } }
このスクリプトでは、赤い四角を右クリックすると、ポップアップメニューを表示します。
しかし、実行するとメニューの位置がおかしいことが分ります。
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);
しかし、これでもまだ問題があります。
今の仕様ではノードに対する位置を指定する必要があります。
たとえば、VPos.BOTTOM で指定している時に、ノードが画面の下の方にあり、ポップアップメニューを表示するための十分な領域がない場合です。
この場合、ポップアップメニューがノードの上部に表示するようにしたいのですが、そうはなりません。中途半端に上にずれて表示されます。
このようにノードに対する位置を指定する方法は問題が多すぎ。JPopupMenu のように位置だけを指定するようにした方がいいと思いますね。
ということで、現状のポップアップメニューはまったく使えないものになっています。どうにかならないかなぁ。