descjop で遊ぼう day 14 : アプリケーションメニューを作ろう
さて、今日からはメニュー周りの解説を書いていきます。
デスクトップアプリケーションでは、ウインドウ内の操作以外にもメニューを使った操作を頻繁に使います。
ただ、アプリケーションのメニューについては、WindowsとOSXで考え方が異なります。
デスクトップアプリケーションのメニューについて考える
Windowsでは、アプリケーションのウインドウ上部にメニューがつきます。
ただOSXでは、デスクトップの上部にメニューバーが常に表示されていて、現在利用中のアプリケーションのメニューが反映されます。
また、アプリケーションのライフサイクルについても考え方が違います。
Windowsだとウインドウを閉じるとアプリケーションは終了してしまいますが、OSXではウインドウを閉じてもアプリケーションは終了しません。(まあ、終了するものもありますが)
メニューをつくる際はその辺りの違いも考えておきましょう。
まずは簡単なメニューを実装してみましょう。
今回つくるアプリケーションメニュー
今回はとりあえず、OSX用のアプリを作っていきます。
Windowsは、一旦はサポート外です(動くかもしれませんが)
機能については以下のとおりとします。
- メニューは、アプリケーション名のメニューとHelpのみ
- アプリケーション名のメニューでは、アプリケーションの終了ができる
- Helpメニューは、OSXの標準のヘルプメニューの機能と、About this app的なものがある
つまりメニューは2個です。
メニュー追加の手順
Electronにおけるアプリケーションメニューは、メニューの組み立て用のmenu
モジュールと、個々のメニューを構成するmenu-item
モジュールで作成します。
個々のメニューは入れ子構造に出来、下位階層のメニューはサブメニューと呼ばれます。
Electronのmenu
モジュールで扱えるメニューは、アプリケーションメニューとコンテキストメニューがあります。
今回は、アプリケーションメニューのみ作成します。
プロジェクトの作成
今まで作ってきたプロジェクトは一旦置いておいて、新しいプロジェクトを作成します。
任意のディレクトリで下記コマンドを実行します。
$ lein new descjop hello_menu +om
プロジェクトの作成が終わったので、プロジェクトディレクトリに移動しておきます。
src/hello_menu/core.clj
を今回は編集していきます。
まずは、OSの判定をとっておきましょう。
OSXかどうかの判定をとっておく
デスクトップアプリのメニューに対する考え方は、OSXだけ異なるのでOSXかどうかだけ判定します。
実は判定ロジック自体は、descjopのプロジェクトテンプレートに入っているのでこれを利用して下記のようにします。
(def is-mac (= (.-platform nodejs/process) "darwin"))
これでis-mac
がtrue
かfalse
かどうかで判定できるようになりました。
各種モジュールの読み込みをする
今回新たに使うモジュールは、shell
とmenu
です。
shell
は、ブラウザを開くのに使います。
メニュー関係のモジュールは、menu
とmenu-item
がありますが、今回はメニューのテンプレートから一気に作成するので、menu
しか使いません。
ということで、下記コードをis-mac
の下に書きます。
(def Shell (nodejs/require "shell"))
(def ElectronMenu (nodejs/require "menu"))
続いて、app
からアプリケーション名を取得しておきましょう。
OSXのメニューのアプリケーション名として使います。
下記コードを、ElectronMenu
の定義の下に書きます。
(def app-name (.getName app))
続いて、メニューの構成を記述していきます。

メニューの構成を決める
Electronのアプリケーションメニューの雛形は、メニューごとのマップが連続したデータとして作ることが出来ます。
ただ、OSXのときとWindows/Linuxのときはメニューを分けたいので、下記のように分岐させてみました。
(def menu-template
"メニュー構成"
(if is-mac
[{:label app-name
:submenu [{:label "Quit."
:accelerator "Command+Q"
:click (fn [] (.quit app))}]}
{:label "Help"
:role "help"
:submenu [{:label "About this mac app"
:accelerator "Command+H"
:click (fn [] (.openExternal Shell "http://descjop.org/"))}]}]
[{:label "Help"
:submenu [{:label "About this windows/linux app"
:accelerator "Control+H"
:click (fn [] (.openExternal Shell "http://descjop.org/"))}]}]))
アプリケーションの大メニューは、
{:label "大メニューのラベル名"
:submenu [サブメニュー]}
という風にキーワードと値のペアで記述していきます。
:label
は、大メニュー、サブメニュー共に指定をします。これが表示される名前になります。:submenu
は、メニューを開いたときに表示される子メニューで、サブメニューと呼ばれます。ここには大メニューのような形でマップを入れていくことが出来ます。:accelerator
というのもコードで出て来ますが、これはショートカットです。"Command+Q"
のように指定します。ちなみに、指定しているとメニューを展開したときにショートカットのヒントも表示されます。:click
というキーもあるのがわかると思いますが、これはメニューを選んだときに実行される関数を指定します。今回は、ブラウザでhttp://descjop.org/
を開くようにしています。ここで、shell
モジュールを使っています。:role
というのも指定しています。ロールを指定するとメニューにあらかじめ標準のアプリケーションに指定されている役割をあてることができます。ここでは:role "help"
を指定していますが、これはヘルプという役割のメニューを設定しているということです。ヘルプの役割を指定すると、メニューを開いたときに他のアプリケーションにあるような検索窓も表示されます。
その他のアプリケーションのロールについては下記があります。
- about - Map to the orderFrontStandardAboutPanel action
- hide - Map to the hide action
- hideothers - Map to the hideOtherApplications action
- unhide - Map to the unhideAllApplications action
- front - Map to the arrangeInFront action
- window - The submenu is a "Window" menu
- help - The submenu is a "Help" menu
- services - The submenu is a "Services" menu
メニューをアプリケーションに反映させる
続いて、定義したメニューをアプリケーションに反映させましょう。
-main
関数の(.on app "ready" (fn [] 処理))
にあるready
イベント内に下記を記述します。
;; menu build
(.setApplicationMenu ElectronMenu
(.buildFromTemplate ElectronMenu (clj->js menu-template)))
その結果、-main
関数は下記のようになります。
(defn -main []
(.start crash-reporter)
;; error listener
(.on nodejs/process "error"
(fn [err] (.log js/console err)))
;; window all closed listener
(.on app "window-all-closed"
(fn [] (if (not= (.-platform nodejs/process) "darwin")
(.quit app))))
;; ready listener
(.on app "ready"
(fn []
(reset! *win* (BrowserWindow. (clj->js {:width 800 :height 600})))
;; when no optimize comment out
(.loadUrl @*win* (str "file://" (.resolve path (js* "__dirname") "../index.html")))
;; when no optimize uncomment
;; (.loadUrl @*win* (str "file://" (.resolve path (js* "__dirname") "../../../index.html")))
;; menu build
(.setApplicationMenu ElectronMenu
(.buildFromTemplate ElectronMenu (clj->js menu-template)))
(.on @*win* "closed" (fn [] (reset! *win* nil))))))

実行してみよう
これでメニューが追加されるはずなので、コンパイルをして実行してみましょう。
メニューが2つ表示されていれば成功です。
Colophon
- 編集長
- Greative GK. 原一浩 ( kara_d )
- 製版システム
- Clojure / Compojure / Ring / Enlive / markdown-clj / Jetty / MySQL
- Share this magazine!
- Follow designudge
- Follow @designudge