ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでファイルIOパネルを拡張する

 Xojo / Real Studio Trial and Error

CocoaのDeclareでファイルIOパネルを拡張する

目次
 はじめに

 以下は、Xojo Cocoaビルドについての話題です。

 Xojoは(当然ながら)標準でファイルIOパネルを持っていますが、パネルの汎用カスタマイズには対応していないので、調べてみました。

 なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + OS X 10.11.6 El Capitan)


 方針

 ファイルIOパネルの拡張機能は、Xojoネイティブにもあることはあって、言語リファレンスのSaveAsDialogの項にサンプルも載っているのですが、(1)保存のみ、(2)用途が限定的、(3)パラメータに直接アクセスできない、といった制約があります。

 そこで、パネルにはNSSavePanel/NSOpenPanel、拡張にはaccessoryViewを使うことにします。
 コントロール(CheckBoxやPopupメニュー等)をベースとなるビューに置き、それをaccessoryViewとしてパネルにセットすることで、拡張ができます。

 ファイルIO用パネルには、印刷と同じく、モーダルダイアログ(以下、ダイアログ)とシートがあります。
 シートは(印刷同様)モードレスなので、ボタンのクリックは、パネルを表示するメソッドとは別の場所で受け取る必要があるのですが、ファイルIOの場合はセレクタではなく、completionHandlerとかBlocksとか呼ばれる方法で受け取ることになります。(セレクタを使うメソッドもあるのですが、既にDeprecatedになっています。)

 問題は、XojoでCompletionHandlerを処理する方法が見当たらないことです。(注:その後判明した方法についてはこちらのページを参照)
macoslibは、completionHandlerを使うメソッドは実装されているものの、nilが指定されていました。
 調べたところ、以下のトピックで、Joe R氏がプラグインを公開されていました。

 参考サイト(1):Obj-C Blocks as Callbacks - Xojo Forum

 このプラグインは、completionHandlerの処理内容をXojoのメソッドとして記述できるようにするものです。(Xcode - Cocoa系では、中身は呼び出し元に直接記述するのが作法?のようですが。)
プラグインはソースもドキュメントも公開されていないようで、パラメータの意味もよく分かっていません。
(追記:同梱のサンプルは、XojoのiOS Frameworkが持つiOSBlockのサンプルと同等であることから、本機能のmac版という位置付けなのかもしれません。また、サンプルの引数の並びはenumerateObjectsUsingBlock:に固有のもので、利用するBlocksに合わせる必要があるようです。)
今回は実験目的なので、同梱のサンプルと推測を元に構成していますが、実用目的で使いたい場合は、作者に連絡を取って確認した方がいいでしょう。
 これらを踏まえて今回は、(1)ダイアログ、(2)プラグインでシート、(3)(Deprecatedではあるものの、参考として)セレクタを使うシート、の3種類を試してみることにしました。
 それぞれの仕様は以下の通りとしました。

 共通
 ダイアログ形式
 シート形式:プラグイン使用
 シート形式:Deprecatedメソッド使用(参考)

 Xojoでの実装

 今回のサンプルは共通部分が多いため、三つ分をまとめて一つのプロジェクトとしています。
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成
  2. Window1にTextArea(TextArea1)を置く。
  3. Window1にPushButtonを6個(PushButton1〜6)置く。
  4. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // 処理種別(オープン=false)
      SaveFlg=false
      
      // AccessoryPanelクラスの生成(パネル種別、親ウィンドウ(モーダルはnil)、ボタンActionを受け取るメソッド、を引数で指定する)
      Dim aod As AccessoryPanel = new AccessoryPanel(1, nil, AddressOf ButtonClicked)
    End Sub
    
  5. 以下をPushButton2にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // 処理種別(保存=true)
      SaveFlg=true
      
      // AccessoryPanelクラスの生成(パネル種別、親ウィンドウ(モーダルはnil)、ボタンActionを受け取るメソッド、を引数で指定する)
      Dim asd As AccessoryPanel = new AccessoryPanel(2, nil, AddressOf ButtonClicked)
    End Sub
    
  6. 以下をPushButton3にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // 処理種別(オープン=false)
      SaveFlg=false
      
      // AccessoryPanelクラスの生成(パネル種別、親ウィンドウ(モーダルはnil)、ボタンActionを受け取るメソッド、を引数で指定する)
      Dim aos As AccessoryPanel = new AccessoryPanel(3, self, AddressOf ButtonClicked)
    End Sub
    
  7. 以下をPushButton4にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // 処理種別(保存=true)
      SaveFlg=true
      
      // AccessoryPanelクラスの生成(パネル種別、親ウィンドウ(モーダルはnil)、ボタンActionを受け取るメソッド、を引数で指定する)
      Dim ass As AccessoryPanel = new AccessoryPanel(4, Self, AddressOf ButtonClicked)
    End Sub
    
  8. 以下をPushButton5にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // 処理種別(オープン=false)
      SaveFlg=false
      
      // AccessoryPanelクラスの生成(パネル種別、親ウィンドウ(モーダルはnil)、ボタンActionを受け取るメソッド、を引数で指定する)
      Dim aos As AccessoryPanel = new AccessoryPanel(5, self, AddressOf ButtonClicked)
    End Sub
    
  9. 以下をPushButton6にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // 処理種別(保存=true)
      SaveFlg=true
      
      // AccessoryPanelクラスの生成(パネル種別、親ウィンドウ(モーダルはnil)、ボタンActionを受け取るメソッド、を引数で指定する)
      Dim ass As AccessoryPanel = new AccessoryPanel(6, Self, AddressOf ButtonClicked)
    End Sub
    
  10. 以下をWindow1にペースト
    Private Sub ButtonClicked(idx As Integer, path As String)
      // キャンセルボタンが押されたら戻る
      if idx=-1 and path="" then
        msgBox "Cancel"
        return
      end if
      
      'msgBox "OK = "+str(idx)+":"+path
      
      // ファイルの取得
      Dim f As FolderItem = GetFolderItem(path,3)
      if f=nil then
        msgBox "File not Opened. = "+path
        return
      end if
      if SaveFlg=false and f.Exists=false then
        msgBox "File not Exists. = "+path
        return
      end if
      
      // パネルで指定されたテキストエンコーディングをセット
      Dim te As TextEncoding
      select case idx
      case 0
        te=Encodings.UTF8
      case 1
        te=Encodings.UTF16
      case 2
        te=Encodings.ShiftJIS
      case 3
        te=GetTextEncoding(&h0920)  // EUC_JP
      end select
      
      // ファイルオープン/保存処理
      if SaveFlg then
        
        Dim t As TextOutputStream
        t=TextOutputStream.Create(f)
        t.Write(ConvertEncoding(TextArea1.Text,te))
        t.Close
        
      else
        
        Dim t As TextInputStream
        t=TextInputStream.Open(f)
        t.Encoding=te
        TextArea1.Text=t.ReadAll
        t.Close
        
      end if
    End Sub
    
  11. 以下をWindow1にペースト(できなければプロパティに、名前:SaveFlg、データ型:Boolean、を追加)
    Private Property SaveFlg as Boolean
    
  12. 新規クラスを作成(名前は、ここでは「AccessoryPanel」とした。)
  13. 以下をAccessoryPanelにペースト(できなければ移譲に、名前:ActionDelegate、引数:sender As Ptr、を追加)
    Private Sub ActionDelegate(sender As Ptr)
    
  14. 以下をAccessoryPanelにペースト
    Public Sub Constructor(type As Integer, win As Window, action As ActionDelegate)
      // インスタンス側でActionを受け取るメソッドを登録
      ActionHandler = action
      
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      if type=1 or type=3 or type=5 then
        
        // OpenPanelの生成
        Panel = NSClassFromString("NSOpenPanel")
        Declare Function openPanel Lib "Cocoa" selector "openPanel" (class_id As Ptr) As Ptr
        Panel = openPanel(Panel)
        
      else
        
        // SavePanelの生成
        Panel = NSClassFromString("NSSavePanel")
        Declare Function savePanel Lib "Cocoa" selector "savePanel" (class_id As Ptr) As Ptr
        Panel = savePanel(Panel)
        
      end if
      
      // AccessoryViewの生成
      Dim aview As Ptr = MakeAccessoryView()
      // AccessoryViewのセット
      Declare Sub setAccessoryView Lib "Cocoa" selector "setAccessoryView:" (class_id As Ptr, view As Ptr)
      setAccessoryView(Panel, aview)
      // 後処理
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      release(aview)
      
      // ファイルタイプ指定(拡張子がtxtのみ有効)
      Declare Sub setRequiredFileType Lib "Cocoa" selector "setRequiredFileType:" (class_id As Ptr, type As CFStringRef)
      setRequiredFileType(Panel, "txt")
      
      if type=1 or type=2 then
        
        // ダイアログ生成
        Declare Function runModal Lib "Cocoa" Selector "runModal" (receiver As Ptr) As Integer
        Dim rcode As Integer = runModal(Panel)
        // ボタンが押された後の処理(パラメータを抽出して、インスタンスに渡す)
        panelButtonClicked(rcode)
        
      elseif type=3 or type=4 then
        
        // シート生成(Blocksプラグイン)
        dim block as ptr = ObjCBlocks.CreateBlock( AddressOf didEndSaveSheetCH )  // プラグイン初期化
        Declare Sub beginSheetModalForWindow Lib "Cocoa" Selector "beginSheetModalForWindow:completionHandler:" (receiver As Ptr, win As Integer, block As Ptr)
        beginSheetModalForWindow(Panel, win.Handle, block)
        ObjCBlocks.ReleaseBlock( block )  // プラグイン後処理
        
      else
        
        // シート生成(Deprecatedメソッド)
        Declare Sub beginSheetForDirectory Lib "Cocoa" Selector "beginSheetForDirectory:file:modalForWindow:modalDelegate:didEndSelector:contextInfo:" (receiver As Ptr, dir As Ptr, file As Ptr, win As Integer, dlgt As Ptr, sel As Ptr, cntx As Ptr)
        beginSheetForDirectory(Panel, nil, nil, win.Handle, makeDelegate(), NSSelectorFromString("didEndSaveSheet:returnCode:conextInfo:"), nil)
        
      end if
    End Sub
    
  15. 以下をAccessoryPanelにペースト
    Private Function MakeAccessoryView() as Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      Declare Function initWithFrame Lib "Cocoa" Selector "initWithFrame:" (receiver As Ptr, identifier As NSRect) As Ptr
      
      // ポップアップメニューとラベルを置くベースviewを生成
      Dim aview As Ptr = NSClassFromString("NSView")
      aview = alloc(aview)
      aview = initWithFrame(aview, NSMakeRect(0,0,330,25))
      
      // ラベルを生成
      Dim label As Ptr = NSClassFromString("NSTextField")
      label = alloc(label)
      label = initWithFrame(label, NSMakeRect(0,0,160,22))
      Declare Sub setStringValue Lib "Cocoa" selector "setStringValue:" (class_id As Ptr, title As CFStringRef)
      setStringValue(label, "テキストエンコーディング:")  // ラベル文字列
      Declare Sub setDrawsBackground Lib "Cocoa" selector "setDrawsBackground:" (class_id As Ptr, flg As Boolean)
      setDrawsBackground(label, false)
      Declare Sub setBordered Lib "Cocoa" selector "setBordered:" (class_id As Ptr, flg As Boolean)
      setBordered(label, false)
      Declare Sub setEditable Lib "Cocoa" selector "setEditable:" (class_id As Ptr, flg As Boolean)
      setEditable(label, false)
      Declare Sub setSelectable Lib "Cocoa" selector "setSelectable:" (class_id As Ptr, flg As Boolean)
      setSelectable(label, false)
      Declare Sub setAlignment Lib "Cocoa" selector "setAlignment:" (class_id As Ptr, flg As Integer)
      setAlignment(label, 1)  // 右揃え
      
      // ポップアップメニューを生成
      Dim btn As Ptr = NSClassFromString("NSPopUpButton")
      btn = alloc(btn)
      btn = initWithFrame(btn, NSMakeRect(160,2,160,22))
      Declare Sub setMenu Lib "Cocoa" selector "setMenu:" (class_id As Ptr, menu As Ptr)
      Dim menu As Ptr = makeMenuPopup("AViewMenuIdentifier")
      setMenu(btn, menu)  // メニューを生成してセット
      
      // ポップアップメニューとラベルをベースのサブビューに設定
      Declare Sub addSubview Lib "Cocoa" selector "addSubview:" (class_id As Ptr, view As Ptr)
      addSubview(aview, label)
      addSubview(aview, btn)
      
      // 後処理
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      release(label)
      release(btn)
      release(menu)
      
      // ベースviewを返す
      return aview
    End Function
    
  16. 以下をAccessoryPanelにペースト
    Private Shared Sub actionEvent(id As Ptr, SEL As CString, sender As Ptr)
      // メニューのアクションを受ける。ここではアクションに対応する動作はないが、指定しないとメニューがDisableになる
    End Sub
    
    注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)
  17. 以下をAccessoryPanelにペースト
    Private Shared Sub didEndSaveSheet(id As Ptr, SEL As CString, sender As Ptr, rcode As Integer, info As Ptr)
      // 渡ってきたパネルのインスタンスをプロパティにセット
      Panel=sender
      
      // ボタンが押された後の処理(パラメータを抽出して、インスタンスに渡す)
      panelButtonClicked(rcode)
    End Sub
    
    注)SELの型をPtrとしていたが、CStringの方が相応しいので変更した。(未使用なので変更しなくても実害はなし。)(2018.07.30)
  18. 以下をAccessoryPanelにペースト
    Private Shared Sub didEndSaveSheetCH(obj as integer, idx as integer, byref stop as boolean)
      // ボタンが押された後の処理(パラメータを抽出して、インスタンスに渡す)
      panelButtonClicked(obj)
    End Sub
    
    Private Shared Sub didEndSaveSheetCH(rcode as integer)
      // ボタンが押された後の処理(パラメータを抽出して、インスタンスに渡す)
      panelButtonClicked(rcode)
    End Sub
    
    注)引数が実態に即していなかったので、書き換えました。(機能は同一です。)
  19. 以下をAccessoryPanelにペースト
    Private Shared Function makeDelegate() as Ptr
      // 既に作成済なら値を返す
      if DelegateID <> nil then
        return DelegateID
      end if
      
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      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
      
      // クラス名をmyClass1Delegate(名前は任意。少なくとも今回のケースでは参照されない。)、メタクラス名をNSObjectにして、生成
      Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "AccessoryDialogDelegate", 0)
      
      
      // ランタイムに登録(参照を可能とするため)
      objc_registerClassPair newClassId
      if not class_addMethod (newClassId, NSSelectorFromString("didEndSaveSheet:returnCode:conextInfo:"), AddressOf didEndSaveSheet, "v@:@i@") 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
      DelegateID = init(alloc(newClassId))
      
      // インスタンスを返す
      return DelegateID
    End Function
    
  20. 以下をAccessoryPanelにペースト
    Private Shared Function makeMenu(itemIdentifier As String, list() As String, tag0 As Integer) as Ptr
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
      Declare Function init Lib "Cocoa" Selector "init" (receiver As Ptr) As Ptr
      Declare Function initWithTitleActKey Lib "Cocoa" Selector "initWithTitle:action:keyEquivalent:" (receiver As Ptr, title As CFStringRef, act As Ptr, key As CFStringRef) As Ptr
      Declare Sub setAction Lib "Cocoa" Selector "setAction:" (receiver As Ptr, actionEvent As Ptr)
      Declare Sub setTitle Lib "Cocoa" Selector "setTitle:" (receiver As Ptr, title As CFStringRef)
      Declare Sub setTarget Lib "Cocoa" Selector "setTarget:" (receiver As Ptr, actionTarget As Ptr)
      Declare Sub setTag Lib "Cocoa" Selector "setTag:" (receiver As Ptr, tag As Integer)
      Declare Sub setSubmenu Lib "Cocoa" Selector "setSubmenu:" (receiver As Ptr, item As Ptr)
      Declare Sub addItem Lib "Cocoa" Selector "addItem:" (receiver As Ptr, item As Ptr)
      Declare Function separatorItem Lib "Cocoa" Selector "separatorItem" (receiver As Ptr) As Ptr
      Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
      
      // メニューの生成と初期化
      Dim menu1 As Ptr = NSClassFromString("NSMenu")
      menu1 = alloc(menu1)
      menu1 = init(menu1)
      
      // メニュー項目
      Dim menu2 As Ptr
      Dim pos As Integer
      pos=0
      do
        
        // メニュー項目クラスの取得
        menu2 = NSClassFromString("NSMenuItem")
        
        // メニュー項目の生成と初期化
        menu2 = alloc(menu2)
        menu2 = initWithTitleActKey(menu2, list(pos), NSSelectorFromString("action:"), "")  // ここでActionも設定
        // Targetを設定
        setTarget(menu2, makeTarget(itemIdentifier))
        // タグ
        setTag(menu2, tag0+pos)  // タグだけ判別するので、同じActionメソッドを使用するメニュー間で固有の数値にする
        
        // メニューにメニュー項目を追加
        addItem(menu1, menu2)
        
        // 後処理
        Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr)
        release(menu2)
        
        // リストが終了したら抜ける
        pos=pos+1
        if pos>list.Ubound then
          exit
        end if
        
      loop
      
      // メニューを返す
      return menu1
    End Function
    
  21. 以下をAccessoryPanelにペースト
    Private Shared Function makeMenuPopup(itemIdentifier As String) as Ptr
      Dim list(-1) As String
      
      list.Append "Unicode(UTF-8)"
      list.Append "Unicode(UTF-16)"
      list.Append "日本語(Shift JIS)"
      list.Append "日本語(EUC)"
      
      return makeMenu(itemIdentifier,list,1)
    End Function
    
  22. 以下をAccessoryPanelにペースト
    Private Shared Function makeTarget(name As String) as Ptr
      // 既にインスタンス作成済なら、それを返す
      if TargetInstance <> nil then
        return TargetInstance
      end if
      
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      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
      
      // クラス名を引数の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
    End Function
    
  23. 以下をAccessoryPanelにペースト
    Private Shared Sub panelButtonClicked(rcode As Integer)
      Declare Function objectAtIndex Lib "Cocoa" selector "objectAtIndex:" (class_id As Ptr, idx As Integer) As Ptr
      
      // rcode = 0 : Cancel
      // rcode = 1 : OK
      
      Dim no As Integer = -1
      Dim path As String = ""
      if rcode=1 then  // OK
        
        Dim i, cnt As Integer
        Dim pnt3, pnt4, pnt5 As Ptr
        Dim str1 As String
        
        // 選択したファイルのパス取得
        Declare Function URL Lib "Cocoa" selector "URL" (class_id As Ptr) As Ptr
        pnt5 = URL(Panel)
        Declare Function myPath Lib "Cocoa" selector "path" (class_id As Ptr) As CFStringRef
        path = myPath(pnt5)
        
        // アクセサリビューの取得
        Declare Function AccessoryView Lib "Cocoa" selector "accessoryView" (class_id As Ptr) As Ptr
        Dim pnt1 As Ptr = AccessoryView(Panel)
        // サブビュー配列を取得
        Declare Function subviews Lib "Cocoa" selector "subviews" (class_id As Ptr) As Ptr
        Dim pnt2 As Ptr = subviews(pnt1)
        // サブビューの個数を取得
        Declare Function count Lib "Cocoa" selector "count" (class_id As Ptr) As Integer
        cnt = count(pnt2)
        // サブビュー数だけ回す
        for i=0 to cnt-1
          // サブビューを順に取得
          pnt3 = objectAtIndex(pnt2, i)
          // サブビューのクラスを取得
          Declare Function myClass Lib "Cocoa" selector "class" (class_id As Ptr) As Ptr
          pnt4 = myClass(pnt3)
          // クラス名を取得
          Declare Function description Lib "Cocoa" selector "description" (class_id As Ptr) As CFStringRef
          str1 = description(pnt4)
          // 指定されたクラス名を持つサブビューを返す
          if str1="NSPopupButton" then
            // PopupMenuの選択されているメニューの番号を取得
            Declare Function indexOfSelectedItem Lib "Cocoa" selector "indexOfSelectedItem" (class_id As Ptr) As Integer
            no = indexOfSelectedItem(pnt3)
            exit
          end if
        next
        
      else  // Cancel
      end if
      
      // インスタンス側でActionを受け取るメソッドをレイズする
      ActionHandler.Invoke(no, path)
    End Sub
    
  24. 以下をAccessoryPanelにペースト(できなければ共有プロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Private Shared Property ActionHandler as ActionDelegate
    
  25. 以下をAccessoryPanelにペースト(できなければ共有プロパティに、名前:DelegateID、データ型:Ptr、を追加)
    Private Shared Property DelegateID as Ptr
    
  26. 以下をAccessoryPanelにペースト(できなければ共有プロパティに、名前:Panel、データ型:Ptr、を追加)
    Private Shared Property Panel as Ptr
    
  27. 以下をAccessoryPanelにペースト(できなければ共有プロパティに、名前:TargetInstance、データ型:Ptr、を追加)
    Private Shared Property TargetInstance as Ptr
    
  28. 他に、NSMakeRect(メソッド)、NSRect(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(上記AccessoryPanelにコピーする。)
 実行してみたところ、ファイルオープン/保存のダイアログ/シートで、オプション設定が機能することを確認しました。
S Shot1


 おわりに

 プラグインを使わずにシートを扱う方法がないか、できればもう少し調べてみたいと思います。については、こちらのページに纏めました

 なお、今回も仕組みを理解するために、極力シンプルな書き方を心懸けています。
 メモリーリークに関しては、leaks上でチェックしたところ、なぜか、ダイアログで保存のケースのみ、リークが発生することがあります。
(頻度としては、発生しない方が多いです。また、再現する条件は特定できていません。)
 もし、この件で何か情報がありましたら、お手数ですが、ご一報頂ければ幸いです。


 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):Obj-C Blocks as Callbacks - Xojo Forum


 更新履歴

 2018.07.30 Xojoでの実装の16,17項を改訂
 2018.07.20 方針のプラグインについての記述に、追記を追加
 2018.07.20 Xojoでの実装の18項を改訂
 2018.07.20 方針とおわりにの記述に、別記事へのリンクを追加
 2017.04.05 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]