ホームページ開発ツール>Xojo / Real Studio / REALbasic コラム(旧その1)

Xojo / Real Studio / REALbasic コラム(旧その1)


 ○Xojo 2017r1.1 多言語版
 ○Xojo 2016r3 多言語版
 ○Xojo 2016r1.1 多言語版
 ○Xojo 2015r4.1 多言語版
 ○Xojo 2015r3 多言語版
 ○Xojo 2014r3.1 多言語版
 ○Xojo 2014r2 多言語版
 ○Xojo 2014r1 多言語版
 ○Real Studio 2011r3 以前のコラムはこちら
 Declareで自作dylibを使う(Cocoa)

 DeclareのLibraryNameで指定するものは、一般的にはFrameworkですが、他にdylibも指定できるということがリファレンスに書かれています。
 これは即ち、自作のライブラリもDeclareで呼び出せる、ということになります。

 自作dylibは、(当然ながら)システム側からは提供されませんので、リリース時はアプリケーション内に自分で納める必要がありますが、置き場所としては、既存のアプリを見る限り、Frameworksフォルダ内が妥当なようです。

 以下は、プラグインの実験に使った、文字列の連結を再現するサンプルです。
 まずはXcodeでdylibを作ります。
  1. Xcodeで新規プロジェクトを作成(テンプレートダイアログで「Library」を選択。ファイル名はここでは「MyFunction」、Frameworkはここでは「Cocoa」)
  2. 以下をMyFunction.mにペースト
    NSString* myCatString( NSString* str1, NSString* str2 ) {
    	NSString* str3 = [NSString stringWithFormat:@"%@ %@",str1, str2];
    	return str3;
    }
    
  3. ビルド
  4. 出来上がったlibMyFunction.dyllibを、以下で作成するXojoのプロジェクトと同じフォルダ内に置く
    (注:ビルドしたdyllibを直接参照する場合は、下記Declare文のLibraryNameを一致させる)
 次にXojoでアプリケーションを作ります。
  1. Xojoで新規プロジェクトを作成
  2. Window1に、PushButton(Name:PushButton1)を追加
  3. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      #if DebugBuild  // デバッグ時にはプロジェクトと同じフォルダ内にあるものを使う。
        Declare Function myCatString Lib "@executable_path/../../../libMyFunction.dylib" (str1 As CFStringRef, str2 As CFStringRef) As CFStringRef
      #else  // リリース時にはアプリケーションパッケージ内のFrameworksフォルダ内にあるものを使う。ビルド後に何らかの手段でコピーしておく。
        Declare Function myCatString Lib "@executable_path/../Frameworks/libMyFunction.dylib" (str1 As CFStringRef, str2 As CFStringRef) As CFStringRef
      #endif
      msgBox myCatString("Hello","World")
    End Sub
    
 テンプレートダイアログのFrameworkは「Cocoa」の他に、内容によっては「None (Plain C/C++ Library)」を選択することもできます。
 デバッグ効率を考えると多用するのは難しいかもしれませんが、選択肢の一つにはなるかも。
 戻る  先頭へ


 アイコン画像にエイリアスバッジを付加する(Cocoa)

 Finderでは、エイリアス(シンボリックリンク含む)にはアイコン画像の左下に、そうであることを示すマーク(エイリアスバッジ:S Shot2 )が付加されます。
 このエイリアスバッジは、(多分)/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AliasBadgeIcon.icnsです。
 矢印部分以外は透明なので、アイコン画像に重ね書きできます。

 エイリアスであるかどうかは、TrueChildまたはTrueItemメソッドのAliasプロパティで分かるのですが、取得は親階層から行うことになります。
(対象フォルダアイテムを直接取得してしまうと、その時点でエイリアスが解決されてしまい、以降の確認手段がなくなる。)
// NG例(デスクトップにDataというフォルダのエイリアスが置かれている場合)
Dim NG1 as Boolean = GetFolderItem("/Users/hoge/Desktop/Data",3).Alias
Dim NG2 as Boolean = GetFolderItem("/Users/hoge/Desktop/Data",3).Parent.TrueChild("Data").Alias
// OK例(同上)
Dim OK1 as Boolean = GetFolderItem("/Users/hoge/Desktop",3).TrueChild("Data").Alias
Dim OK2 as Boolean = GetFolderItem("/Users/hoge/Desktop",3).TrueItem(n).Alias  // n = "Data"の配列要素番号
 一個下の話題を踏まえた対応例は、以下の通りです。
  1. こちらのプロジェクトを使う等してAliasBadgeIcon.icnsから16x16サイズの画像を抜き出す。(ファイル名はここではAliasBadgeIcon16.pngとした。)
  2. AliasBadgeIcon16.pngをプロジェクトにドロップ
  3. NSImageToPictureの引数にisAlias As Booleanを追加
  4. NSImageToPictureの最後(return pの手前)に以下を追加
    // エイリアスの場合はバッジをつける
    if isAlias then
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // バッジ画像の取得(プロジェクト内に取り込み済)(image2をあらかじめ取得しておき、プロパティで保持して使い回してもよい)
      Dim image2 As Ptr = NSClassFromString("NSImage")
      Declare Function imageNamed Lib "Cocoa" Selector "imageNamed:" (receiver As Ptr, name As CFStringRef) As Ptr
      image2 = imageNamed(image2, "aliasBadgeIcon16.png")
      
      // バッジを重ね書き
      Dim cgPtr2 As ptr = CGImageForProposedRect(image2, rect, nil, nil)
      CGContextDrawImage(cntx, rect, cgPtr2)
    end if
    
  5. NSImageToPicture呼び出し側に、isAlias判定と結果を引数で渡す処理を追加
 戻る  先頭へ


 アイコン画像を高速に取得する(Cocoa)

 アイコンの取得は既にこちらで行なっていますが、この方法では取得に時間が掛かることが分かっています。
 同プロジェクトのように、まずフォルダアイテムを指定してサイズリストを作り、その後個別のアイコン画像を表示するような場合は、遅いことがあまり問題にはならないのでいいのですが、例えば、右側のリストの各アイテムにアイコン画像を付加したい場合には、遅さが問題になってきます。

 調べたところ、どうもTIFFRepresentationがネックのようでした。
 なので、使わずに済む方法がないか調べたところ、以下がヒットしました。
 参考サイト:Dealing with NSImage - Xojo Forum(Carlo R氏のSep 8の投稿)
注)実験したところ、このやり方では最小サイズのアイコンが返ってくるようです。より大きなサイズで取得したい場合は、別途検証が必要かと思われます。
 Answer coming.の後にコードが記述されていますので、(例によって)Public Function〜End Functionをペーストすれば、メソッドとして復元されます。
(引数のextendsは、必要がなければ省略可能)
 NSImageの取得部分を含むラッパーは、例えばこんな感じ。(引数のpathは、アイコン表示対象FolderItemのNativePath)
Private Function GetIconPicture(path As String) as Picture
  // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
  Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
  
  // 指定されたファイルへのパスからイメージを取得
  Dim wspace As Ptr = NSClassFromString("NSWorkspace")
  Declare Function sharedWorkspace Lib "Cocoa" Selector "sharedWorkspace" (receiver As Ptr) As Ptr
  wspace = sharedWorkspace(wspace)
  Declare Function iconForFile Lib "Cocoa" Selector "iconForFile:" (receiver As Ptr, path As CFStringRef) As Ptr
  Dim image As Ptr = iconForFile(wspace, path)
  
  // イメージをピクチャーに変換して返す(ここでは16x16ピクセルの画像とするが、15x15ピクセル程度の方が収まりがいいかも)
  return NSImageToPicture(image,16,16)
End Function
 戻る  先頭へ


 マルチページTIFFと相互変換する(Cocoa)

 Xojo Cocoa 64bit Plugin 覚書 応用編1で触れた、マルチページTIFFの話題続きです。
 一般的な(即ち、シングルページの)画像ファイルとマルチページTIFFファイルの相互変換について、調べてみました。
 以下は、デスクトップ上のpage1.png、page2.pngの2枚の画像を、マルチページTIFF化して出力するサンプルです。(圧縮方式はLZWとしています。)
Private Sub CombineTIFF()
  // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
  Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
  
  // 配列の初期化
  Dim irepArray As Ptr = NSClassFromString("NSMutableArray")  // クラスメソッドなので、まずNSMutableArrayクラスを取得
  Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr
  irepArray = getArray(irepArray)
  
  Declare Function imageRepWithContentsOfFile Lib "Cocoa" Selector "imageRepWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As ptr
  Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As Ptr)
  
  // ページ配列の生成
  Dim imgrep As Ptr
  for i As Integer = 0 to 1
    
    // ファイルからImageRepを取得
    imgrep = NSClassFromString("NSImageRep")  // クラスメソッドなので、まずNSImageRepクラスを取得
    imgrep = imageRepWithContentsOfFile(imgrep, "/Users/hoge/Desktop/page"+Str(i+1)+".png")
    
    // ImageRepを配列に追加
    addObject(irepArray, imgrep)
    
  next
  
  // ページ配列からData生成
  Dim birep As Ptr = NSClassFromString("NSBitmapImageRep")  // クラスメソッドなので、まずNSBitmapImageRepクラスを取得
  Declare Function TIFFRepresentationOfImageRepsInArray Lib "Cocoa" Selector "TIFFRepresentationOfImageRepsInArray:usingCompression:factor:" (receiver As Ptr, ary As Ptr, comp As UInteger, fact As CGFloat) As Ptr
  Dim data As Ptr = TIFFRepresentationOfImageRepsInArray(birep, irepArray, 5, 0.0)  // return NSData*, 5 = NSTIFFCompressionLZW
  
  // Dataをファイルに書き出し
  Declare Sub writeToFile Lib "Cocoa" Selector "writeToFile:atomically:" (receiver As Ptr, path As CFStringRef, atm As Boolean)
  writeToFile(data, "/Users/hoge/Desktop/multi.tiff", true)
End Sub
 以下は、デスクトップ上のmulti.tiff(2ページからなるマルチページTIFF)を、シングルページに分解して、PNG形式で出力するサンプルです。
Private Sub SeparateTIFF()
  // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
  Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
  
  // ファイルからImageRep配列を取得
  Dim imgreps As Ptr = NSClassFromString("NSImageRep")  // クラスメソッドなので、まずNSImageRepクラスを取得
  Declare Function imageRepsWithContentsOfFile Lib "Cocoa" Selector "imageRepsWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As ptr
  imgreps = imageRepsWithContentsOfFile(imgreps, "/Users/hoge/Desktop/multi.tiff")
  
  // falseをNSNumber形式に変換
  Dim numb As Ptr = NSClassFromString("NSNumber")  // クラスメソッドなので、まずNSNumberクラスを取得
  Declare Function numberWithBool Lib "Cocoa" Selector "numberWithBool:" (receiver As Ptr, path As Boolean) As Ptr
  numb = numberWithBool(numb, false)
  
  // PNG用オプションのセット
  Dim dict As Ptr = NSClassFromString("NSDictionary")  // クラスメソッドなので、まずNSDictionaryクラスを取得
  Declare Function dictionaryWithObject Lib "Cocoa" Selector "dictionaryWithObject:forKey:" (receiver As Ptr, objt As Ptr, key As CFStringRef) As Ptr
  dict = dictionaryWithObject(dict, numb, "NSImageInterlaced")
  
  Declare Function objectAtIndex Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As Ptr
  Declare Function TIFFRepresentation Lib "Cocoa" Selector "TIFFRepresentation" (receiver As Ptr) As Ptr
  Declare Function imageRepWithData Lib "Cocoa" Selector "imageRepWithData:" (receiver As Ptr, data As Ptr) As ptr
  Declare Function representationUsingType Lib "Cocoa" Selector "representationUsingType:properties:" (receiver As Ptr, type As Integer, prop As Ptr) As Ptr
  Declare Sub writeToFile Lib "Cocoa" Selector "writeToFile:atomically:" (receiver As Ptr, path As CFStringRef, atm As Boolean)
  
  // ページごとの処理
  Dim irep, data, bitmapImageRep, pngData As Ptr
  for i As Integer = 0 to 1
    
    // ImageRep配列からページごとのImageRepを取得
    irep = objectAtIndex(imgreps, i)
    
    // ImageRepからData生成
    data = TIFFRepresentation(irep)  // return NSData*
    
    // DataからBitmapImageRep生成
    bitmapImageRep = NSClassFromString("NSBitmapImageRep")  // クラスメソッドなので、まずNSBitmapImageRepクラスを取得
    bitmapImageRep = imageRepWithData(bitmapImageRep, data)
    
    // BitmapImageRepからPNG形式のData生成
    pngData = representationUsingType(bitmapImageRep, 4, dict)  // 4 = NSPNGFileType
    
    // Dataをファイルに書き出し
    writeToFile(pngData, "/Users/hoge/Desktop/page"+Str(i+1)+".png", true)
    
  next
End Sub
 戻る  先頭へ


 任意のアプリが64bitか32bitかを確認する【10.14以前】(Cocoa)

 64bit/32bitの確認方法としては、システムレポートやアクティビティモニタが一般的ではありますが、目の前にあるこのアプリがどっちなのかを判別したい場合には、もう少し手軽なものがあると便利です。で、こういう場合は、自作するのが手っ取り早いかと。
(注:引数のpathは、アプリのネイティブパス)
Private Sub CheckArchitecture(path As String)
  // NSBundleExecutableArchitectureI386 = 7
  // NSBundleExecutableArchitecturePPC = &h12
  // NSBundleExecutableArchitectureX86_64 = &h01000007
  // NSBundleExecutableArchitecturePPC64 = &h01000012
  
  // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
  Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
  
  // バンドルを取得
  Dim bndl As Ptr = NSClassFromString("NSBundle")
  Declare Function bundleWithPath Lib "Cocoa" Selector "bundleWithPath:" (receiver As Ptr, path As CFStringRef) As Ptr  // Return NSBundle
  bndl = bundleWithPath(bndl, path)
  
  // 実行可能なアーキテクチャを取得
  Declare Function executableArchitectures Lib "Cocoa" Selector "executableArchitectures" (receiver As Ptr) As Ptr  // Return NSArray
  Dim ary As Ptr = executableArchitectures(bndl)
  
  // アーキテクチャ数を取得
  Dim cnt As Integer
  Declare Function aCount lib "Cocoa" Selector "count" (obj_id As Ptr) As Integer
  cnt = aCount(ary)
  
  // アーキテクチャを文字で表現
  Dim num As Ptr
  Dim i, v1 As Integer
  Dim st As String = ""
  Declare Function objectAtIndex lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As Ptr  // Return NSNumber
  Declare Function intValue Lib "Cocoa" Selector "intValue" (receiver As Ptr) As Integer
  for i=0 to cnt-1
    num = objectAtIndex(ary, i)
    v1 = intValue(num)
    select case v1
    case 7  // Intel 32bit
      if st<>"" then st=st+", "
      st=st+"Intel 32bit"
    case &h12  // PPC 32bit
      if st<>"" then st=st+", "
      st=st+"PPC 32bit"
    case &h01000007  // Intel 64bit
      if st<>"" then st=st+", "
      st=st+"Intel 64bit"
    case &h01000012  // PPC 64bit
      if st<>"" then st=st+", "
      st=st+"PPC 64bit"
    end select
  next
  
  MsgBox st
End Sub
 戻る  先頭へ


 AppleScriptをシェルから実行する【10.14以降】(ネイティブ)

(注:以下の内容は2016r3で発生するものであり、(確認した範囲では)2018r1.1では問題ないようです。)
 macOS 10.14以降、Finderを操作するAppleScriptの中に、動作しないものがあることが分かりました。
 エラーコードを調べると、-1743でした。検索すると、以下がヒットしました。
 参考サイト:macOS 10.14で新設されたエラーコード-1743を確認する – AppleScriptの穴
 Xojoでは、AppleScriptのソースファイルをIDEウィンドウ左ペインにドロップすると、ファイル名で呼び出すことができるのですが、このやり方だと、一部の処理(注1)はダメなようです。そこで、シェルを噛ましてみたところ、メッセージが表示されて、OKを押すと実行されるようになりました。
注1)NGを確認済は、ファイルをゴミ箱に移動/ディスクをイジェクト。OKを確認済は、Finderでファイルを表示。
 以下は、ファイルをゴミ箱に移動する処理の一例です。
Public Sub DoAppleScript()
  // IDEにドロップしたAppleScriptソースは、Resourcesフォルダ内に置かれるので、そこから取得
  Dim f As FolderItem = App.ExecutableFile.Parent.Parent.Child("Resources").Child("deleteFolderItem.scpt")
  if f=nil or f.Exists=false then
    msgBox "file not found."
    return
  end if
  
  // 削除対象ファイル(例)
  Dim delFilePath As String = "/Users/hoge/Desktop/test.txt"
  
  // シェルを生成して、AppleScript実行
  Dim s As New Shell
  s.Execute("osascript """+f.NativePath+""" """+delFilePath+"""")
  
  if s.ErrorCode = 0 then
  else
    msgBox("error code: " + Str(s.ErrorCode))
  end if
End Sub
 以下は、deleteFolderItem.scptの内容です。
on run {thePath}
	try
		tell application "Finder"
			set theFile to thePath as POSIX file
			delete theFile
		end tell
	on error number n
		display dialog "ERROR: " & n
	end try
end run
 戻る  先頭へ


 ポインター型と整数型を相互に変換する(ネイティブ)

 こちらの記事でも使いましたが、都合がいい場合もあるので。
Public Function ptrToInt(pnt As Ptr) as UInteger
  // Pointer to UInteger
  #if Target32Bit
    Dim m As new MemoryBlock(4)
    m.Ptr(0)=pnt
    return m.UInt32Value(0)
  #endif
  #if Target64Bit
    Dim m As new MemoryBlock(8)
    m.Ptr(0)=pnt
    return m.UInt64Value(0)
  #endif
End Function
Public Function intToPtr(vv As UInteger) as Ptr
  // UInteger to Pointer
  #if Target32Bit
    Dim m As new MemoryBlock(4)
    m.UInt32Value(0)=vv
    return m.Ptr(0)
  #endif
  #if Target64Bit
    Dim m As new MemoryBlock(8)
    m.UInt64Value(0)=vv
    return m.Ptr(0)
  #endif
End Function
 戻る  先頭へ


 メソッドのタイプエンコーディングを取得する(Cocoa)

 Objective-CのランタイムAPIを用いた動的クラス生成においては、メソッドに対してタイプエンコーディングを指定する必要があります。
(注:タイプエンコーディングについては、こちらを参照。)
 経験的には適当で良さそう(?)なのですが、厳密に指定しておきたい場合は、以下のようにして取得できます。
(注:macoslibでは、後述するプロトコルも含め、もっと詳細に情報を取得しています。そちらを使わせて頂いた方がいいかもしれません。)
  1. Xojoで新規プロジェクトを作成
  2. Window1に、Listbox(Name:Listbox1、ColumnCount:2)、PushButton(Name:PushButton1)、TextField(Name:TextField1)を追加
  3. 以下をListbox1にペースト(できなければ、Function - Endの間をCellClickイベントに記述)
    Function CellClick(row as Integer, column as Integer, x as Integer, y as Integer) Handles CellClick as Boolean
      Me.ColumnType(column) = ListBox.TypeEditableTextField
      Me.EditCell(row, column)
      Return True
    End Function
    
  4. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // まず、全行削除
      Listbox1.DeleteAllRows
      // リスト取得
      GetList()
    End Sub
    
  5. 以下をWindow1に追加
    Public Sub GetList()
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // クラスを取得
      Dim classId As Ptr = NSClassFromString(TextField1.Text)
      
      // オフセット値の設定(アーキテクチャ対応)
      Dim byteCnt As Integer
      #if Target64Bit then
        byteCnt = 8
      #endif
      #if Target32Bit then
        byteCnt = 4
      #endif
      
      // リストの取得(戻り値はポインターの配列)
      Dim cnt As Integer
      Dim list As Ptr  // 領域は当該メソッド側で確保している筈なので、先頭ポインター分だけでいい?
      Declare Function class_copyMethodList Lib "Cocoa" (cls As Ptr, byRef cnt As Integer) As Ptr
      list = class_copyMethodList(classId, cnt)
      
      Declare Function method_getName Lib "Cocoa" (cls As Ptr) As CString
      Declare Function method_getTypeEncoding Lib "Cocoa" (cls As Ptr) As CString
      
      // リストの表示
      Dim pnt As Ptr
      for i As Integer = 0 to cnt-1
        pnt = list.Ptr(i*byteCnt)  // オフセット分ずつスライド
        listbox1.addRow ""  // 行を追加
        listbox1.Cell(i,0) = method_getName(pnt)  // メソッド名
        listbox1.Cell(i,1) = method_getTypeEncoding(pnt)  // タイプエンコーディング
      next
      
      // リスト領域の解放
      Declare Sub free Lib "System" (p As Ptr)
      free(list)
      
      // 昇順にソート
      ListBox1.ColumnSortDirection(0) = ListBox.SortAscending
      ListBox1.SortedColumn = 0 // first column is the sort column
      ListBox1.Sort
    End Sub
    
 テキストフィールドにクラス名(例えばNSControl)を書いてボタンを押すと、メソッド名とタイプエンコーディングのリストを取得できます。
 当該メソッド行のセルをクリックすると、内容をコピーできます。

 なお、プロトコル(例えばNSTableViewDelegate)は上記のやり方では取得できません。以下(またはmacoslib)が参考になります。
 参考サイト:API returning a C Array - how to read Objc.protocol_copymethoddescriptionList? - Xojo Forum(Eli O氏の投稿)
(注:数カ所ビルドエラーが出るが、容易に対応可。また、構造体メンバーのSELはPtr型になっているが、CString型にしておく方が使い勝手がいい。)
 戻る  先頭へ


 アドレスをインクリメントする(ネイティブ)

 滅多にないこととは思いますが、(MemoryBlock等の)アドレスをインクリメントしたい場合もあろうかと思われます。
 どうやるのか疑問だったのですが、たまたま別件でググっていたら、以下がヒットしました。
 参考サイト:Pointer math - Xojo Forum
 例えば、以下のコードでcontinueにブレイクポイントを設定し、デバッガでpnt1を調べると様子が分かります。
Dim mb As New MemoryBlock(10)
Dim pnt1 As Ptr = mb
for x As Integer = 0 to 4
    pnt1 = pnt1 + Ptr(1)
    continue  // for Break Point
next
 なお、Ptr(1)をPtr(n)として、n個分、一気にオフセットすることも可能です。
 戻る  先頭へ


 バイト列をMemoryBlockにコピーする(ネイティブ)

 Xojo / Real Studio Trial and Error の記事(こちらこちらこちら)中で、バイト列をMemoryBlockにコピーする処理があり、そこでは1バイトずつコピーする方法を採っていたのですが、Xojo.Core.MemoryBlockを使えば、一括コピーできることを知りました。
 ただし、結果を利用する処理がMemoryBlockしか受け付けない場合は、Xojo.Core.MemoryBlockからMemoryBlockに変換する必要が生じます。
 1バイトずつのコピーよりは効率がいいのかもしれませんが、ちょっと煩雑になってしまいます。
Dim bstream As Ptr = …  // バイト列
Dim lng As Integer = …  // バイト列の長さ

// バイト列をMemoryBlockに1バイトずつコピー
Dim i As Integer
Dim mb As new MemoryBlock(lng)
for i = 0 to lng-1
    mb.Byte(i) = bstream.Byte(i)
next
Dim bstream As Ptr = …  // バイト列
Dim lng As Integer = …  // バイト列の長さ

// バイト列をXojo.Core.MemoryBlockに格納後、MemoryBlockに変換
Dim xmb As new Xojo.Core.MemoryBlock(bstream, lng)  // Xojo.Core.MemoryBlockだけで処理が完結する場合は、この行だけでOK
Dim temp As MemoryBlock = xmb.Data
Dim mb As New MemoryBlock(xmb.Size)
mb.StringValue(0, mb.Size) = temp.StringValue(0, mb.Size)
 注)MemoryBlockへの変換には、言語リファレンス>Xojo.Core.MemoryBlockのサンプルを使わせて頂きました。
 戻る  先頭へ


 IDEスクリプトでリリース用アプリケーションを削除する(ネイティブ)

 前回(下の項目)の続き。
 最新版では解決しているようですが、2016r3では「ビルド」時に既存のアプリケーション(HOGE.app)が削除されずに、「Compilation of "HOGE" failed.」となってしまい、手作業で捨てなければならないない、という問題が起きることがあります。

 この場合は、デバッグ時と同様、ビルド設定にIDEスクリプトを追加してBehavior>Applies toをReleaseに設定すれば、同様に処理することはできるのですが、32bitと64bitを両方ビルドする場合は、スクリプト内で32bit/64bitを条件分岐するのが無理そうな点や、Architectureを切り替えなければならない点とか、多少厄介なことになりそうです。
 上記を踏まえ、かつ、ビルドは使用頻度が低い点を考慮すると、外部IDEスクリプトを使う方が便利そうなので、試してみました。
  1. メニューから、ファイル>IDE スクリプト>新規 IDE スクリプトを選択
  2. 以下をスクリプトエディタにペースト(ここでは64bitのビルドを想定。)
    Dim path, name, command, result, appPath As String
    
    path = DoShellCommand("echo $PROJECT_PATH")  // 環境変数からプロジェクトのパスを取得
    path = RTrim(path)  // 最後に空白がつくので削除
    
    name = "HOGE"  // プロジェクト名とアプリケーション名が同一の場合(異なる場合は各々に変数を割り当て)
    
    command = "rm -rf """+path+"/Builds - "+name+".xojo_binary_project/OS X 64 bit/"+name+".app"""
    result = DoShellCommand(command)
    
    appPath = BuildApp(16)  // Mac 64-bit build
    
  3. メニューから、名前を付けて保存...を選択し、保存。この時、プロジェクトファイルと同じ場所に作成したScriptsフォルダに追加すると、メニューからアクセスできるようになる。(UserGuide-Framework P208〜)
 これで、IDE スクリプトメニューのサブメニューに、保存したスクリプトが表示されるようになりますので、選択するとビルドが実行されます。
例えば、32bitはビルド設定IDEスクリプトで、64bitは外部IDEスクリプトで、というのはうまくいかないようだ。(BuildAppした時点で、ビルド設定IDEスクリプトが実行されてしまうため。)
 戻る  先頭へ


 IDEスクリプトでデバッグ用アプリケーションを削除する(ネイティブ)

 最新版では解決しているようですが、2016r3では「実行」の後にデバッグ用アプリケーション(HOGE.debug.app)が削除されずに、次回「実行」で「Compilation of "HOGE" failed.」となってしまい、手作業で捨てなければならないない、という問題が起きることがあります。
(注:「実行」だけでなく、「ビルド」でも起きるのですが、ここでは「実行」に特化しています。)
 どうもこれが起きるのは、アプリケーションアイコンを設定している場合のようで、取り除けば回避することはできます。
 それも一つの方法ではありますが、他にないか調べてみたところ、IDEスクリプトからコマンドを発行させることで対応できました。
  1. ビルド設定>OS Xをクリックし、コンテキストメニューから、「ビルド設定」に追加>ビルドステップ>スクリプトを選択
  2. 作成されたスクリプト(名前はここではデフォルトのScript1)をドラッグして、Buildの上に移動(ここ、重要)
  3. Behavior>Applies toをDebugにする
  4. 以下をScript1にペースト
    Dim path, name, command, result As String
    
    path = DoShellCommand("echo $PROJECT_PATH")  // ビルド前はCurrentBuildLocationでは取得できないので、環境変数から取得
    path = RTrim(path)  // 最後に空白がつくので削除
    
    name = "HOGE.debug"  // ビルド前はCurrentBuildAppNameでは取得できないので、手作業で設定(取得方法ある?)
    
    command = "rm -rf """+path+"/"+name+".app"""
    result = DoShellCommand(command)
    
 これで、「実行」するとコンパイル前にデバッグ用アプリケーションが削除されるようになります。
(できればアプリケーション終了後に行いたいのですが、やり方が分かりませんでした。あったら教えて頂けると助かります。)
 戻る  先頭へ


 タイマーを動的に生成する(ネイティブ)

 本件は、Xojo言語リファレンスのAddHandlerの項に、まさにそのものの例題が載っていました(知らんかった…2回目)。
 以下は参考までにどうぞ。

 以前、こちらで「コントロールのオンザフライ生成機能は、Xojo自身も持ってはいますが、それはコントロール配列であり、最初の一個目はウィンドウに配置しておかなければならない」と書きましたが、どうもこれはウィンドウ内に直接配置するコントールについてであり、そうではないコントロールは動的に生成できるようです。
 例えばタイマーをAppに置きたい、という場合は、以下のようにすればできました。
  1. Xojoで新規プロジェクトを作成
  2. 以下をAppにペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      Timer1 = new TimerOnTheFly(AddressOf Timer1Action)
      Timer1.Period = 1000
      Timer1.Mode = 1
      
      Timer2 = new TimerOnTheFly(AddressOf Timer2Action)
      Timer2.Period = 3000
      Timer2.Mode = 1
    End Sub
    
  3. 以下をAppにペースト
    Public Sub Timer1Action()
      msgBox "done 1"
    End Sub
    
  4. 以下をAppにペースト
    Public Sub Timer2Action()
      msgBox "done 2"
    End Sub
    
  5. 以下をAppにペースト(できなければプロパティに、名前:Timer1、データ型:TimerOnTheFly、を追加)
    Protected Property Timer1 as TimerOnTheFly
    
  6. 以下をAppにペースト(できなければプロパティに、名前:Timer2、データ型:TimerOnTheFly、を追加)
    Protected Property Timer2 as TimerOnTheFly
    
  7. 新規クラス(名前は、ここでは「TimerOnTheFly」)を作成し、Superを「Timer」にする。
  8. 以下をTimerOnTheFlyにペースト(できなければ移譲に、名前:ActionDelegate、を追加)
    Public Sub ActionDelegate()
    
  9. 以下をTimerOnTheFlyにペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      ActionHandler.Invoke()  // クラス生成元でActionを受け取るメソッドを呼び出す
    End Sub
    
  10. 以下をTimerOnTheFlyにペースト
    Public Sub Constructor(action As ActionDelegate)
      ActionHandler = action  // クラス生成元でActionを受け取るメソッドを登録
    End Sub
    
  11. 以下をTimerOnTheFlyにペースト(できなければプロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Public Property ActionHandler as ActionDelegate
    
 実行すると、ウィンドウが表示された後に、メッセージが表示されます。

 戻る  先頭へ


 インスペクターバー追加時のウィンドウ高さを補正する(Cocoa)

 インスペクターバーを追加すると、バーの高さ分だけウィンドウ領域が下にズレるのですが、ウィンドウの高さは変化せず、結果として領域下部が見えなくなる、といった現象が起きます。
 ウィンドウをリサイズしてやると正しく表示されるようになるので、高さが増えたことの情報が行き渡っていないような感じです。
 試行の結果、インスペクターバー追加後に以下を記述してやれば、回避できることが確認できました。
// インスペクターバー付加によるHeightの増加に、ウィンドウが追随しないことへの対策(なぜか、これでうまくいくみたい。)
me.Height=me.Height
(Trial and Errorの新規トピックでは対応済ですが、旧トピックでは未実装だったので、追加しておきました。)
 戻る  先頭へ


 Cocoaツールバーとインスペクターバーの順番【10.7限定】(Cocoa)

 こちらこちらのトピックにある、Cocoaツールバーとインスペクターバーについての注意事項です。
 両者を同時に使用する場合、10.7では、Cocoaツールバーの後にインスペクターバーを付加する必要があります。
 逆にすると、表示が崩れます。

 10.8以降では、どちらが先でも問題なく表示されます。
 戻る  先頭へ


 OSのバージョンを取得する(Cocoa)

 System.Gestaltが、いつの間にかDEPRECATEDになっていたので。
 NSProcessInfoを使う方法もあるようですが、Major,Minor,BugFix各バージョンを数値として取得したい場合は、以下の方が簡単そうです。
(参考サイト:OS X のバージョン番号を取得する - Qiita
// 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr

// SystemVersion.plistの取得
Declare Function dictionaryWithContentsOfFile Lib "Cocoa" Selector "dictionaryWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As Ptr
Dim dic As Ptr = NSClassFromString("NSDictionary")  // クラスメソッドなので、まずNSDictionaryクラスを取得
dic = dictionaryWithContentsOfFile(dic, "/System/Library/CoreServices/SystemVersion.plist")

// ProductVersionキーの内容を取得
Declare Function objectForKeyStr Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As CFStringRef
Dim osVer As String = objectForKeyStr(dic, "ProductVersion")
 osVerには、例えば「10.12.5」のような文字列が返ってきますので、ドットをセパレータに分解後数値化すればOKです。
 戻る  先頭へ


 TextAreaでの日本語文字列コピー時の文字化けを回避する(ネイティブ)

 Xojoのデフォルトの設定では、TextAreaに書き込んだ日本語の文字列を「コピー」すると、この時点で文字化けが発生します。(Finderで「クリップボードを表示」してみると分かります。)
 この現象は、TextAreaのFontをデフォルトの「System」から日本語フォント(ヒラギノ系でもOsakaでもOK)に変更することで、回避できます。
Fontの設定は、インスペクタの歯車アイコンボタンを押して表示されるペインで行う。(またはコードで指定してもOK)
 スタイルが不要であれば、(デフォルトでオンになっている)「Styled」をオフにすることでも回避できます。(この場合は、FontはSystemのままでもOK)
 戻る  先頭へ


 文字列定数の内容を取得する(Cocoa)

 Cocoaではパラメータの指定に、文字列定数(NSString * const)が使われることがあります。
 Xojoでは定数名を指定しても、自動的に内容を持ってきてはくれないので、ググるかヘッダを調べるかして、自分で内容をセットする必要がありました。
 これでは不便なので、例によってmacoslibを紐解いてみたところ、CFBundleの機能を使って取得していることが分かりました。

 ただし、この機能のためだけにmacoslibを導入するのはちょっと重たい気がするので、必要な部分のみを抜き出させて頂きました。
  1. Xojoで新規プロジェクトを作成し、TextField1、Textfield2、PushButton1を置く
  2. 以下をPushButton1のActionイベントに記述
    Dim symbolName As String = TextField1.Text  // TextField1から文字列定数を取得
    
    // CFBundleの取得
    declare function CFBundleGetBundleWithIdentifier lib "Cocoa" (bundleID as CFStringRef) as Ptr
    Dim cfb As Ptr = CFBundleGetBundleWithIdentifier("com.apple.Cocoa")
    
    // 与えられた名前に対応するシンボルのポインタを取得
    declare function CFBundleGetDataPointerForName lib "Cocoa" (bundle as Ptr, symbolName as CFStringRef) as Ptr
    dim p as Ptr = CFBundleGetDataPointerForName(cfb, symbolName)
    
    // ポインタの示す文字列を取得
    declare function CFRetain lib "Cocoa" (cf as Ptr) as CFStringRef
    Dim st As CFStringRef = CFRetain(p.Ptr(0))
    
    TextField2.Text = st  // TextField2に内容を表示
    
 戻る  先頭へ


 クラス側から生成元のメソッドを呼び出す(ネイティブ)

 自作のクラスにおいて、クラス側から、そのクラスを生成した側のメソッドを呼び出したい場合があります。
 この場合、例えば「クラス側にプロパティ(例:caller)を用意してクラス生成時にselfをセットし、caller.method()として呼び出す」という方法もありますが、プロパティには(呼び出し側の)型を指定しなければならないため、使い方に制約がつくことになります。

 他に方法がないか、例によってmacoslibを眺めていたところ、Delegateが目に留まりました。
 Delegateは、実行時にメソッドを切り替えたいようなケースを想定したもの?のようですが、今回の目的にも使えそうな感じでした。
  1. クラス内に、デリゲートを定義
  2. クラス内に、「定義したデリゲートをデータ型とする」プロパティを定義
  3. クラス生成時に、(クラス側から呼び出したい)クラス生成元のメソッドを、上述のプロパティに登録
    (メソッドはAddressOfを付けて渡し、型には「定義したデリゲート」を指定する。デリゲートはPtr型に自動的に変換される、とのこと。)
  4. クラス生成元のメソッドを呼び出したい時は、上述のプロパティをInvokeする
 コードで表すとすると、例えば、
  1. 新規クラスを作成(名前は、ここではデフォルトの「Class1」とした。)
  2. 以下をClass1の移譲(Delegates)に追加
    デリゲート名: ActionDelegate
    引数:なし
    戻り値型:なし
    
  3. 以下をClass1のメソッド(Methods)に追加(注:Constructorは予約語で、クラスをnewした時に自動的に呼び出される。)
    メソッド名: Constructor
    引数:action As ActionDelegate
    
    ActionHandler = action  // クラス生成元でActionを受け取るメソッドを登録
    ActionEvent()  // この例では、直ちに呼び出す
    
  4. 以下をClass1のメソッド(Methods)に追加
    メソッド名: ActionEvent
    引数:なし
    戻り値型:なし
    
    ActionHandler.Invoke()  // クラス生成元でActionを受け取るメソッドを呼び出す
    
  5. 以下をClass1のプロパティ(Properties)に追加(注:データ型は、デリゲート名と一致させる。)
    プロパティ名: ActionHandler
    データ型: ActionDelegate
    標準値: なし
    

  6. 以下をWindow1のOpenイベントに記述(注:引数は、Constructorに渡される。)
    Dim cls As Class1 = new Class1(AddressOf CalledFromClass1)  // クラスのインスタンスを生成(Actionを受け取るメソッドを、引数で指定する)
    
  7. 以下をWindow1のメソッド(Methods)に追加
    メソッド名: CalledFromClass1
    引数:なし
    戻り値型:なし
    
    MsgBox "Called From Class1"  // 動作確認用
    
 上記例ではメソッド/プロパティを使用しましたが、共有メソッド/共有プロパティでも同様です。

 戻る  先頭へ


 Declareの引数がアドレスの場合に値を取得する(Cocoa&Carbonも?)

(暫くやってないとすぐ忘れるので、ここに記しておきます。)
 Declareで指定したいメソッドの引数が、アドレスの場合があります。
 この場合、まずは(取得したい値の型にかかわらず)全てMemoryBlockを割り当てて、その後、型に応じた処理をして値を取得します。

 Cocoa Objective-cでの記述例(注:Carbonの場合も基本的に同じ筈)
id *data;
int value;
NSString *err;
[NShoge getData:&data value:&value error:&err];  // 注:あくまで例題で、CocoaにこのようなAPIはありません
 上記例のXojoでの実装例
Dim hoge As Ptr = NSClassFromString("NShoge")  // (この文は、例題としての整合性を図るためのもので、本題とは直接関係ありません。)

Dim data As New MemoryBlock(4)
Dim value As New MemoryBlock(4)
Dim err As New MemoryBlock(4)

Declare Sub getDataValueErr Lib "Cocoa" Selector "getData:value:error:" (receiver As Ptr, data As Ptr, value As Ptr, err As Ptr)  // アドレス指定の引数の型はPtr
getDataValueErr(hoge, data, value, err)

// 値がオブジェクトの場合(ここではNSDictionaryとしている)
Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As CFStringRef
Dim r As CFStringRef = objectForKey(data.Ptr(0), "keyword")  // オブジェクトから情報を引き出す

// 値がintの場合
Dim v As Integer = value.Int32Value(0)

// 値がNSStringの場合
Declare Function UTF8String Lib "Cocoa" Selector "UTF8String" (receiver As Ptr) As CString
Dim s As String = UTF8String(err.Ptr(0))

msgBox r+" "+Str(v)+" "+s
 追記:整数/実数/論理値は、byRef指定することで直接取得することもできます。また、文字列が日本語を含む場合は、エンコーディングがUTF8であることを明示しないと文字化けします。(2021/02/24)
Dim value As Integer
Declare Sub getDataValueErr Lib "Cocoa" Selector "getData:value:error:" (receiver As Ptr, data As Ptr, byRef value As Integer, err As Ptr)
…
s=DefineEncoding(s,Encodings.UTF8)
 戻る  先頭へ


 ウィンドウのタイトルにアイコンとパスのポップアップメニューを表示する(Cocoa)

 XojoのIDE自体は対応しているのですが、ユーザが使えるAPIを見つけられなかったので。(あったら教えてください。)
 ウィンドウのタイトルにオープンしたファイルのアイコンと、タイトル右クリック時にアイコンと、パスをフォルダごとに区切ったポップアップメニューを表示します。
メソッド名: setTitleWithRepresentedFilename
引数: w As Window, f As FolderItem

// 同時にタイトル名もセットします。タイトル名をセットしたくない場合は、setRepresentedFilename:
Declare Sub setTitleWithRepresentedFilename Lib "Cocoa" selector "setTitleWithRepresentedFilename:" (receiver As Integer, path As CFStringRef)
setTitleWithRepresentedFilename(w.Handle, f.NativePath)

 戻る  先頭へ


 ファイルの拡張子を取得する(Cocoa)

 Xojoネイティブの機能にはなさそうだったので。
メソッド名: GetFileExtention
引数: f As FolderItem
戻り値型: String

Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
Dim nsstr As Ptr = NSClassFromString("NSString")

Declare Function stringWithString Lib "Cocoa" Selector "stringWithString:" (receiver As Ptr, string As CFStringRef) As Ptr
Declare Function pathExtension Lib "Cocoa" Selector "pathExtension" (receiver As Ptr) As CFStringRef
return pathExtension(stringWithString(nsstr, f.NativePath))
 戻る  先頭へ


 ダーティフラグを操作する(Cocoa>ネイティブ)

 本件は、Xojoネイティブの機能であるWindow.ContentsChangedを使えばできます(知らんかった…)。
 以下は参考までにどうぞ。

 こんなことばっかりやっていると、「素直にXcodeで開発したら?」って声が聞こえてきそう(笑)ですが...
 ダーティフラグを操作したいウィンドウに、以下のメソッドを追加します。
メソッド名: SetDirtyFlag
引数: flag As Boolean

Declare Sub setDocumentEdited Lib "Cocoa" selector "setDocumentEdited:" (receiver As Integer, flag As Boolean)

setDocumentEdited(self.Handle, flag)  // self.Handleはウィンドウのハンドル。flag = true:付加 false:除去
注)汎用関数化したい場合は、引数にWindowまたはWindowのハンドルを追加してやるといいでしょう。

 戻る  先頭へ


 ウィンドウメニューについて(Cocoa編 ダイアモンドマークを付加・改訂版)

 「ウィンドウメニューについて(Cocoa編)」のうち、「しまわれた項目にダイアモンドマークを付加」は、理解が不十分だったため、書き直しました。
  1. 「しまわれた項目にダイアモンドマークを付加」
     こちらもXcodeを参考にしたいところですが、Document-Based Applicationを作ってみると分かるとおり、ウィンドウメニューへの追加やマーク付け等は自動で行われるため。手掛りは得られませんでした。
     なので、定番メソッドであるSetItemMark()のCocoa互換版がないか検索してみたところ、こちらのサイトがヒットしました。

     ここを糸口に更に調べてみたところ、メニュー項目のイメージを書き換えればいけるらしいことが分かりました。即ち、
     (1) マークの付加/クリアは、メニュー項目のCheckedプロパティをtrue/falseにする、という通常の処理で行う。
     (2) マーク付加時のイメージを、しまうが実行されたらダイアモンドに、戻す(ドックから出す)が実行されたらチェックマークに差し替える。
    という、SetItemMark()に近い(同じかどうかはよくわかりません。)ものです。

     イメージのタイプは参考サイトではNSMenuItemBulettになっていますが、類推してNSMenuItemDiamondとしたところ、うまくいきました。
     また、イメージセットの対象となるStateにはonState/offState/mixedStateとありますが、趣旨から言って、onState(デフォルトのイメージはチェックマーク)が妥当と思われます。
     なお、イメージの取得にはこちらのサイトを参考にさせて頂きました。

     セット(ダイアモンド)はMinimizeイベント、リセット(チェックマーク)はRestoreイベントで、以下のメソッドをコールします。(ただし、RestoreはMaximizeでも呼ばれるので、MinimizeとMaximizeを区別する仕込みが別途必要。)
    メソッド名: SetOnStateImage
    引数: iMenuItem As MenuItem, flag As Boolean
    
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    Dim iPtr As Ptr = nil
    Declare Function imageNamed Lib "Cocoa" selector "imageNamed:" (receiver As Ptr, name As CFStringRef) As Ptr
    if flag then  // flag = true:マークをダイアモンドにセット false:マークをデフォルト(チェックマーク)にリセット
        iPtr = imageNamed(NSClassFromString("NSImage"), "NSMenuItemDiamond")  // イメージを取得
    else
        iPtr = imageNamed(NSClassFromString("NSImage"), "NSMenuItemSelection")  // イメージを取得
    end if
    
    // メニューのハンドルを取得(メッセージの受け手として利用できる。)
    Dim iMenuHandle As Integer
    iMenuHandle = iMenuItem.Handle(MenuItem.HandleType.CocoaNSMenuItem)
    if iMenuHandle = 0 Then
        //msgBox "error."
        return
    end if
    
    Declare Sub setOnStateImage Lib "Cocoa" selector "setOnStateImage:" (receiver As Integer, image As Ptr)
    setOnStateImage(iMenuHandle, iPtr)  // onStateのイメージを設定
    
    注)XojoでDocument-Based Applicationを作成する場合、ウィンドウメニューへの書類名の追加は自分で行うことになるので、引数にはその(自分で生成した)メニュー項目を指定する。
 戻る  先頭へ


 ファイルタイプグループの項目を並べ替える(Cocoa&Carbon)

 ファイルタイプグループの項目が多くなってくると、並べ替えた方がスッキリする場合があるのですが、IDE上では並べ替えることができません。(推測)
 なので、何か方法がないか調べてみたところ、「XMLでエクスポートし、テキストエディットで開いて並べ替えた後、インポート」すればいいことが分かりました。
 多分に原始的で、集中力を要求されはするものの、有効な手法としてReal Studioの頃は機能していました。

 ところが、Xojoになってエクスポートのダイアログから出力フォーマットの選択オプションがなくなり、バイナリでしか書き出せなくなってしまったため、この手が使えなくなってしまいました。(推測)
 「xojo export xml」で検索してみても、めぼしい情報が得られなかったので、結局、以下の方法で凌ぐことにしました。
  1. ファイルタイプグループ(以下、FileTypes1)をクリックし、ファイルメニューから「FileTypes1をエクスポート...」を選択して、書き出す。
  2. 書き出したファイル(FileTypes1.xojo_filetypeset)をXojoアイコンにドラッグ&ドロップ。
  3. ファイルメニューから「名前を付けて保存...」を選択し、Formatを「Xojo XML プロジェクト」にして保存。
  4. 保存したファイル(FileTypes1.xojo_xml_project)をテキストエディットで開く。
  5. 3行目の「<block type="Project" ID="0">」から「<block type="FileTypes1" ID="...">の直前」までを削除。
  6. 「<block type="Module" ID="...">」から「最後から2行目の</block>」までを削除。
  7. この時点でひとまず保存。

    これで、Real StudioでのXMLエクスポート相当のファイルが出来上がりました。(推測)
    あとは、「<FileType> 〜 </FileType>」が一つの項目なので、これを範囲指定してカット&ペーストで順番を入れ替えます。
    終わったら保存し、

  8. まず、既存のファイルタイプグループをIDE上から削除。(インポート後に削除してもいいが、その場合は名前がFileTypes2等に変わってしまうことに注意)
  9. ファイルメニューから「インポート...」を選択し、編集後のファイル(FileTypes1.xojo_xml_project)を指定して取り込む。
 以下はTipsです。
  1. アイコンも文字化されるので巨大な文字列となり、扱い難くなるので、予め削除して、インポート後にIDE上で再設定した方が楽かも。
  2. アイコンは一度セットすると、全て削除しても「丸に三点マーク」が表示されなくなるが、XML上で「<Icon> </Icon>」の2行を削除してやると復活する。
 余裕があれば、XMLパーサーを自作してツール化する手もありますが、滅多にやることでもないので手作業でもいいかな、と。
 なお、もっと簡単に並べ替える方法があれば、教えて頂けると助かります。
 戻る  先頭へ


 書体を含めたフォント名を取得する(Cocoa)

 本稿は、再構成してこちらに移動しました。(2014/10/30)

 戻る  先頭へ


 パッケージ形式の書類について(Cocoa&Carbon)

 パッケージ(バンドル)形式の書類は、Xojoでは標準的にはサポートされていないようなので、少し調べてみました。
 パッケージ形式の書類は「実体はフォルダであるが、Finder上では単一の書類として扱えるもの」です。
例えば、テキストエディットで画像ファイルをドロップして保存すると、拡張子がrtfdの書類が作成されるが、これがパッケージ形式。
Cocoaでは、OS9以前からの資産であるResourceForkがDeprecatedとなったため、その代替として利用されることがある。
 パッケージ化する仕組みとしては、以下の二つが確認できました。
  1. フォルダ内にPkgInfoファイルを含める。
    ・ファイルの中身は「BNDL????」一行のみ。Contentsサブフォルダ内に収めるのが作法?
    ・フォルダ名に適当な拡張子を付加すると、直ちにパッケージ書類となる。
    ・手軽だが、フォルダ内に必ずしも必要でないファイルが含まれてしまう。また、これだけではアイコンが設定できない。

  2. アプリケーションのinfo.plistで、書類がパッケージであることを宣言する。
    ・Xcodeで作成する場合は、こちらの方式。
    ・PkgInfo方式に比べ、余分なファイルがない分フォルダ内がシンプル。
    ・方法は、
    1. Xojoで「ファイルタイプグループ」を作成し、当該書類を登録する。
      表示名:<各自が設定> オブジェクト名:<各自が設定> Macタイプ:fold Macクリエータ:MACS 拡張子:<各自が設定> アイコン:<各自が設定>
      
      注1)ポイントは、Macタイプを「fold」にすることと、拡張子を設定すること。
      注2)表示名はCFBundleTypeName(Finderの「情報を見る」で種類のところに表示)として使われ、オブジェクト名は内部処理(.Filter等)で使われる。
      注2)表示名はCFBundleTypeName(Finderの「情報を見る」で種類のところに表示。ただしOSによっては別表記になることも)や.Filterの指定に使われ、オブジェクト名は内部処理(ビルド設定>OS X>File Types を選択して表示されるダイアログ等)で使われる。(2015.01.22 訂正)

    2. ビルドしたアプリケーションのinfo.plistを開き、当該書類のパートに以下を追加。
      <key>LSTypeIsPackage</key>
      <true/>
      
      注)Xojo 2014r2以降にはinfo.plistのオーバーライド機能があるが、トップレベルのみで、本件のようなサブ階層の編集には未対応のようだ。
 パッケージ形式の書類は、Xojoからはフォルダとして見えるので、.Childで内部のファイルにアクセスできます。
 問題点は、当該書類をドラッグ&ドロップに対応させると、全てのフォルダがドロップ対象になってしまうことでしょうか。(拡張子でふるい分けができればいいのでしょうが、やり方が分かりません。)

 追記:パッケージをファイルとして認識しない(フォルダのままの)場合は、(LaunchServices databaseは常に更新されているので)まずは暫く様子を見て、それでも変化がなければ、以下の処理を行うと改善される場合があります。(ターミナルを使用します。)(2015/02/23)
  1. LaunchServices databaseを再構築。
    /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -r -f -all local,system,user
    
    注1)10.4以前では、lsregisterのパスが「/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support」
    注2)本処理を行うと、アプリケーションとファイルの関連付けがリセットされる。これにより、「このアプリケーションで開く」で表示されるリストがクリア/再取得されるため、関連付けをカスタマイズしている場合は、再設定が必要になる場合がある。
  2. Finderを再起動。
    killall Finder
    
 戻る  先頭へ


 ウィンドウメニューについて(Cocoa編)

 以前の「ウィンドウメニューについて」の内容はCarbon用で、Cocoaには適用されません。なので、纏めておくことにしました。
 ここで扱う項目は「しまう」「拡大/縮小」「すべてを手前に移動」メニューと「しまわれた項目にダイアモンドマークを付加」です。
  1. 「しまう」「拡大/縮小」
     Carbonとは異なり、「しまう」だけでなく、「拡大/縮小」もXojoの標準機能で事足ります。
    (しまう>Minimize、拡大/縮小>Maximize)

  2. 「すべてを手前に移動」
     Xcodeの当該メニューを参考に実装してみましたが、とりあえず動くみたいです。
    (対応するメニューハンドラに、以下を記述。)
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // NSApplicationオブジェクトを取得(メッセージの受け手)
    Dim sA As Ptr = NSClassFromString("NSApplication")
    Declare Function SharedApplication Lib "Cocoa" Selector "sharedApplication" (receiver As Ptr) As Ptr
    sA = SharedApplication(sA)
    
    Declare Sub arrangeInFront Lib "Cocoa" Selector "arrangeInFront:" (receiver As Ptr, id As Ptr)
    arrangeInFront(sA, nil)  // NSApplicationのセレクタであるarrangeInFront:の処理
    
    Return True
    

  3. 「しまわれた項目にダイアモンドマークを付加」
     本項は、理解が不十分だったため、書き直してこちらに移動しました。(2014/12/15)

  4. 補足
     上述のNSMenuItemBulettですが、アップルのガイドラインによると、変更があって保存されていない時に使われるものなので、こちらも対応しておいた方がよさそうです。(ガイドラインについては、例えばこちらのサイト参照。)

  5. macoslibについて
     例によって、macoslibはCocoaベースのウィンドウメニュー全般にも対応していますので、実用が目的であれば、使わせて頂いた方が手軽です。
 戻る  先頭へ


 CocoaのDeclareでバッジを表示する

 アプリケーションアイコン(Doc内)の「バッジ(アイコンの右肩に表示される赤丸)」表示ですが、CocoaのDeclareを使えば可能であることが確認できました。
メソッド名: ShowBadge
引数: str As String

// 以下は、Objective-cでの記述
// NSApplication *sA = [NSApplication sharedApplication];
// NSDockTile *tile = [sA dockTile];
// [tile setBadgeLabel:@"5"];

// 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr

// sharedApplicationの取得
Dim sA As Ptr = NSClassFromString("NSApplication")
Declare Function SharedApplication Lib "Cocoa" Selector "sharedApplication" (receiver As Ptr) As Ptr
sA = SharedApplication(sA)

// Docアイコンの取得
Dim tile As Ptr
Declare Function dockTile Lib "Cocoa" Selector "dockTile" (receiver As Ptr) As Ptr
tile = dockTile(sA)

// バッジを表示(引数は文字列。空文字列を指定するとクリア)
Declare Sub setBadgeLabel Lib "Cocoa" Selector "setBadgeLabel:" (receiver As Ptr, numString As CFStringRef)
setBadgeLabel(tile,str)
 引数に"5"とか指定すると、アイコンにバッジがつきます。""(空文字列)を指定すると、バッジが消えます。ちなみに引数は文字列なので、"abc"とかもできます。
 戻る  先頭へ


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