ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでツールバーを実装する
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでツールバーを実装する
はじめに
以下は、Xojo Cocoaビルドについての話題です。
Xojoは標準でツールバーを用意していますが、以下の制約があります。
その点、Cocoaのツールバーは全て対応しています。なので、使えるか、調べてみました。
- 配置できるツールバーアイテムが限られており、セグメントコントロールやサーチフィールド等に対応していない。
- カスタマイズ(ダイアログ)機能に対応していない。
- 表示方法(非表示、アイコンとテキスト、小さいアイコン等)を実行時に選択できない。
なお検証には、Xojo 2014 Release 3.1を用いています。(Mac mini mid 2010 + OS X 10.10.5 Yosemite)
方針
実装は、ツールバーと、その上に置くツールバーアイテム(以下、アイテム)の双方それぞれについて行います。
まずはツールバーですが、アイテムの登録はDelegateメソッドで行う必要があります。
そのため、以前の通知センターの時と同じく、Objective-CのランタイムAPIを用いた動的クラス生成を用いることとしました。
今回は、以下のDelegateメソッドを対象とします。
- toolbarDefaultItemIdentifiers:(デフォルトのアイテムの並びリストを作成)
- toolbarAllowedItemIdentifiers:(カスタマイズダイアログに表示されるアイテムの並びリストを作成)
- toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:(ユーザ定義アイテムの生成)
次にアイテムですが、こちらはDelegateは不要ですが、Target/Actionの設定が必要となります。
例によってmacoslibを紐解いてみたところ、基本的にはDelegateと同じ、動的クラス生成を使えばいいことが分かりました。
ただし、Delegateとは異なり、登録するのはフレームワークから提供される予約語ではなく、自分で定義(以下の実装では「action:」としている)したものになります。
共有メソッドについて
Xojoの共有メソッド(Shared Methods)は、Objective-Cでいうクラスメソッドのことのようです。
macoslibは動的クラス生成を、共有メソッドで実装しています。
当初は汎用化が目的かと思っていましたが、クラスを作るのはクラスメソッドで、と言われればそうかなという気もします。
また、実験してみて分かったことですが、ツールバーのDelegateメソッドを通常のメソッドで実装すると、起動時にエラーが発生してしまいます。
今回のケースでエラーが発生するのはtoolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:メソッドのみで、他は問題ありません。一方、共有メソッドで実装すれば、問題なく動作します。
どうもメソッドかオブジェクトが作成前に参照されているような感触です。(デバッガごと落ちてしまうので詳細不明。なので、あくまで感触です。)
前回の通知センターでは通常のメソッドで実装しても特に問題はありませんでしたが、これはたまたま動作した、と考えるべきかもしれません。このようなことから、以下の実装では、動的クラス生成に共有メソッドを使っています。
追記:登録したXojo側のメソッドには、2個のPtr型引数を先頭に追加する必要があります。前回は引数を参照しなかったので、なくても実害はありませんでしたが、今回は参照しますので、忘れないようにします。
参考サイト(3):Objective-C - Blockで動的にメソッドを実装できるクラスの作り方 - Qiita
Xojoでの実装
今回は実験として、標準的なアイテム(カラー、フォント、プリント、スペース、伸縮自在のスペース)とユーザ定義アイテム(保存)を配置してみます。
なお、実装にあたっては、以下のサイトを参考にさせて頂きました。
参考サイト(1):Introduction to Toolbars
参考サイト(2):NSToolbar basics | No pain no gain
- Xojoで新規プロジェクトを作成
- ユーザ定義アイテム用のアイコン画像ファイル(ここではsave.pngとしている)を、プロジェクトに追加
- 以下をWindow1のOpenイベントに記述
// Shared MethodsからMethodsにアクセスするために、自身をインスタンスとして登録 Window1Instance = self // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // ツールバーの初期化 Dim toolbar1 As Ptr = NSClassFromString("NSToolbar") Declare Function toolbarAlloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr toolbar1 = toolbarAlloc(toolbar1) Declare Function toolbarInit Lib "Cocoa" Selector "initWithIdentifier:" (receiver As Ptr, identifier As CFStringRef) As Ptr toolbar1 = toolbarInit(toolbar1, "MySampleToolbar") // 各種パラメータの設定 Declare Sub setAllowsUserCustomization Lib "Cocoa" Selector "setAllowsUserCustomization:" (receiver As Ptr, value As Boolean) setAllowsUserCustomization(toolbar1, true) Declare Sub setAutosavesConfiguration Lib "Cocoa" Selector "setAutosavesConfiguration:" (receiver As Ptr, value As Boolean) setAutosavesConfiguration(toolbar1, true) Declare Sub setDisplayMode Lib "Cocoa" Selector "setDisplayMode:" (receiver As Ptr, value As Integer) setDisplayMode(toolbar1, 1) // 0=Default, 1=Icon & Text, 2=Icon Only, 3=Text Only // Delegateの設定 Declare Sub setDelegate Lib "Cocoa" Selector "setDelegate:" (receiver As Ptr, id As Ptr) setDelegate toolbar1, makeDelegate() // Delegateの設定 // ウィンドウにツールバーを登録 Declare Sub setToolbar Lib "Cocoa" Selector "setToolbar:" (receiver As WindowPtr, toolbar As Ptr) setToolbar(self, toolbar1) // 後処理 Declare Sub release Lib "Cocoa" Selector "release" (toolbar As Ptr) release(toolbar1)
- 以下をWindow1のメソッドに追加
メソッド名: ToolbarItemClicked 引数: sender As Ptr // アイテムの種別がSaveなら必要な処理を行う Declare Function label Lib "Cocoa" Selector "label" (receiver As Ptr) As CFStringRef if label(sender) = "Save" then … end if
- 以下をWindow1の共有メソッド(Shared Methods)に追加
注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)メソッド名: actionEvent 引数: id As Ptr, SEL As CString, sender As Ptr // Sharedでないメソッドに渡す Window1Instance.ToolbarItemClicked(sender)
- 以下をWindow1の共有メソッド(Shared Methods)に追加
メソッド名: makeDelegate 戻り値型: Ptr // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr // Declare宣言 Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr) Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean // クラス名をmyNSToolbarDelegate(名前は任意。少なくとも今回のケースでは参照されない。)、メタクラス名をNSObjectにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myNSToolbarDelegate", 0) // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // Delegateの対象となるメソッドを追加(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:をXojo側で用意したtoolbarItemForItemIdentifierWillBeInsertedIntoToolbarメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:"), AddressOf toolbarItemForItemIdentifierWillBeInsertedIntoToolbar, "@@:@@c") then msgBox "error." return nil end if // Delegateの対象となるメソッドを追加(toolbarAllowedItemIdentifiers:をXojo側で用意したtoolbarAllowedItemIdentifiersメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("toolbarAllowedItemIdentifiers:"), AddressOf toolbarAllowedItemIdentifiers, "@@:@") then msgBox "error." return nil end if // Delegateの対象となるメソッドを追加(toolbarDefaultItemIdentifiers:をXojo側で用意したtoolbarDefaultItemIdentifiersメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("toolbarDefaultItemIdentifiers:"), AddressOf toolbarDefaultItemIdentifiers, "@@:@") then msgBox "error." return nil end if // Delegateの対象となるメソッドを追加(toolbarSelectableItemIdentifiers:をXojo側で用意したtoolbarSelectableItemIdentifiersメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("toolbarSelectableItemIdentifiers:"), AddressOf toolbarSelectableItemIdentifiers, "@@:@") then msgBox "error." return nil end if // 上記で生成したクラスのインスタンスを作成 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr Dim delegateId As Ptr = init(alloc(newClassId)) // インスタンスを返す return delegateId
- 以下をWindow1の共有メソッド(Shared Methods)に追加
注)初出時、既に生成済ならクラスを返すようになっていましたが、明らかに間違いで、生成済のインスタンスを返すようにしました。メソッド名: makeTarget 引数: name As String 戻り値型: Ptr // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr // Declare宣言 Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr) Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean // 既にインスタンス作成済なら、それを返す if TargetInstance <> nil then return TargetInstance end if // クラス名を引数のname(名前は任意だが、重複を避けるためにアイテムのIdentifierにしている。)、メタクラス名をNSObjectにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), name, 0) // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // Tarrgetに送られてきたActionの受け口となるメソッドを追加(action:をXojo側で用意したactionEventメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("action:"), AddressOf actionEvent, "@@:@") then msgBox "error." return nil end if // 上記で生成したクラスのインスタンスを作成 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr Dim targetId As Ptr = init(alloc(newClassId)) // インスタンスを保持 TargetInstance = targetId // インスタンスを返す return targetId
- 以下をWindow1の共有メソッド(Shared Methods)に追加
注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)メソッド名: toolbarAllowedItemIdentifiers 引数: id As Ptr, SEL As CString, toolbar As Ptr 戻り値型: Ptr // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim arr As Ptr = NSClassFromString("NSMutableArray") // クラスメソッドなので、まずNSMutableArrayクラスを取得 Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr // Return Array* arr=getArray(arr) Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As CFStringRef) addObject(arr, SaveToolbarItemIdentifier) // ユーザ定義アイテム addObject(arr, "NSToolbarShowColorsItem") addObject(arr, "NSToolbarShowFontsItem") addObject(arr, "NSToolbarPrintItem") addObject(arr, "NSToolbarSpaceItem") addObject(arr, "NSToolbarFlexibleSpaceItem") // リストを返す return arr
- 以下をWindow1の共有メソッド(Shared Methods)に追加
注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)メソッド名: toolbarDefaultItemIdentifiers 引数: id As Ptr, SEL As CString, toolbar As Ptr 戻り値型: Ptr // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim arr As Ptr = NSClassFromString("NSMutableArray") // クラスメソッドなので、まずNSMutableArrayクラスを取得 Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr // Return Array* arr=getArray(arr) Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As CFStringRef) addObject(arr, SaveToolbarItemIdentifier) // ユーザ定義アイテム addObject(arr, "NSToolbarSpaceItem") addObject(arr, "NSToolbarShowColorsItem") addObject(arr, "NSToolbarShowFontsItem") addObject(arr, "NSToolbarFlexibleSpaceItem") addObject(arr, "NSToolbarPrintItem") // リストを返す return arr
- 以下をWindow1の共有メソッド(Shared Methods)に追加
注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)メソッド名: toolbarItemForItemIdentifierWillBeInsertedIntoToolbar 引数: id As Ptr, SEL As CString, toolbar As Ptr, itemIdentifier As CFStringRef, flag As Boolean 戻り値型: Ptr // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr // アイテムの種別が「保存」なら生成する if itemIdentifier = SaveToolbarItemIdentifier then // アイテムの初期化 Dim toolbarItem1 As Ptr = NSClassFromString("NSToolbarItem") Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr toolbarItem1 = alloc(toolbarItem1) Declare Function initWithItemIdentifier Lib "Cocoa" Selector "initWithItemIdentifier:" (receiver As Ptr, identifier As CFStringRef) As Ptr toolbarItem1 = initWithItemIdentifier(toolbarItem1, itemIdentifier) // ラベル設定 Declare Sub setLabel Lib "Cocoa" Selector "setLabel:" (receiver As Ptr, label As CFStringRef) setLabel(toolbarItem1, "Save") Declare Sub setPaletteLabel Lib "Cocoa" Selector "setPaletteLabel:" (receiver As Ptr, label As CFStringRef) setPaletteLabel(toolbarItem1, "Save") // ツールチップ設定 Declare Sub setToolTip Lib "Cocoa" Selector "setToolTip:" (receiver As Ptr, text As CFStringRef) setToolTip(toolbarItem1, "Save Your Document") // アイコン設定 Dim bundle1 As Ptr = NSClassFromString("NSBundle") Declare Function mainBundle Lib "Cocoa" Selector "mainBundle" (receiver As Ptr) As Ptr bundle1 = mainBundle(bundle1) Declare Function pathForResource Lib "Cocoa" Selector "pathForResource:ofType:" (receiver As Ptr, path As CFStringRef, type As CFStringRef) As CFStringRef Dim filepath As String = pathForResource(bundle1,"save","png") // プロジェクトに追加したアイコン画像ファイルの名前 Dim image1 As Ptr = NSClassFromString("NSImage") image1 = alloc(image1) Declare Function initWithContentsOfFile Lib "Cocoa" Selector "initWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As Ptr image1 = initWithContentsOfFile(image1, filepath) Declare Sub setImage Lib "Cocoa" Selector "setImage:" (receiver As Ptr, image As Ptr) setImage(toolbarItem1, image1) // Target/Action設定 Declare Sub setTarget Lib "Cocoa" Selector "setTarget:" (receiver As Ptr, actionTarget As Ptr) setTarget(toolbarItem1, makeTarget(itemIdentifier)) // Targetの設定 Declare Sub setAction Lib "Cocoa" Selector "setAction:" (receiver As Ptr, actionEvent As Ptr) setAction(toolbarItem1, NSSelectorFromString("action:")) // Actionの設定 // 生成したアイテムを返す return toolbarItem1 else return nil end if
- 以下をWindow1の共有プロパティ(Shared Properties)に追加
プロパティ名: SaveToolbarItemIdentifier データ型: String 標準値: My Save Toolbar Item
- 以下をWindow1の共有プロパティ(Shared Properties)に追加
プロパティ名: TargetInstance データ型: Ptr 標準値: nil
- 以下をWindow1の共有プロパティ(Shared Properties)に追加
プロパティ名: Window1Instance データ型: Window1
実行してみたところ、ツールバーが表示されて、アイテム及びカスタマイズが機能することを確認しました。
おわりに
今回は仕組みを理解するために、極力シンプルな書き方を心懸けました。
(例えば、allocした変数の一部は然るべきタイミングでReleaseするべきと思われますが、そのためにはコードを工夫する必要があります。)
実用目的であれば、macoslibのように汎用化する(というか、そのまま利用させて頂く)ことを考えた方がいいでしょう。
ボタン以外のセグメントやサーチフィールドは、機会を見つけてトライしてみたいと思います。の配置については、こちらのページに纏めました。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):Introduction to Toolbars
参考サイト(2):NSToolbar basics | No pain no gain
参考サイト(3):Objective-C - Blockで動的にメソッドを実装できるクラスの作り方 - Qiita
更新履歴
2018.07.30 Xojoでの実装、の5,8,9,10項を改訂
2015.10.12 共有メソッドについて、に追記と参考サイト(3)を追加。
2015.10.08 Xojoでの実装 7項(makeTarget)のコードを修正して注を付記。
2015.10.08 TargetInstanceを共有プロパティに追加(12項)
2015.09.30 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]