ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでアウトラインビューを使ってみる
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでアウトラインビューを使ってみる
はじめに
以下は、Xojo Cocoaビルドについての話題です。
以前、ちらっと話題にしたアウトラインビューの実装について、調べてみました。
なお検証には、Xojo 2021 Release 2.1を用いています。(Mac mini 2018 + macOS 11.6 Big Sur)
方針
アウトラインビュー(NSOutlineView)はテーブルビュー(NSTableView)のサブクラスで、基本的な考え方は以前にやったこちらと同じです。
この時は、全て自前でやったので、同じようなことをやれば所望の機能は得られると思われます。
ですが、ここではアウトラインビュー(テーブルビューも)の特徴の一つである、Cocoa Bindingを利用してみることにします。
ただし、Cocoa Bindingで検索して見つかる情報は、Interface Builderを使う方法が圧倒的で、例によってXojoでは使えません。
それでも、以下が見つかりました。
参考サイト(1):NSTreeController + NSOutlineView: A powerful combination | by Alexander Murphy | Building Ibotta | Medium
参考サイト(2):NSTreeController + NSOutlineView:強力な組み合わせ(上記の日本語翻訳?)
今回は、基本を理解するために不確定要素はできるだけ排除したいので、上記サンプルをそのまま移植してみることにしました。
(プロジェクトは、参考サイト(1)の「Just give me the code!」からダウンロード可)
サンプルを眺めてみると、バインディングの部分は確かにコードなのですが、アウトラインビューの配置にはInterface Builderを使っています。
このことは、カラム(デフォルトで2列配置済)、セル用のビュー、文字列用のテキストフィールドといった様々なものが「自動的に」セットされることを意味しますが、Xojoではこれら(と、プロジェクトを作成すると「自動的に」生成されるViewControllerも含めて)全てをコードで記述、となります。
あと、サンプルではバインドの相手先としてプロパティを指定しているのですが、これ自体は初お目見えではあるものの、実装にはお馴染みのObjective-CのランタイムAPIを用います。
ところがここで問題が。というのも、プロパティは引数に構造体の配列を指定するのですが、この方法が分からないという事態に。
そこで、代わりにインスタンス変数を使ってみたところ、動作しているようでしたので、結局こちらにしました。
なお、プロパティ/インスタンス変数は、宣言だけでなく、ゲッター/セッター(リードオンリーでは不要)メソッドも同時に記述します。
参考サイト(3):objective c - How can I add properties to an object at runtime? - Stack Overflow
注:上記サイトの例では、セッターでの変数コピーにcopyを使っていますが、実験の結果、NSMutableArrayをセットすると(ゲッターでは)NSArray型で返ってきました。これでは書き換えができないので、copyの代わりにmutableCopyを使ったところ、NSMutableArray型で返ってくるようになりました。なので、以下の実装ではmutableCopyを使っていますが、これでいいのかはよく分かりません。以上を踏まえ、(残りの)仕様は以下の通りとしました。
追記:セッターでは(上記サイトの通り)copyを使い、(編集可能なものは)ゲッターでmutable化する方が素直に思えてきました。
- 完全移植ではなく、機能互換とする(一部、Xojoとは親和性のない記述法が用いられている等のため)
- 一部変数名は変更する(予約語との違いをより明確にする等のため)
- アウトラインビュー、ViewController、Nodeはクラス化し、NodeFactoryはWindow1にまとめる
- オリジナルが起動時にViewController内で行っている処理は、原則Window1にまとめる
Xojoでの実装
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、サンプルと同等な結果が得られることを確認しました。
- Xojoで新規プロジェクトを作成(Window1のWidthを480、Heightを270に変更)
- 以下をWindow1にペースト(できなければConstantsに、Constant Name:NSContentArrayBinding、Default Value:contentArray、Type:String、を追加)
Protected Const NSContentArrayBinding as String = contentArray
- 以下をWindow1にペースト(できなければConstantsに、Constant Name:NSContentBinding、Default Value:content、Type:String、を追加)
Protected Const NSContentBinding as String = content
- 以下をWindow1にペースト(できなければConstantsに、Constant Name:NSValueBinding、Default Value:value、Type:String、を追加)
Protected Const NSValueBinding as String = value
- 以下をWindow1にペースト
Sub Open() Handles Open // アウトラインビューとビューコントローラー初期化 InitOutlineView() InitViewController() // nodeクラス初期化 nodeClass = new Node() // データを生成してセット MakeOutline() // ウィンドウのTop補正(Big Surの場合のみ) me.Top=me.Top+8 End Sub
- 以下をWindow1にペースト
Protected Sub AddCol(title As String, width As CGFloat, ident As String) // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // 列クラス(NSTableColumn)のインスタンスを生成 Dim tCol As Ptr = NSClassFromString("NSTableColumn") // クラスメソッドなので、まずNSTableColumnクラスを取得 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function initWithIdentifier Lib "Cocoa" selector "initWithIdentifier:" (obj_id As Ptr, ifer As CFStringRef) As Ptr tCol = initWithIdentifier(alloc(tCol), ident) // 列幅をセット Declare Sub setWidth Lib "Cocoa" Selector "setWidth:" (receiver As Ptr, w As CGFloat) setWidth(tCol, width) // ヘッダーのタイトルをセット Declare Sub setTitle Lib "Cocoa" Selector "setTitle:" (receiver As Ptr, title As CFStringRef) setTitle(tCol, title) // 列クラスのインスタンスをアウトラインビューに追加 Declare Sub addTableColumn Lib "Cocoa" Selector "addTableColumn:" (receiver As Ptr, col As Ptr) addTableColumn(outlineviewInst, tCol) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(tCol) End Sub
- 以下をWindow1にペースト
Protected Sub InitOutlineView() // NSOutlineView。引数は順に、生成したインスタンス(アウトラインビューとスクロールビュー)、位置/サイズ、Delegateメソッド Dim o As NSOutlineView = new NSOutlineView(outlineviewInst, scrollviewInst, NSMakeRect(0, 0, me.Width, me.Height), AddressOf viewForTableCol) End Sub
- 以下をWindow1にペースト
Protected Sub InitViewController() // OutlineViewのController生成 Dim c As NSViewController = new NSViewController(viewcntlInst) // ViewControllerに別途生成したOutlineView(実際にセットするのはScrollView)をセット Declare Sub setView Lib "Cocoa" Selector "setView:" (receiver As Ptr, view As Ptr) setView(viewcntlInst, scrollviewInst) // ウィンドウにViewControllerをセット Declare Sub setContentViewController Lib "Cocoa" Selector "setContentViewController:" (receiver As Integer, view As Ptr) setContentViewController(me.Handle, viewcntlInst) End Sub
- 以下をWindow1にペースト
Protected Sub MakeOutline() // 二列生成 AddCol("Nodes",320,"node") AddCol("Count",104,"count") // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // TreeController生成 treecntlInst = NSClassFromString("NSTreeController") Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" Selector "init" (receiver As Ptr) As Ptr treecntlInst = init(alloc(treecntlInst)) Declare Sub setChildrenKeyPath Lib "Cocoa" Selector "setChildrenKeyPath:" (receiver As Ptr, kpath As CFStringRef) setChildrenKeyPath(treecntlInst, "children") Declare Sub setCountKeyPath Lib "Cocoa" Selector "setCountKeyPath:" (receiver As Ptr, cpath As CFStringRef) setCountKeyPath(treecntlInst, "count") Declare Sub setLeafKeyPath Lib "Cocoa" Selector "setLeafKeyPath:" (receiver As Ptr, kpath As CFStringRef) setLeafKeyPath(treecntlInst, "isLeaf") Declare Sub setObjectClass Lib "Cocoa" Selector "setObjectClass:" (receiver As Ptr, cls As Ptr) setObjectClass(treecntlInst, Node.GetNodeClass()) // Cocoa Binding Declare Sub bind Lib "Cocoa" Selector "bind:toObject:withKeyPath:options:" (receiver As Ptr, bind As CFStringRef, obj As Ptr, key As CFStringRef, optn As Ptr) bind(treecntlInst, NSContentArrayBinding, viewcntlInst, "container", nil) // TreeControllerのcontentArrayにViewControllerのcontainerをバインド bind(outlineviewInst, NSContentBinding, treecntlInst, "arrangedObjects", nil) // OutlineviewのcontentにTreeControllerのarrangedObjectsをバインド // node生成 Dim node1 As Ptr = setNode() // ViewControllerのcontainerにnodeをセット Declare Sub setContainer Lib "Cocoa" Selector "setContainer:" (receiver As Ptr, node As Ptr) setContainer(viewcntlInst, node1) End Sub
- 以下をWindow1にペースト
Protected Function SetNode() As Ptr // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As Ptr) // ルートノード Dim root As Ptr = NSClassFromString("NSMutableArray") Declare Function myArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr root = myArray(root) Dim ofr(2), tmp(0) As Ptr tmp(0) = nodeClass.MakeElement("💵 $0.24 back") // リーフノードは名前のみ ofr(0) = nodeClass.MakeElement("🍦 Ice Cream",tmp) // 枝ノードは名前と子供指定 tmp(0) = nodeClass.MakeElement("💵 $0.75 back") // リーフノードは名前のみ ofr(1) = nodeClass.MakeElement("☕️ Coffeem",tmp) // 枝ノードは名前と子供指定 tmp(0) = nodeClass.MakeElement("💵 $1.00 back") // リーフノードは名前のみ ofr(2) = nodeClass.MakeElement("🍔 Burger",tmp) // 枝ノードは名前と子供指定 Dim ofrB As Ptr = nodeClass.MakeElement("💰 Offers",ofr) // 枝ノードは名前と子供指定 addObject(root, ofrB) // ルートに追加 Dim rtl(2) As Ptr rtl(0) = nodeClass.MakeElement("King Soopers") // リーフノードは名前のみ rtl(1) = nodeClass.MakeElement("Walmart") // リーフノードは名前のみ rtl(2) = nodeClass.MakeElement("Target") // リーフノードは名前のみ Dim rtlB As Ptr = nodeClass.MakeElement("Retailers",rtl) // 枝ノードは名前と子供指定 addObject(root, rtlB) // ルートに追加 // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(ofrB) release(rtlB) return root End Function
- 以下をWindow1にペースト
Private Function viewForTableCol(id As Ptr, sel As CString, outlineView As Ptr, tableColumn As Ptr, item As Ptr) As Ptr // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName 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 Declare Sub setIdentifier Lib "Cocoa" Selector "setIdentifier:" (receiver As Ptr, ident As CFStringRef) // NSTableCellView生成 Dim cellView As Ptr = NSClassFromString("NSTableCellView") Declare Function identifier Lib "Cocoa" selector "identifier" (class_id As Ptr) As CFStringRef Dim ident As CFStringRef = identifier(tableColumn) if ident="" then return cellView if ident="node" then // 一列目 Declare Function myDelegate Lib "Cocoa" selector "delegate" (class_id As Ptr) As Ptr Dim dele As Ptr = myDelegate(outlineView) // TableCellViewを取得(できなければ生成) Declare Function makeViewWithIdentifier Lib "Cocoa" Selector "makeViewWithIdentifier:owner:" (receiver As Ptr, ident As CFStringRef, own As Ptr) As Ptr Dim view As Ptr = makeViewWithIdentifier(outlineView, ident, dele) if view=nil then // Create TableCellView view = NSClassFromString("NSTableCellView") view = initWithFrame(alloc(view), NSMakeRect(0,0,300,19)) setIdentifier(view, ident) else // 生成済ならそれを返す(そうしないと、二重書きが発生する) return view end if // TextField生成 Dim textField As Ptr = NSClassFromString("NSTextField") Dim rect As NSRect = NSMakeRect(0,0,300,18) textField = initWithFrame(alloc(textField), rect) // パラメータセット Declare Sub setBezeled Lib "Cocoa" Selector "setBezeled:" (receiver As Ptr, flg As Boolean) setBezeled(textField, false) Declare Sub setEditable Lib "Cocoa" Selector "setEditable:" (receiver As Ptr, flg As Boolean) setEditable(textField, false) Declare Sub setDrawsBackground Lib "Cocoa" Selector "setDrawsBackground:" (receiver As Ptr, flg As Boolean) setDrawsBackground(textField, false) // TextFieldをTableCellViewに追加 Declare Sub addSubview Lib "Cocoa" Selector "addSubview:" (receiver As Ptr, txt As Ptr) addSubview(view, textField) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(textField) // Cocoa Binding Declare Sub bind Lib "Cocoa" Selector "bind:toObject:withKeyPath:options:" (receiver As Ptr, bind As CFStringRef, obj As Ptr, key As CFStringRef, optn As Ptr) bind(textField, NSValueBinding, view, "objectValue.listitem", nil) // textFieldのvalueにviewのobjectValue.listitemをバインド cellView = view elseif ident="count" then // 二列目 Declare Function myDelegate Lib "Cocoa" selector "delegate" (class_id As Ptr) As Ptr Dim dele As Ptr = myDelegate(outlineView) // TableCellViewを取得(できなければ生成) Declare Function makeViewWithIdentifier Lib "Cocoa" Selector "makeViewWithIdentifier:owner:" (receiver As Ptr, ident As CFStringRef, own As Ptr) As Ptr Dim view As Ptr = makeViewWithIdentifier(outlineView, ident, dele) if view=nil then // Create TableCellView view = NSClassFromString("NSTableCellView") view = initWithFrame(alloc(view), NSMakeRect(0,0,100,19)) setIdentifier(view, ident) else // 生成済ならそれを返す(そうしないと、二重書きが発生する) return view end if // TextField生成 Dim textField As Ptr = NSClassFromString("NSTextField") Dim rect As NSRect = NSMakeRect(0,0,100,18) textField = initWithFrame(alloc(textField), rect) // パラメータセット Declare Sub setBezeled Lib "Cocoa" Selector "setBezeled:" (receiver As Ptr, flg As Boolean) setBezeled(textField, false) Declare Sub setEditable Lib "Cocoa" Selector "setEditable:" (receiver As Ptr, flg As Boolean) setEditable(textField, false) Declare Sub setDrawsBackground Lib "Cocoa" Selector "setDrawsBackground:" (receiver As Ptr, flg As Boolean) setDrawsBackground(textField, false) // TextFieldをTableCellViewに追加 Declare Sub addSubview Lib "Cocoa" Selector "addSubview:" (receiver As Ptr, txt As Ptr) addSubview(view, textField) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(textField) // Cocoa Binding Declare Sub bind Lib "Cocoa" Selector "bind:toObject:withKeyPath:options:" (receiver As Ptr, bind As CFStringRef, obj As Ptr, key As CFStringRef, optn As Ptr) bind(textField, NSValueBinding, view, "objectValue.countString", nil) // textFieldのvalueにviewのobjectValue.countStringをバインド cellView = view else return cellView end if return cellView End Function
- 以下をWindow1にペースト(できなければPropertyに、Name:nodeClass、Type:Node、を追加)
Protected Property nodeClass As Node
- 以下をWindow1にペースト(できなければPropertyに、Name:outlineviewInst、Type:Ptr、を追加)
Protected Property outlineviewInst As Ptr
- 以下をWindow1にペースト(できなければPropertyに、Name:scrollviewInst、Type:Ptr、を追加)
Protected Property scrollviewInst As Ptr
- 以下をWindow1にペースト(できなければPropertyに、Name:treecntlInst、Type:Ptr、を追加)
Protected Property treecntlInst As Ptr
- 以下をWindow1にペースト(できなければPropertyに、Name:viewcntlInst、Type:Ptr、を追加)
Protected Property viewcntlInst As Ptr
- 新規クラスを作成(名前は、ここでは「NSOutlineView」とした。)
- 以下をNSOutlineViewにペースト(できなければDelegateに、Delegate Name:ActionDelegate、Parameters:id As Ptr, sel As CString, outlineView As Ptr, tableColumn As Ptr, item As Ptr、Return Type:Ptr、を追加)
Private Function ActionDelegate1(id As Ptr, sel As CString, outlineView As Ptr, tableColumn As Ptr, item As Ptr) As Ptr
- 以下をNSOutlineViewにペースト
Public Sub Constructor(byRef inst As Ptr, byRef inst2 As Ptr, rect As NSRect, action1 As ActionDelegate1) // Window側でActionを受け取るメソッドを登録 ActionHandler1 = action1 // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function initWithFrame Lib "Cocoa" selector "initWithFrame:" (obj_id As Ptr, frame As NSRect) As Ptr // NSOutlineViewを継承したカスタムクラスを作成。初回のみ makeClass() // NSOutlineViewのインスタンス作成 Dim outlineView As Ptr = initWithFrame(alloc(NSOutlineViewClass), NSMakeRect(0, 0, 0, 0)) // rectを指定すると、スクロール時に描画が乱れる(ヘッダーずれ他) Declare Sub setFocusRingType Lib "Cocoa" Selector "setFocusRingType:" (receiver As Ptr, typ As Integer) setFocusRingType(outlineView, 1) // 1 = NSFocusRingTypeNone(フォーカスリングなし) Declare Sub setGridStyleMask Lib "Cocoa" Selector "setGridStyleMask:" (receiver As Ptr, typ As Integer) setGridStyleMask(outlineView, 2) // 2 = NSTableViewSolidHorizontalGridLineMask(実線の横方向罫線あり) Declare Sub setRowHeight Lib "Cocoa" Selector "setRowHeight:" (receiver As Ptr, typ As CGFloat) setRowHeight(outlineView, 19.0) // 行の高さ Declare Sub setSelectionHighlightStyle Lib "Cocoa" Selector "setSelectionHighlightStyle:" (receiver As Ptr, typ As Integer) setSelectionHighlightStyle(outlineView, -1) // -1 = NSTableViewSelectionHighlightStyleNone(行のハイライト表示を行わない) Declare Sub setAutoresizesOutlineColumn Lib "Cocoa" Selector "setAutoresizesOutlineColumn:" (receiver As Ptr, typ As Boolean) setAutoresizesOutlineColumn(outlineView, false) // 列幅の自動リサイズを行わない // NSScrollViewのインスタンス生成 Dim scrollView As Ptr = NSClassFromString("NSScrollView") // クラスメソッドなので、まずNSScrollViewクラスを取得 scrollView = initWithFrame(alloc(scrollView), rect) Declare Sub setBorderType Lib "Cocoa" Selector "setBorderType:" (receiver As Ptr, typ As Integer) setBorderType(scrollView, 2) // 2 = NSBezelBorder Declare Sub setHasVerticalScroller Lib "Cocoa" Selector "setHasVerticalScroller:" (receiver As Ptr, typ As Boolean) setHasVerticalScroller(scrollView, true) Declare Sub setHasHorizontalScroller Lib "Cocoa" Selector "setHasHorizontalScroller:" (receiver As Ptr, typ As Boolean) setHasHorizontalScroller(scrollView, true) Declare Sub setAutohidesScrollers Lib "Cocoa" Selector "setAutohidesScrollers:" (receiver As Ptr, typ As Boolean) setAutohidesScrollers(scrollView, true) Declare Sub setAutoresizingMask Lib "Cocoa" Selector "setAutoresizingMask:" (receiver As Ptr, typ As Integer) setAutoresizingMask(scrollView, 2+16) // 2+16 = NSViewWidthSizable | NSViewHeightSizable Declare Sub setDrawsBackground Lib "Cocoa" Selector "setDrawsBackground:" (receiver As Ptr, flg As Boolean) setDrawsBackground(scrollView, false) // 背景を透明化 // Delegateの設定 Declare Sub setDelegate Lib "Cocoa" Selector "setDelegate:" (receiver As Ptr, obj As Ptr) setDelegate(outlineView, outlineView) // スクロールビューにアウトラインビューをセット Declare Sub setDocumentView Lib "Cocoa" Selector "setDocumentView:" (receiver As Ptr, obj As Ptr) setDocumentView(scrollView, outlineView) // インスタンスを保持 InstancePtr = outlineView inst = InstancePtr inst2 = scrollView End Sub
- 以下をNSOutlineViewにペースト(できなければPropertyに、Name:InstancePtr、Type:Ptr、を追加)
Private Property InstancePtr As Ptr
- 以下をNSOutlineViewにペースト
Private Shared Sub makeClass() // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 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 NSOutlineViewClass <> nil then return end if // クラス名をmyNSOutlineView、メタクラス名をNSOutlineViewにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSOutlineView"), "myNSOutlineView", 0) // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // Delegateの対象となるメソッドを追加(outlineView:viewForTableColumn:item:をXojo側で用意したmyViewForTableColumnメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("outlineView:viewForTableColumn:item:"), AddressOf myViewForTableColumn, "@32@0:8@16p24") then msgBox "error1." return end if // クラスを保持 NSOutlineViewClass = newClassId End Sub
- 以下をNSOutlineViewにペースト
Private Shared Function myViewForTableColumn(id As Ptr, sel As CString, outlineView As Ptr, tableColumn As Ptr, item As Ptr) As Ptr // Constructorで登録した、Actionを受け取るメソッドを呼び出す return ActionHandler1.Invoke(id, sel, outlineView, tableColumn, item) End Function
- 以下をNSOutlineViewにペースト(できなければShared Propertyに、Name:ActionHandler1、Type:ActionDelegate1、を追加)
Private Shared Property ActionHandler1 As ActionDelegate1
- 以下をNSOutlineViewにペースト(できなければShared Propertyに、Name:NSOutlineViewClass、Type:Ptr、を追加)
Private Shared Property NSOutlineViewClass As Ptr
- 新規クラスを作成(名前は、ここでは「NSViewController」とした。)
- 以下をNSViewControllerにペースト(できなければDelegateに、Delegate Name:ActionDelegate1、Parameters:id As Ptr, sel As CString、Return Type:Ptr、を追加)
Private Function ActionDelegate1(id As Ptr, sel As CString) As Ptr
- 以下をNSViewControllerにペースト(できなければDelegateに、Delegate Name:ActionDelegate2、Parameters:id As Ptr, sel As CString, value As Ptr、を追加)
Private Sub ActionDelegate2(id As Ptr, sel As CString, value As Ptr)
- 以下をNSViewControllerにペースト
Public Sub Constructor(byRef inst As Ptr) // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // NSViewControllerを継承したカスタムクラスを作成。初回のみ makeClass() // NSViewControllerのインスタンス作成 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function initWithNibName Lib "Cocoa" Selector "initWithNibName:bundle:" (receiver As Ptr, name As Ptr, bundle As Ptr) As Ptr Dim viewCntl As Ptr = initWithNibName(alloc(NSViewControllerClass), nil, nil) // インスタンスを保持 InstancePtr = viewCntl inst = InstancePtr // インスタンス側でActionを受け取るメソッドを登録 ActionHandler1 = addressOf getContainer ActionHandler2 = addressOf setContainer End Sub
- 以下をNSViewControllerにペースト
Private Function getContainer(id As Ptr, sel As CString) As Ptr // クラスから変数(container)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, cnt As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSViewControllerClass, "container") // インスタンスから変数(container)の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim value As Ptr = object_getIvar(id, ivar) return value End Function
- 以下をNSViewControllerにペースト
Private Sub setContainer(id As Ptr, sel As CString, value As Ptr) // クラスから変数(container)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, name As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSViewControllerClass, "container") // インスタンスから変数(container)の現在の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim oldValue As Ptr = object_getIvar(id, ivar) // 値が異なればセット if value<>oldValue then // インスタンスの変数(container)に値をセット Declare Sub object_setIvar Lib "Cocoa" (superclass As Ptr, ivar as Ptr, val as Ptr) Declare Function mutableCopy Lib "Cocoa" Selector "mutableCopy" (receiver As Ptr) As Ptr object_setIvar(id, ivar, mutableCopy(value)) // コピーをセットしないと、オリジナルが解放されてしまう? end if End Sub
- 以下をNSViewControllerにペースト(できなければPropertyに、Name:InstancePtr、Type:Ptr、を追加)
Private Property InstancePtr As Ptr
- 以下をNSViewControllerにペースト
Private Shared Function containerGetter(id As Ptr, sel As CString) As Ptr // Constructorで登録した、Actionを受け取るメソッドを呼び出す return ActionHandler1.Invoke(id, sel) End Function
- 以下をNSViewControllerにペースト
Private Shared Sub containerSetter(id As Ptr, sel As CString, value As Ptr) // Constructorで登録した、Actionを受け取るメソッドを呼び出す ActionHandler2.Invoke(id, sel, value) End Sub
- 以下をNSViewControllerにペースト
Private Shared Sub makeClass() // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 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 Declare Function class_addIvar Lib "Cocoa" (cls as Ptr, name as CString, size as UInteger, alignment as UInt8, types as CString) As Boolean // 既にクラス作成済なら戻る if NSViewControllerClass <> nil then return end if // クラス名をmyNSViewController、メタクラス名をNSViewControllerにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSViewController"), "myNSViewController", 0) // 変数を追加(container) if not class_addIvar (newClassId, "container", 8, 3, "@") then msgBox "error1." return end if // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // メソッドを追加(containerをXojo側で用意したtypeGetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("container"), AddressOf containerGetter, "@@:") then msgBox "error2." return end if // メソッドを追加(setContainer:をXojo側で用意したtypeSetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("setContainer:"), AddressOf containerSetter, "v@:@") then msgBox "error3." return end if // クラスを保持 NSViewControllerClass = newClassId End Sub
- 以下をNSViewControllerにペースト(できなければShared Propertyに、Name:ActionHandler1、Type:ActionDelegate1、を追加)
Private Shared Property ActionHandler1 As ActionDelegate1
- 以下をNSViewControllerにペースト(できなければShared Propertyに、Name:ActionHandler2、Type:ActionDelegate2、を追加)
Private Shared Property ActionHandler2 As ActionDelegate2
- 以下をNSViewControllerにペースト(できなければShared Propertyに、Name:NSViewControllerClass、Type:Ptr、を追加)
Private Shared Property NSViewControllerClass As Ptr
- 新規クラスを作成(名前は、ここでは「Node」とした。)
- 以下をNodeにペースト(できなければDelegateに、Delegate Name:ActionDelegate1、Parameters:id As Ptr, sel As CString、Return Type:CFStringRef、を追加)
Private Function ActionDelegate1(id As Ptr, sel As CString) As CFStringRef
- 以下をNodeにペースト(できなければDelegateに、Delegate Name:ActionDelegate2、Parameters:id As Ptr, sel As CString, newName As Ptr、を追加)
Private Sub ActionDelegate2(id As Ptr, sel As CString, newName As Ptr)
- 以下をNodeにペースト(できなければDelegateに、Delegate Name:ActionDelegate3、Parameters:id As Ptr, sel As CString、Return Type:Ptr、を追加)
Private Function ActionDelegate3(id As Ptr, sel As CString) As Ptr
- 以下をNodeにペースト(できなければDelegateに、Delegate Name:ActionDelegate4、Parameters:id As Ptr, sel As CString, newName As Ptr、を追加)
Private Sub ActionDelegate4(id As Ptr, sel As CString, newName As Ptr)
- 以下をNodeにペースト(できなければDelegateに、Delegate Name:ActionDelegate5、Parameters:id As Ptr, sel As CString、Return Type:Integer、を追加)
Private Function ActionDelegate5(id As Ptr, sel As CString) As Integer
- 以下をNodeにペースト(できなければDelegateに、Delegate Name:ActionDelegate6、Parameters:id As Ptr, sel As CString、Return Type:Boolean、を追加)
Private Function ActionDelegate6(id As Ptr, sel As CString) As Boolean
- 以下をNodeにペースト(できなければDelegateに、Delegate Name:ActionDelegate7、Parameters:id As Ptr, sel As CString、Return Type:Ptr、を追加)
Private Function ActionDelegate7(id As Ptr, sel As CString) As Ptr
- 以下をNodeにペースト
Public Sub Constructor() // NSObjectを継承したカスタムクラスを作成。初回のみ makeClass() // インスタンス側でActionを受け取るメソッドを登録 ActionHandler1 = addressOf getListitem ActionHandler2 = addressOf setListitem ActionHandler3 = addressOf getChildren ActionHandler4 = addressOf setChildren ActionHandler5 = addressOf getCount ActionHandler6 = addressOf getIsLeaf ActionHandler7 = addressOf getChildrenCount End Sub
- 以下をNodeにペースト
Private Function getChildren(id As Ptr, sel As CString) As Ptr // クラスから変数(children)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, cnt As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSNodeClass, "children") // インスタンスから変数(children)の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim value As Ptr = object_getIvar(id, ivar) return value End Function
- 以下をNodeにペースト
Private Function getChildrenCount(id As Ptr, sel As CString) As Ptr // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // クラスから変数(children)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, cnt As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSNodeClass, "children") // インスタンスから変数(children)の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim value As Ptr = object_getIvar(id, ivar) Declare Function count Lib "Cocoa" Selector "count" (receiver As Ptr) As Integer Dim cnt As Integer = count(value) if cnt<=0 then return nil end if Dim stt As String if cnt=1 then stt = Str(cnt)+" node" else stt = Str(cnt)+" nodes" end if Dim st As Ptr = NSClassFromString("NSString") Declare Function stringWithUTF8String Lib "Cocoa" Selector "stringWithUTF8String:" (receiver As Ptr, str As CString) As Ptr st = stringWithUTF8String(st, stt) return st End Function
- 以下をNodeにペースト
Private Function getCount(id As Ptr, sel As CString) As Integer // クラスから変数(children)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, cnt As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSNodeClass, "children") // インスタンスから変数(children)の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim value As Ptr = object_getIvar(id, ivar) Declare Function count Lib "Cocoa" Selector "count" (receiver As Ptr) As Integer Dim value2 As Integer = count(value) return value2 End Function
- 以下をNodeにペースト
Private Function getIsLeaf(id As Ptr, sel As CString) As Boolean // クラスから変数(children)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, cnt As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSNodeClass, "children") // インスタンスから変数(children)の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim value As Ptr = object_getIvar(id, ivar) // childrenの数を取得 Declare Function count Lib "Cocoa" Selector "count" (receiver As Ptr) As Integer Dim value2 As Integer = count(value) // childrenを持っていなければtrue、持っていればfalseを返す if value2=0 then return true else return false end if End Function
- 以下をNodeにペースト
Private Function getListitem(id As Ptr, sel As CString) As CFStringRef // クラスから変数(listitem)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, cnt As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSNodeClass, "listitem") // インスタンスから変数(listitem)の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as CFStringRef Dim value As String = object_getIvar(id, ivar) return value End Function
- 以下をNodeにペースト
Public Function MakeElement(Listitem As String) As Ptr return MakeElement(Listitem,nil) End Function Public Function MakeElement(Listitem As String, Child() As Ptr) As Ptr // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // Nodeのインスタンス生成 Dim inst As Ptr=NewInstance() // タイトルをセット Declare Sub setListitem Lib "Cocoa" Selector "setListitem:" (receiver As Ptr, value As CFStringRef) setListitem(inst, Listitem) // 空の子供配列を生成 Dim ary As Ptr = NSClassFromString("NSMutableArray") Declare Function myArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr ary = myArray(ary) // 子供をセット if Child=nil then // リーフはnilが渡ってくるので、空の子供配列をそのままセット Declare Sub setChildren Lib "Cocoa" Selector "setChildren:" (receiver As Ptr, value As Ptr) setChildren(inst, ary) else // 渡ってきた子供を子供配列に追加 for i As Integer = 0 to Child.Ubound Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As Ptr) addObject(ary, Child(i)) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(Child(i)) next Declare Sub setChildren Lib "Cocoa" Selector "setChildren:" (receiver As Ptr, value As Ptr) setChildren(inst, ary) end if // インスタンスを返す return inst End Function
- 以下をNodeにペースト
Private Function NewInstance() As Ptr // インスタンスを作成して返す 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 return init(alloc(NSNodeClass)) End Function
- 以下をNodeにペースト
Private Sub setChildren(id As Ptr, sel As CString, value As Ptr) // クラスから変数(children)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, name As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSNodeClass, "children") // インスタンスから変数(children)の現在の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim oldValue As Ptr = object_getIvar(id, ivar) // 値が異なればセット if value<>oldValue then // インスタンスの変数(children)に値をセット Declare Sub object_setIvar Lib "Cocoa" (superclass As Ptr, ivar as Ptr, str as Ptr) Declare Function mutableCopy Lib "Cocoa" Selector "mutableCopy" (receiver As Ptr) As Ptr object_setIvar(id, ivar, mutableCopy(value)) // コピーをセットしないと、オリジナルが解放されてしまう? end if End Sub
- 以下をNodeにペースト
Private Sub setListitem(id As Ptr, sel As CString, value As Ptr) // クラスから変数(listitem)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, name As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(NSNodeClass, "listitem") // インスタンスから変数(listitem)の現在の値を取得 Declare Function object_getIvar Lib "Cocoa" (superclass As Ptr, extraBytes As Ptr) as Ptr Dim oldValue As Ptr = object_getIvar(id, ivar) // 値が異なればセット if value<>oldValue then // インスタンスの変数(listitem)に値をセット Declare Sub object_setIvar Lib "Cocoa" (superclass As Ptr, ivar as Ptr, str as Ptr) Declare Function mutableCopy Lib "Cocoa" Selector "mutableCopy" (receiver As Ptr) As Ptr object_setIvar(id, ivar, mutableCopy(value)) // コピーをセットしないと、オリジナルが解放されてしまう? end if End Sub
- 以下をNodeにペースト(できなければPropertyに、Name:InstancePtr、Type:Ptr、を追加)
Public Property InstancePtr As Ptr
- 以下をNodeにペースト
Private Shared Function chCountGetter(id As Ptr, sel As CString) As Ptr // Constructorで登録した、Actionを受け取るメソッドを呼び出す return ActionHandler7.Invoke(id, sel) End Function
- 以下をNodeにペースト
Private Shared Function childrenGetter(id As Ptr, sel As CString) As Ptr // Constructorで登録した、Actionを受け取るメソッドを呼び出す return ActionHandler3.Invoke(id, sel) End Function
- 以下をNodeにペースト
Private Shared Sub childrenSetter(id As Ptr, sel As CString, value As Ptr) // Constructorで登録した、Actionを受け取るメソッドを呼び出す ActionHandler4.Invoke(id, sel, value) End Sub
- 以下をNodeにペースト
Private Shared Function countGetter(id As Ptr, sel As CString) As Integer // Constructorで登録した、Actionを受け取るメソッドを呼び出す return ActionHandler5.Invoke(id, sel) End Function
- 以下をNodeにペースト
Public Shared Function GetNodeClass() As Ptr return NSNodeClass End Function
- 以下をNodeにペースト
Private Shared Function isLeafGetter(id As Ptr, sel As CString) As Boolean // Constructorで登録した、Actionを受け取るメソッドを呼び出す return ActionHandler6.Invoke(id, sel) End Function
- 以下をNodeにペースト
Private Shared Function listitemGetter(id As Ptr, sel As CString) As CFStringRef // Constructorで登録した、Actionを受け取るメソッドを呼び出す return ActionHandler1.Invoke(id, sel) End Function
- 以下をNodeにペースト
Private Shared Sub listitemSetter(id As Ptr, sel As CString, value As Ptr) // Constructorで登録した、Actionを受け取るメソッドを呼び出す ActionHandler2.Invoke(id, sel, value) End Sub
- 以下をNodeにペースト
Private Shared Sub makeClass() // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 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 Declare Function class_addIvar Lib "Cocoa" (cls as Ptr, name as CString, size as UInteger, alignment as UInt8, types as CString) As Boolean // 既にクラス作成済なら戻る if NSNodeClass <> nil then return end if // クラス名をmyNSNode、メタクラス名をNSObjectにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myNSNode", 0) // 変数を追加(listitem) if not class_addIvar (newClassId, "listitem", 8, 3, "@") then msgBox "error1." return end if // 変数を追加(children) if not class_addIvar (newClassId, "children", 8, 3, "@") then msgBox "error2." return end if // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // メソッドを追加(listitemをXojo側で用意したlistitemGetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("listitem"), AddressOf listitemGetter, "@@:") then msgBox "error3." return end if // メソッドを追加(setListitem:をXojo側で用意したlistitemSetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("setListitem:"), AddressOf listitemSetter, "v@:@") then msgBox "error4." return end if // メソッドを追加(childrenをXojo側で用意したchildrenGetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("children"), AddressOf childrenGetter, "@@:") then msgBox "error5." return end if // メソッドを追加(setChildren:をXojo側で用意したchildrenSetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("setChildren:"), AddressOf childrenSetter, "v@:@") then msgBox "error6." return end if // メソッドを追加(isLeafをXojo側で用意したisLeafGetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("isLeaf"), AddressOf isLeafGetter, "B@:") then msgBox "error7." return end if // メソッドを追加(countをXojo側で用意したcountGetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("count"), AddressOf countGetter, "i@:") then msgBox "error8." return end if // メソッドを追加(childrenCountをXojo側で用意したchCountGetterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("countString"), AddressOf chCountGetter, "@@:") then msgBox "error8." return end if // クラスを保持 NSNodeClass = newClassId End Sub
- 以下をNodeにペースト(できなければShared Propertyに、Name:ActionHandler1、Type:ActionDelegate1、を追加)
Private Shared Property ActionHandler1 As ActionDelegate1
- 以下をNodeにペースト(できなければShared Propertyに、Name:ActionHandler2、Type:ActionDelegate2、を追加)
Private Shared Property ActionHandler2 As ActionDelegate2
- 以下をNodeにペースト(できなければShared Propertyに、Name:ActionHandler3、Type:ActionDelegate3、を追加)
Private Shared Property ActionHandler3 As ActionDelegate3
- 以下をNodeにペースト(できなければShared Propertyに、Name:ActionHandler4、Type:ActionDelegate4、を追加)
Private Shared Property ActionHandler4 As ActionDelegate4
- 以下をNodeにペースト(できなければShared Propertyに、Name:ActionHandler5、Type:ActionDelegate5、を追加)
Private Shared Property ActionHandler5 As ActionDelegate5
- 以下をNodeにペースト(できなければShared Propertyに、Name:ActionHandler6、Type:ActionDelegate6、を追加)
Private Shared Property ActionHandler6 As ActionDelegate6
- 以下をNodeにペースト(できなければShared Propertyに、Name:ActionHandler7、Type:ActionDelegate7、を追加)
Private Shared Property ActionHandler7 As ActionDelegate7
- 以下をNodeにペースト(できなければShared Propertyに、Name:NSNodeClass、Type:Ptr、を追加)
Private Shared Property NSNodeClass As Ptr
- 他に、NSMakeRect(メソッド)、NSRect(構造体)が必要ですが、それらはmacoslibからコピーさせて頂きました。(別途モジュールを用意してコピーする。)
注)macoslibではNSMakeRectの引数、NSRectのメンバーの型にSingleが割り当てられているが、64bitに対応するため、CGFloatに書き換える。
おわりに
Cocoa Bindingを利用することで、outlineView:viewForTableColumn:item:以外のDelegate/DataSourceを意識しなくて済むのは楽です。
それから、描画時のバインディングは、とりあえず動作しているようなのでそのまま使ってしまいましたが、実は今ひとつピンときていません。
というのも、バインド先はセルビューなのですが、バインドする変数/メソッドはセルビューではなく、ビューコントローラーからアクセスできるものです。
オリジナルがアウトラインビューからセルビューを呼び出しているのに対し、自作ではセルビューをその場で生成しているので、この時点では両者は紐づけられていません。
それでも機能するということは、セルビューを返した後に紐づけられるとしか考えられませんが、その解釈でいいのかは分かりません。
あと、メモリーリークのチェックにLeaksを使おうとしたのですが、エラーが出て使えなくなっていました。
調べたら、以下のサイトに解決法が示されていました。
実行の結果、Leaksでのメモリーリーク検出はありませんでした。
参考サイト(4):M1. Instrument Leaks error "Failed… | Apple Developer Forums
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):NSTreeController + NSOutlineView: A powerful combination | by Alexander Murphy | Building Ibotta | Medium
参考サイト(2):NSTreeController + NSOutlineView:強力な組み合わせ(上記の日本語翻訳?)
参考サイト(3):objective c - How can I add properties to an object at runtime? - Stack Overflow
参考サイト(4):M1. Instrument Leaks error "Failed… | Apple Developer Forums
更新履歴
2022.03.08 方針に、追記を追加。
2021.10.08 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]