ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでリストボックスのセル種別を増やしてみる・複数で
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでリストボックスのセル種別を増やしてみる・複数で
はじめに
以下は、Xojo Cocoaビルドについての話題です。
前回はメインウィンドウに一つのリストボックスでしたが、複数化を試してみました。
なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + OS X 10.13.6 High Sierra)
方針
複数インスタンス対応は以前にもやりました(こことか、こことか)ので、基本的な考え方は同じです。
ただし、これまでの方式では保持する変数が増えてしまったり、クローズ時の後始末も不十分だったので、もう少しきめ細かく対応することにします。
まずは、様々な通知(Datasource/DelegateメソッドやTarget/Action)を共有メソッドが受け取って、これを各インスタンスメソッドに振り分ける処理ですが、macoslibに倣い、Dictionaryを使うことにします。
macoslibのやり方は巧妙で、Dictionaryはキーも値もVariant型であることを利用して、キーにNSTableViewのインスタンスへのポインター、値にXojoのインスタンスへのポインター、を代入しています。なお、スクロールビューへのポインターは、日常的には使いませんが開放時には必要となるので、これも保持するようにします。
次に、ActionHandlerですが、それ自体は共有プロパティ/(通常の)プロパティどちら側に置いてもいいので、プロパティに置くことにします。(そのためにはインスタンスを特定できる必要がありますが、今回はそれが可能。)これで、個別にアドレスを保持する必要がなくなります。
更に、不要になったメモリー領域の解放については、Xojo側はインスタンスへの参照が全て切れるとDestructorが呼ばれるので、(例えばmsgBox "Done"とかしておいて、)所望のタイミングで呼ばれるかどうかをチェックすることで、確認できます。(注:今回はチェックに使ったのみで、以下の実装では使っていません。)
一方、Cocoa側は、これも以前取り上げた、Xcode付属のInstrumentsの、Leaksを使って確認します。
また、前回はNSTableView生成時にXojoのインスタンスは捨てていましたが、ツールバーからの指示を伝えるのに必要なため、プロパティとして保持することにします。
さて、実装についてですが、ここでは、(1)メインウィンドウに複数のリストボックス、(2)複数ウィンドウに各一つのリストボックス、の2種類を試してみることとしました。
どちらも、前回プロジェクトをベースにします。
主な変更は、Window1のメソッド/プロパティをNSTableViewクラスに移動(ドラッグ&ドロップ)して、あとはインスタンス情報を保持する処理とメモリーを解放する処理を追加します。
以上を踏まえ、それぞれの仕様は以下の通りとしました。
例1(メインウィンドウに複数のリストボックス)
例2(複数ウィンドウに各一つのリストボックス)
- リストボックスを2個置く
- リストボックスの選択は、オプションキーによって行う(キーが押されている場合は二つ目を選択)
- ウィンドウリサイズは考慮しない(リサイズすると位置関係がおかしなことになります。必要なら改変して下さい。)
共通
- メインウィンドウからサブウィンドウを生成
- リストボックスはサブウィンドウに置く
- リストボックスの操作は、各サブウィンドウごとに行う
- リストボックスの仕様(コントロールの種類や、行や列の操作)は前回と同一とする
Xojoでの実装(例1)
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、二つのリストボックスが機能することを確認しました。
- 前回プロジェクトをベースとする
- クラス名を、NSViewListBoxからListBoxTableViewに変更。(注:必須ではない。変更しない場合は、以下のクラス名を読み替えて下さい。)
- 以下のToolbar11のイベントを置き換え(できなければ、Sub - Endの間をToolbar11のActionイベントに記述)
Sub Action(item As ToolItem) Handles Action // OptionKeyが押されていたら、二つ目のリストボックスを選択 Dim cnt As Integer if Keyboard.AsyncOptionKey then cnt=1 else cnt=0 end if // ツールバーで選択された処理を実行 tableInst(cnt).ToolbarAction(Item.Name) End Sub
- 以下をWindow1にペースト(できなければ、Sub - Endの間をCloseイベントに記述)
Sub Close() Handles Close // テーブルビューインスタンスのクローズ処理 tableInst(0).CloseInst() tableInst(1).CloseInst() End Sub
- 以下のWindow1のイベントを置き換え(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open // NSTableViewベースのListBox。引数は順に、配置するウィンドウ、位置/サイズ tableInst(0) = new ListBoxTableView(self, NSMakeRect(16, 16, me.Width-32, 176)) tableInst(1) = new ListBoxTableView(self, NSMakeRect(16, 16+176+16, me.Width-32, 176)) End Sub
- Window1の全てのメソッドをListBoxTableViewにドラッグ移動。
- Window1の全てのプロパティをListBoxTableViewにドラッグ移動。
- 以下をWindow1にペースト(できなければプロパティに、名前:tableInst(1)、データ型:ListBoxTableView、を追加)
Protected Property tableInst(1) as ListBoxTableView
- 以下をWindow1にペースト(できなければプロパティに、名前:tableviewInst(1)、データ型:Ptr、を追加)
Protected Property tableviewInst(1) as Ptr
- 以下をNSViewListBoxにペースト
Public Sub CloseInst() // allocした要素のリリース ResetTable(false) // false = データ領域配列の生成を行わない // ActionHandlerの解放 ActionHandler = nil ActionHandler1 = nil ActionHandler2 = nil ActionHandler3 = nil ActionHandlerT = nil // インスタンス情報の解放 Dim scrollView As Ptr if InstanceDict.HasKey(tableviewInst) then // テーブルビューのスクロールビューのインスタンスを取得できれば scrollView=InstanceDict.Value(tableviewInst) // テーブルビューのスクロールビューのインスタンスを取得 if InstanceDict.HasKey(scrollView) then // テーブルビューのXojo側のインスタンスを取得できれば InstanceDict.Remove(scrollView) // テーブルビューのXojo側のインスタンスを解放 end if InstanceDict.Remove(tableviewInst) // テーブルビューのスクロールビューのインスタンス要素を解放 end if // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(tableviewInst) release(scrollView) End Sub
- 以下をNSViewListBoxにペースト(既存のものを置き換え)
Public Sub Constructor(win As Window, rect As NSRect) rect.y = win.Height - rect.y - rect.h // y座標系が、CocoaとXojoで反転しているので、補正 // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 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:" (obj_id As Ptr, frame As NSRect) As Ptr // NSTableViewを継承したカスタムクラスを作成。初回のみ makeClass() // NSTableViewのインスタンス作成 Dim tableView As Ptr = initWithFrame(alloc(NSTableViewClass), NSMakeRect(0, 0, 0, 0)) // rectを指定すると、スクロール時に描画が乱れる(ヘッダーずれ他) Declare Sub setFocusRingType Lib "Cocoa" Selector "setFocusRingType:" (receiver As Ptr, typ As Integer) setFocusRingType(tableView, 1) // 1 = NSFocusRingTypeNone(フォーカスリングなし) Declare Sub setUsesAlternatingRow Lib "Cocoa" Selector "setUsesAlternatingRowBackgroundColors:" (receiver As Ptr, flg As Boolean) setUsesAlternatingRow(tableView, true) // 1行ごとに背景色を変える Declare Sub setRowHeight Lib "Cocoa" Selector "setRowHeight:" (receiver As Ptr, typ As CGFloat) setRowHeight(tableView, 22.0) // 行の高さ Declare Sub setSelectionHighlightStyle Lib "Cocoa" Selector "setSelectionHighlightStyle:" (receiver As Ptr, typ As Integer) setSelectionHighlightStyle(tableView, -1) // -1 = NSTableViewSelectionHighlightStyleNone(行のハイライト表示を行わない) Declare Sub setColumnAutoresizingStyle Lib "Cocoa" Selector "setColumnAutoresizingStyle:" (receiver As Ptr, typ As Integer) setColumnAutoresizingStyle(tableView, 0) // 0 = NSTableViewNoColumnAutoresizing(列幅の自動リサイズを行わない) // 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 // DataSource, Delegateの設定 Declare Sub setDataSource Lib "Cocoa" Selector "setDataSource:" (receiver As Ptr, obj As Ptr) setDataSource(tableView, tableView) Declare Sub setDelegate Lib "Cocoa" Selector "setDelegate:" (receiver As Ptr, obj As Ptr) setDelegate(tableView, tableView) // スクロールビューにテーブルビューをセット Declare Sub setDocumentView Lib "Cocoa" Selector "setDocumentView:" (receiver As Ptr, obj As Ptr) setDocumentView(scrollView, tableView) // ウィンドウのビューを取得 Declare Function contentView Lib "Cocoa" selector "contentView" (class_id As Integer) As Ptr Dim pnt2 As Ptr = contentView(win.handle) // スクロールビューをウィンドウのビューに追加 Declare Sub addSubview Lib "Cocoa" selector "addSubview:" (class_id As Ptr, view As Ptr) addSubview(pnt2, scrollView) // テーブルビューのインスタンスを保持 tableviewInst = tableView // Window側でActionを受け取るメソッドを登録 ActionHandler = AddressOf actionEvent ActionHandler1 = AddressOf viewForTable ActionHandler2 = AddressOf sortDescriptors ActionHandler3 = AddressOf didClickTableColumn ActionHandlerT = AddressOf textEndEdit // 共有メソッドがインスタンスを特定できるように、自身を登録 if InstanceDict=nil then InstanceDict=new Dictionary end if InstanceDict.Value(tableView)=scrollView // NSTableViewのポインターをキーに、NSScrollViewのインスタンスを登録 InstanceDict.Value(scrollView)=me // NSScrollViewのポインターをキーに、自身(Xojoのインスタンス)を登録 // 内部データの初期化 InitDataArray() End Sub
- 以下をNSViewListBoxにペースト
Protected Sub ReleaseDataArray() // 参照数の取得 Declare Function retainCount Lib "Cocoa" selector "retainCount" (class_id As Ptr) As Integer Dim cnt As Integer = retainCount(dataArrayNS) // 参照があったら、解放する if cnt>0 then // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(dataArrayNS) end if End Sub
- 以下をNSViewListBoxにペースト
Public Sub ToolbarAction(name As String) select case name case "ToolItem1" LoadFile() case "ToolItem2" SaveFile() case "ToolItem4" Dim colCnt As Integer = GetColCount() Dim rowData(-1) As String ReDim rowData(colCnt) // 列数分の空の行データ AddRow(rowData) case "ToolItem5" AddCol("col "+Str(GetColMaxVal()),150,"text") end select End Sub
- 以下をNSViewListBoxにペースト
Protected Shared Function getInstFromID(id As Ptr) as ListBoxTableView // CocoaのインスタンスからXojoのインスタンスを特定する if InstanceDict.HasKey(id) then Dim scrollView As Ptr = InstanceDict.Value(id) // 最初の値はスクロールビューへのポインター if InstanceDict.HasKey(scrollView) then return InstanceDict.Value(scrollView) // 次がXojoのインスタンス else return nil end if else return nil end if End Function
- ActionHandler、ActionHandler1、ActionHandler2、ActionHandler3、ActionHandlerT、を共有プロパティからプロパティに変更(注:コンテキストメニューから変更可)
- 以下をNSViewListBoxにペースト(できなければ共有プロパティに、名前:InstanceDict、データ型:Dictionary、を追加)
Public Shared Property InstanceDict as Dictionary
Xojoでの実装(例2)
実行してみたところ、複数ウィンドウに配置したリストボックスが機能することを確認しました。
- 前回プロジェクトをベースとする
- クラス名を、NSViewListBoxからListBoxTableViewに変更。
- Window1の全てのメソッドをListBoxTableViewにドラッグ移動。
- Window1の全てのプロパティをListBoxTableViewにドラッグ移動。
- Window1に、PushButton(Name:PushButton1)を追加
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action Dim a As new Window2 End Sub
- 新規ウィンドウ(名前は、ここではデフォルトの「Window2」)を作成。
- Window2にToolbar1をドロップ
- 以下をToolbar11にペースト(できなければ、Sub - Endの間をToolbar11のActionイベントに記述)
Sub Action(item As ToolItem) Handles Action // ツールバーで選択された処理を実行 tableInst.ToolbarAction(Item.Name) End Sub
- 以下をWindow2にペースト(できなければ、Sub - Endの間をCloseイベントに記述)
Sub Close() Handles Close // テーブルビューインスタンスのクローズ処理 tableInst.CloseInst() End Sub
- 以下をWindow2にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open // NSTableViewベースのListBox。引数は順に、配置するウィンドウ、位置/サイズ tableInst = new ListBoxTableView(self, NSMakeRect(16, 16, me.Width-32, me.Height-32)) End Sub
- 以下をWindow2にペースト(できなければプロパティに、名前:tableInst、データ型:ListBoxTableView、を追加)
Protected Property tableInst as ListBoxTableView
- 以下をNSViewListBoxにペースト
Public Sub CloseInst() // allocした要素のリリース ResetTable(false) // false = データ領域配列の生成を行わない // ActionHandlerの解放 ActionHandler = nil ActionHandler1 = nil ActionHandler2 = nil ActionHandler3 = nil ActionHandlerT = nil // インスタンス情報の解放 Dim scrollView As Ptr if InstanceDict.HasKey(tableviewInst) then // テーブルビューのスクロールビューのインスタンスを取得できれば scrollView=InstanceDict.Value(tableviewInst) // テーブルビューのスクロールビューのインスタンスを取得 if InstanceDict.HasKey(scrollView) then // テーブルビューのXojo側のインスタンスを取得できれば InstanceDict.Remove(scrollView) // テーブルビューのXojo側のインスタンスを解放 end if InstanceDict.Remove(tableviewInst) // テーブルビューのスクロールビューのインスタンス要素を解放 end if // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(tableviewInst) release(scrollView) End Sub
- 以下をNSViewListBoxにペースト(既存のものを置き換え)
Public Sub Constructor(win As Window, rect As NSRect) rect.y = win.Height - rect.y - rect.h // y座標系が、CocoaとXojoで反転しているので、補正 // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 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:" (obj_id As Ptr, frame As NSRect) As Ptr // NSTableViewを継承したカスタムクラスを作成。初回のみ makeClass() // NSTableViewのインスタンス作成 Dim tableView As Ptr = initWithFrame(alloc(NSTableViewClass), NSMakeRect(0, 0, 0, 0)) // rectを指定すると、スクロール時に描画が乱れる(ヘッダーずれ他) Declare Sub setFocusRingType Lib "Cocoa" Selector "setFocusRingType:" (receiver As Ptr, typ As Integer) setFocusRingType(tableView, 1) // 1 = NSFocusRingTypeNone(フォーカスリングなし) Declare Sub setUsesAlternatingRow Lib "Cocoa" Selector "setUsesAlternatingRowBackgroundColors:" (receiver As Ptr, flg As Boolean) setUsesAlternatingRow(tableView, true) // 1行ごとに背景色を変える Declare Sub setRowHeight Lib "Cocoa" Selector "setRowHeight:" (receiver As Ptr, typ As CGFloat) setRowHeight(tableView, 22.0) // 行の高さ Declare Sub setSelectionHighlightStyle Lib "Cocoa" Selector "setSelectionHighlightStyle:" (receiver As Ptr, typ As Integer) setSelectionHighlightStyle(tableView, -1) // -1 = NSTableViewSelectionHighlightStyleNone(行のハイライト表示を行わない) Declare Sub setColumnAutoresizingStyle Lib "Cocoa" Selector "setColumnAutoresizingStyle:" (receiver As Ptr, typ As Integer) setColumnAutoresizingStyle(tableView, 0) // 0 = NSTableViewNoColumnAutoresizing(列幅の自動リサイズを行わない) // 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 // DataSource, Delegateの設定 Declare Sub setDataSource Lib "Cocoa" Selector "setDataSource:" (receiver As Ptr, obj As Ptr) setDataSource(tableView, tableView) Declare Sub setDelegate Lib "Cocoa" Selector "setDelegate:" (receiver As Ptr, obj As Ptr) setDelegate(tableView, tableView) // スクロールビューにテーブルビューをセット Declare Sub setDocumentView Lib "Cocoa" Selector "setDocumentView:" (receiver As Ptr, obj As Ptr) setDocumentView(scrollView, tableView) // ウィンドウのビューを取得 Declare Function contentView Lib "Cocoa" selector "contentView" (class_id As Integer) As Ptr Dim pnt2 As Ptr = contentView(win.handle) // スクロールビューをウィンドウのビューに追加 Declare Sub addSubview Lib "Cocoa" selector "addSubview:" (class_id As Ptr, view As Ptr) addSubview(pnt2, scrollView) // テーブルビューのインスタンスを保持 tableviewInst = tableView // Window側でActionを受け取るメソッドを登録 ActionHandler = AddressOf actionEvent ActionHandler1 = AddressOf viewForTable ActionHandler2 = AddressOf sortDescriptors ActionHandler3 = AddressOf didClickTableColumn ActionHandlerT = AddressOf textEndEdit // 共有メソッドがインスタンスを特定できるように、自身を登録 if InstanceDict=nil then InstanceDict=new Dictionary end if InstanceDict.Value(tableView)=scrollView // NSTableViewのポインターをキーに、NSScrollViewのインスタンスを登録 InstanceDict.Value(scrollView)=me // NSScrollViewのポインターをキーに、自身(Xojoのインスタンス)を登録 // 内部データの初期化 InitDataArray() End Sub
- 以下をNSViewListBoxにペースト
Protected Sub ReleaseDataArray() // 参照数の取得 Declare Function retainCount Lib "Cocoa" selector "retainCount" (class_id As Ptr) As Integer Dim cnt As Integer = retainCount(dataArrayNS) // 参照があったら、解放する if cnt>0 then // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(dataArrayNS) end if End Sub
- 以下をNSViewListBoxにペースト
Public Sub ToolbarAction(name As String) select case name case "ToolItem1" LoadFile() case "ToolItem2" SaveFile() case "ToolItem4" Dim colCnt As Integer = GetColCount() Dim rowData(-1) As String ReDim rowData(colCnt) // 列数分の空の行データ AddRow(rowData) case "ToolItem5" AddCol("col "+Str(GetColMaxVal()),150,"text") end select End Sub
- 以下をNSViewListBoxにペースト
Protected Shared Function getInstFromID(id As Ptr) as ListBoxTableView // CocoaのインスタンスからXojoのインスタンスを特定する if InstanceDict.HasKey(id) then Dim scrollView As Ptr = InstanceDict.Value(id) // 最初の値はスクロールビューへのポインター if InstanceDict.HasKey(scrollView) then return InstanceDict.Value(scrollView) // 次がXojoのインスタンス else return nil end if else return nil end if End Function
- ActionHandler、ActionHandler1、ActionHandler2、ActionHandler3、ActionHandlerT、を共有プロパティからプロパティに変更(注:コンテキストメニューから変更可)
- 以下をNSViewListBoxにペースト(できなければ共有プロパティに、名前:InstanceDict、データ型:Dictionary、を追加)
Public Shared Property InstanceDict as Dictionary
おわりに
NSViewListBoxのSuperをRectControlにすると、IDE上でドロップが可能になります。
両者(RectControlとNSTableView)をうまいこと組み合わせられれば、より使い勝手が向上しますが、それで問題ないかは検証が必要かと思われます。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
更新履歴
2019.08.02 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]