ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでアプリアイコンを作る・フォントパネルを使う

 Xojo / Real Studio Trial and Error

CocoaのDeclareでアプリアイコンを作る・フォントパネルを使う

目次
 はじめに

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

 フォントパネルを表示して、選択したフォントの情報を取得する方法について、調べてみました。

 なお検証には、Xojo 2022 Release 4.1を用いています。(Mac mini 2018 + macOS 15.3 Sequoia)


 方針

 前回アプリでは、フォント名の指定はプリセットまたは自分で調べてセットするようにしていましたが、使い勝手向上のため、フォントパネルを使えるようにしてみます。
 フォントパネルの表示については、様々情報が公開されていますので、すぐに分かりました。
(NSFontPanelだけでなく、NSFontManagerと連携して使う、あたりがキモでしょうか。)

 選択フォントの情報取得方法は、デリゲートや通知センターではなく、ターゲット/アクションであることも分かりました。
 なので、お馴染みのObjective-CのランタイムAPIを用いた動的クラス生成を利用します。

 手こずったのは、フォント取得の具体的手順で、当初はselectedFontプロパティから取れるのかと思ったらそうではなく、あらかじめフォントインスタンスを用意しておき、convertFont:で中身を入れ替える、というちょっとトリッキーなものとなっています。

 参考サイト(1):objective c - NSFontManager selectedFont returning null - Stack Overflow

 取得する情報は、フォント名のフルネームのみとします。サイズその他はここでは対象としません。
(注:ボールドやイタリックは、ファミリー内に存在すれば、フォント名の一部として指定することは可能。)

 以上を踏まえ、(残りの)仕様は以下の通りとしました。

 Xojoでの実装
注:以下の実装では、一部Xojo 2022r4.1ではDeprecatedな機能が使われています。必要なら推奨される機能に置き換えて下さい。
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. 前回プロジェクトをベースとする
  2. Window1に、DesktopBevelButton1個(Name:BevelButton1, Caption:Panel)を置く。(配置はスクリーンショット参照)
  3. 以下をBevelButton1にペースト(できなければ、Sub - Endの間をPressedイベントに記述)
    Sub Pressed() Handles Pressed
      ShowFontPanel()
    End Sub
    
  4. 以下のWindow1のイベントを置き換え(できなければ、Sub - Endの間をOpeningイベントに記述)
    Sub Opening() Handles Opening
      me.Top=me.Top+3
      me.AcceptFileDrop("image/png")
      
      // NSFontPanel。引数は、フォントパネルクリックアクションの受け口
      Dim p As NSFontPanel = new NSFontPanel(AddressOf FontSelected)
      
      // NSView。引数は順に、生成したviewのインスタンス、配置するウィンドウ、位置/サイズ
      Dim d As NSViewCanvas = new NSViewCanvas(pViewInst, self, NSMakeRect(10, 10, 1024, 1024))
      
      // アイコンビュー生成(初期化)
      SetIconView(kReset)
    End Sub
    
  5. 以下をWindow1にペースト
    Protected Sub FontSelected(id As Ptr, SEL As CString, sender As Ptr)
      'sender = NSFontManager
      
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 初めにガワを作る。
      Dim sfont As Ptr = NSClassFromString("NSFont")
      Declare Function systemFontOfSize Lib "Cocoa" Selector "systemFontOfSize:" (receiver As Ptr, size As CGFloat) As Ptr
      sfont = systemFontOfSize(sfont, 12.0)  // 中身はなんでも良い。
      
      // senderにガワを渡して、選択中のフォントを取り込む
      Declare Function convertFont Lib "Cocoa" Selector "convertFont:" (receiver As Ptr, flg As Ptr) As Ptr
      sfont = convertFont(sender, sfont)
      
      // フォント名を取得
      Declare Function fontName Lib "Cocoa" Selector "fontName" (receiver As Ptr) As CFStringRef
      Dim fname As String = fontName(sfont)
      
      // ComboBox1を書き換え
      ComboBox1.Text=fname
    End Sub
    
  6. 以下をWindow1にペースト
    Protected Sub ShowFontPanel()
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      Dim fmanager As Ptr = NSClassFromString("NSFontManager")
      Declare Function sharedFontManager Lib "Cocoa" selector "sharedFontManager" (class_id As Ptr) As Ptr
      fmanager = sharedFontManager(fmanager)
      
      Dim fpanel As Ptr = NSClassFromString("NSFontPanel")
      Declare Function sharedFontPanel Lib "Cocoa" selector "sharedFontPanel" (class_id As Ptr) As Ptr
      fpanel = sharedFontPanel(fpanel)
      
      Declare Sub orderFrontFontPanel Lib "Cocoa" Selector "orderFrontFontPanel:" (receiver As Ptr, val As Ptr)
      orderFrontFontPanel(fmanager, fpanel)
    End Sub
    
  7. 新規クラス(名前は、ここでは「NSFontPanel」)を作成する。
  8. 以下をNSFontPanelにペースト(できなければDelegatesに、名前:ActionDelegate、引数:id As Ptr, SEL As CString, sender As Ptr、を追加)
    Private Sub ActionDelegate(id As Ptr, SEL As CString, sender As Ptr)
    
  9. 以下をNSFontPanelにペースト
    Public Sub Constructor(action As ActionDelegate)
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      // アクションを受け取るメソッドを定義したクラスを作成。初回のみ
      makeTarget()
      
      // ターゲットアクションのインスタンスを作成
      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(TargetActionClass))
      
      // FontManagerを取得
      Dim fmanager As Ptr = NSClassFromString("NSFontManager")
      Declare Function sharedFontManager Lib "Cocoa" selector "sharedFontManager" (class_id As Ptr) As Ptr
      fmanager = sharedFontManager(fmanager)
      
      // FontManagerにターゲットアクションを設定
      Declare Sub setTarget Lib "Cocoa" Selector "setTarget:" (receiver As Ptr, actionTarget As Ptr)
      setTarget(fmanager, targetId)
      Declare Sub setAction Lib "Cocoa" Selector "setAction:" (receiver As Ptr, actionEvent As Ptr)
      setAction(fmanager, NSSelectorFromString("action:"))
      
      
      // Window側でActionを受け取るメソッドを登録
      ActionHandler = action
    End Sub
    
  10. 以下をNSFontPanelにペースト
    Private Shared Sub actionEvent(id As Ptr, SEL As CString, sender As Ptr)
      // インスタンス側でActionを受け取るメソッドをレイズする
      ActionHandler.Invoke(id,SEL,sender)
    End Sub
    
  11. 以下をNSFontPanelにペースト
    Private Shared Sub makeTarget()
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      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 TargetActionClass <> nil then
        return
      end if
      
      // クラス名をmyFontPanelAction、メタクラス名をNSObjectにして、生成
      Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myFontPanelAction", 0)
      // ランタイムに登録(参照を可能とするため)
      objc_registerClassPair newClassId
      // Tarrgetに送られてきたActionの受け口となるメソッドを追加(action:をXojo側で用意したactionEventメソッドで受け取る。)
      if not class_addMethod (newClassId, NSSelectorFromString("action:"), AddressOf actionEvent, "@@:@") then
        msgBox "error."
        return
      end if
      
      // クラスを保持
      TargetActionClass = newClassId
    End Sub
    
  12. 以下をNSFontPanelにペースト(できなければShared Propertyに、Name:ActionHandler、Type:ActionDelegate、を追加)
    Private Shared Property ActionHandler As ActionDelegate
    
  13. 以下をNSFontPanelにペースト(できなければShared Propertyに、Name:TargetActionClass、Type:Ptr、を追加)
    Private Shared Property TargetActionClass As Ptr
    
 実行してみたところ、フォントパネルを表示し、フォント名を取得できることを確認しました。
S Shot1

 おわりに

 フォントパネルのインスタンスはsharedFontPanelで取得しているので、全アプリ共用なのかな?というイメージもあったため、並行して、テキストエディットでもフォントパネルを表示して、それぞれ指定してみましたが、パネル自体が別個に表示され、選択がクロスするようなこともありませんでした。

 あと、時々、起動時にフォントパネルが表示されることがあります。何度かアプリの起動を繰り返すと直るので、現状、特に何もしていません。何処かに(誤って?)情報がキャッシュされているのかもしれませんが、詳細は未調査です。


 お世話になったサイト

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

 参考サイト(1):objective c - NSFontManager selectedFont returning null - Stack Overflow


 更新履歴

 2025.02.17 新規作成


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