ホームページ>開発ツール>Xojo / Real Studio Trial and Error・附録・通知センター機能をクラス化する
Xojo / Real Studio Trial and Error
目次
附録・通知センター機能をクラス化する
はじめに
通知センター機能をクラス化してみました。
なお検証には、Xojo 2015 Release 4.1を用いています。(Mac mini mid 2010 + OS X 10.11.4 El Capitan)
方針
クラス化にあたっては、以下の点が課題となりました。
1項については、クラス内にインスタンスを保持するプロパティを用意して、ここからインスタンス側のメソッドをコールすることとしました。
- Delegateメソッドは共有メソッドだが、パラメータの設定はインスタンス側で行うので、どう受け渡すか?
- 1項に関連して、インスタンスを複数個置いた場合、どのインスタンスに処理を投げるかの判断が必要になる。
- パラメータの設定をメソッドで行うとすると「このクラスを使うときはこれこれのメソッドを必ず実装してください」となってしまい、使い勝手が悪い。
- 初期化を通常のメソッドで行おうとすると、3項と同様の問題が発生する。
2項については、プロパティを配列化し、NSUserNotificationのUserInfoに添字を格納することで、通知を受け取った時に、どのインスタンスに渡せばいいかを判定できるようにしました。
3項については、メソッドでなく、イベントを使用することとしました。イベントには以下のメリットがあります
(1) パラメータを指定しなくていい場合は、何もしなくていい。(イベントはパスされ、戻り値はデフォルトの値(今回のケースではfalse)が適用される。)
(2) パラメータを指定する場合も、「イベントハンドラ...」メニューから選んで設定できるので、名前や引数を正確に覚えておかなくていい。
4項については、Constructorを使うこととしました。Constructorはインスタンス生成時に自動的に呼ばれるため、ユーザが呼び出す必要がなくなります。
Xojoでの実装
実行してみたところ、通知が表示されることを確認しました。
- Xojoで新規プロジェクトを作成
- 新規クラスを作成(名前は、ここでは「NotificationCenter」とした。)
- 以下をNotificationCenterのイベント定義(Event Definitions)に追加
注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)イベント名: ShouldPresentNotification 引数: id As Ptr, SEL As CString, center As Ptr, notification As Ptr 戻り値型: Boolean
- 以下をNotificationCenterのメソッドに追加
メソッド名: Constructor 引数: なし // 未設定なら if not DelegateFlg then // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr // Delegateを設定するための仕込み 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 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myNSUserNotificationCenterDelegate", 0) if not class_addMethod (newClassId, NSSelectorFromString("userNotificationCenter:shouldPresentNotification:"), AddressOf ySharedShouldPresentNotification, "B@:@@") then msgBox "error." return end if objc_registerClassPair newClassId 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)) // defaultUserNotificationCenterを取得して保持 NCenter = NSClassFromString("NSUserNotificationCenter") Declare Function defaultUserNotificationCenter Lib "Cocoa" Selector "defaultUserNotificationCenter" (receiver As Ptr) As Ptr NCenter = defaultUserNotificationCenter(NCenter) // Delegateの設定 Declare Sub setDelegate Lib "Cocoa" selector "setDelegate:" (receiver As Ptr, id As Ptr) setDelegate NCenter, delegateId // 設定済フラグ・オン DelegateFlg = true end if
- 以下をNotificationCenterのメソッドに追加
メソッド名: NotifyAfterSeconds 引数: title As String, info As String, ival As Integer 戻り値型: なし // インスタンスを保持(イベントのレイズはインスタンスメソッドからしかできないが、インスタンスメソッドはクラスから直接呼び出せないため、確保しておく) Inst.Append self // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // UserNotificationの初期化 Dim notification As Ptr = NSClassFromString("NSUserNotification") Declare Function notificationAlloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr notification = notificationAlloc(notification) Declare Function notificationInit Lib "Cocoa" Selector "init" (receiver As Ptr) As Ptr notification = notificationInit(notification) // Titleのセット Declare Sub setTitle Lib "Cocoa" Selector "setTitle:" (receiver As Ptr, id As CFStringRef) setTitle(notification, title) // InformativeTextのセット Declare Sub setInformativeText Lib "Cocoa" Selector "setInformativeText:" (receiver As Ptr, id As CFStringRef) setInformativeText(notification, info) // DeliveryDateのセット(ここでは指定秒数後の時刻をセットしている) Dim date1 As Ptr = NSClassFromString("NSDate") Declare Function date Lib "Cocoa" Selector "date" (receiver As Ptr) As Ptr date1 = date(date1) Dim date2 As Ptr = NSClassFromString("NSDate") Declare Function dateWithTimeInterval Lib "Cocoa" Selector "dateWithTimeInterval:sinceDate:" (receiver As Ptr, id1 As Double, id2 As Ptr) As Ptr date2 = dateWithTimeInterval(date2, ival, date1) Declare Sub setDeliveryDate Lib "Cocoa" Selector "setDeliveryDate:" (receiver As Ptr, id As Ptr) setDeliveryDate(notification, date2) // SoundNameのセット Declare Sub setSoundName Lib "Cocoa" Selector "setSoundName:" (receiver As Ptr, id As CFStringRef) setSoundName(notification, "DefaultSoundName") // UserInfoのセット(インスタンスを格納した配列の添字を保持するために使用) Dim dic As Ptr = NSClassFromString("NSMutableDictionary") // クラスメソッドなので、まずNSMutableDictionaryクラスを取得 Declare Function getDictionary Lib "Cocoa" Selector "dictionary" (receiver As Ptr) As Ptr // Return NSMutableDictionary* dic = getDictionary(dic) Declare Sub addObjectForKey Lib "Cocoa" Selector "setObject:forKey:" (receiver As Ptr, obj As CFStringRef, key As CFStringRef) addObjectForKey(dic, Str(Ubound(Inst)), "instNo") // 配列の添字をセット(冒頭で配列にAppendしているので、最後が当該インスタンス) Declare Sub setUserInfo Lib "Cocoa" Selector "setUserInfo:" (receiver As Ptr, id As Ptr) setUserInfo(notification, dic) // 通知の発行 Declare Sub scheduleNotification Lib "Cocoa" Selector "scheduleNotification:" (receiver As Ptr, id As Ptr) scheduleNotification(NCenter, notification)
- 以下をNotificationCenterのメソッドに追加
注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)メソッド名: yShouldPresentNotification 引数: id As Ptr, SEL As CString, center As Ptr, notification As Ptr 戻り値型: Boolean // イベントをレイズ return RaiseEvent ShouldPresentNotification(id, sel, center, notification)
- 以下をNotificationCenterの共有メソッド(Shared Methods)に追加
注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)メソッド名: ySharedShouldPresentNotification 引数: id As Ptr, SEL As CString, center As Ptr, notification As Ptr 戻り値型: Boolean // notificationのuserInfoからインスタンスを格納した配列の添字を取得 Declare Function userInfo Lib "Cocoa" Selector "userInfo" (receiver As Ptr) As Ptr Dim dic As Ptr = userInfo(notification) Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As CFStringRef Dim noStr As String = objectForKey(dic, "instNo") // イベントをレイズするため、通知を発行したインスタンスのメソッドを起動 return Inst(Val(noStr)).yShouldPresentNotification(id, sel, center, notification)
- 以下をNotificationCenterの共有プロパティ(Shared Properties)に追加
名前: DelegateFlg データ型: Boolean 標準値: なし
- 以下をNotificationCenterの共有プロパティ(Shared Properties)に追加
名前: Inst(-1) データ型: NotificationCenter 標準値: なし
- 以下をNotificationCenterの共有プロパティ(Shared Properties)に追加
名前: NCenter データ型: Ptr 標準値: なし
- Xojoプロジェクトウィンドウ>ナビゲーターのWindow1をクリックし、NotificationCenterをレイアウトエディタにドラッグ&ドロップ(2回)
(レイアウトエディタ下部とナビゲーターのWindow1>Controlsに、NotificationCenterクラスのインスタンス(NotificationCenter1と2)が生成される)
- 以下をNotificationCenter1と2のShouldPresentNotificationイベントに記述
return true // trueを返すことで、自身が最前面にあってもバナー表示が可能に
- Window1にPushButtonを置き、以下をActionイベントに記述
// NotificationCenter1に対し、タイトルを"Hello World 1"、インフォメーションを"my message 1"、インターバルタイムを3秒後に設定 NotificationCenter1.NotifyAfterSeconds("Hello World 1","my message 1",3) // NotificationCenter2に対し、タイトルを"Hello World 2"、インフォメーションを"my message 2"、インターバルタイムを10秒後に設定 NotificationCenter2.NotifyAfterSeconds("Hello World 2","my message 2",10)
おわりに
Delegateのための、Objective-CのランタイムAPIを用いた動的クラス生成は、NSUserNotificationCenterに対して行うものなので、本来一回行えばいいものです。
ですが、現状Constructor内に置いているために、インスタンス生成時に繰り返し呼ばれることになり、それをフラグで抑止するという、あまり筋がいいとは言えない実装になっています。
かといって、他に(自動的に起動される)適当なイベントやメソッドが思いつかないこともあり、継続調査となりそうです。
macoslibは、class_addMethodの戻り値がfalseの場合は(既に作成済と判断して?)何もしない、っぽい?(ざっと眺めただけなので、不正確かも…)あと、UserInfoに配列の添字ではなく、インスタンスまたはメソッドそれ自身を登録できればよりシンプルになるのですが、方法がわかりません。これも今後の課題となりそうです。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
更新履歴
2018.07.30 Xojoでの実装、の3,6,7項を改訂
2016.05.14 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]