Paepoi

Paepoi » PyObjC Tips » NSApplication

NSApplication

最終更新日 2019.04.18
NSApp を作る
NSApplication.sharedApplication()
を呼び出すと NSApp というグローバル変数に NSApplication のインスタンスが入ります。

#!/usr/bin/env python3

from AppKit import *

class AppMenu(NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        # command+Q で閉じるメニュー
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

# NSApp を作る
NSApplication.sharedApplication()
# command+Q で終了するメニューを入れる
NSApp.setMainMenu_(AppMenu.new())
# コレをしないと最前面に出てこない
NSApp.activateIgnoringOtherApps_(True)
# メインループを回す
NSApp.run()
ウインドウも何も無いアプリケーション。

.NET や GtkApplication や TApplication 等を知っている人はアレッと思うかも。
NSApplication は NSWindow を参照カウンタで管理なんかしません。
同じインスタンスで動いている NSWindow を keyWindow 等で見つけてくれるだけです。
でも終了時には全部まとめて破棄してくれる頼もしい存在です。

閉じるボタンで終了するサンプルコードばかりなので最初にコレを書いてみました。
macOS アプリは command+Q で終了しないとおかしいですよね。

NSWindow を作る
AppKit の一般的な動作をするウインドウを書いてみます。

#!/usr/bin/env python3

from AppKit import *

wins = []

class MyWindow(NSWindow):
    def init(self):
        objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
            NSMakeRect(0, 0, 320, 240),
            NSTitledWindowMask | NSClosableWindowMask |
            NSResizableWindowMask | NSMiniaturizableWindowMask,
            NSBackingStoreBuffered, False)
        # すでに Window がある場合はズラして配置
        if len(NSApp.windows()) == 1:
            self.center()
        else:
            point = NSApp.keyWindow().cascadeTopLeftFromPoint_(NSZeroPoint)
            self.setFrameTopLeftPoint_(point)
        # title はプロパティなので
        self.setTitle_('日本語')
        # 自身をデリゲートに
        self.setDelegate_(self)
        # 自分を戻す
        return self

    def windowWillClose_(self, sender):
        # オーバーライド、閉じるアクションでココに来る
        print('Close!')

class AppDelegate(NSObject):
    def applicationDidFinishLaunching_(self, notification):
        # オーバーライド、ウインドウは基本ココで作る
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        # ガベージコレクションされないようにどこかに保存する
        wins.append(window)

    def newWindow_(self, notification):
        # 自作メソッドの場合でもメニューではアンダーバーをコロンにする
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        wins.append(window)

    def closeWindow_(self, notification):
        NSApp.keyWindow().close()

class AppMenu(NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        # new menu
        item_new = NSMenuItem.new()
        item_new.initWithTitle_action_keyEquivalent_('New Window', 'newWindow:', 'n')
        menu_app.addItem_(item_new)
        # close menu
        item_close = NSMenuItem.new()
        item_close.initWithTitle_action_keyEquivalent_('Close Window', 'closeWindow:', 'w')
        menu_app.addItem_(item_close)
        # separator
        menu_app.addItem_(NSMenuItem.separatorItem())
        # quit menu
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

NSApplication.sharedApplication()
NSApp.setMainMenu_(AppMenu.new())
NSApp.setDelegate_(AppDelegate.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()
最小限、と思っていたけど長くなってしまった。

先ほどの NSApp にデリゲートを追加してウインドウを作るメニューを追加。
メソッド名はアンダーバーを使いますがメニューを作る時はコロンで指定なので注意。
デリゲートは NSApplicationDelegate を継承する必要は無い、ただの class でいい。

command+N で新規ウインドウ、command+W でウインドウを閉じる。
という macOS 標準ショートカットはこんな感じで実装します。
NSApp.keyWindow() で現在キー操作を受け付けるウインドウが得られます。

NSWindow を作ったらどこかに保存する。
先ほど書いたように NSApp は NSWindow を参照カウンタで管理なんかしません。
つまり自力で保持しないとガベージコレクションで破棄されてしまいます。

NSWindow は close() メソッドを呼ぶ、又は閉じるボタンで自動的に破棄される。
その時リストで保持した NSWindow は何もしなくていい。
ポインタアドレスが残るだけなので無視する、へたに破棄すると二重破棄でエラー。

継承した NSWindow はこんな感じで作ります。
必要に応じて NSRect と **mask を指定、他の引数は現在固定値です。
Interface Builder の nib から作る方法もあるけどココでは解説しません。
ズラして配置はどうせ必要になるから書いたけど説明は省きます。

NSWindowDelegate は自身を指定する。
別の class を作ってもいいけど無駄に class が多くなるより管理しやすい。
windowWillClose_ は閉じるボタンや command+W で届くメッセージのハンドラです。
stdout に Close! が表示されるのを確認ください。

Copyright(C) sasakima-nao All rights reserved 2002 --- 2020.