ホームページ開発ツール>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)


 方針

 スクロールバーの背景(色)は、標準では白に近いグレーになっています。
 この色は、プロパティやメソッドによってカスタマイズできるようにはなっていないようです。

 一方、Xojo CocoaビルドにおけるスクロールバーのクラスはXOJScrollerで、これはNSScrollerのサブクラスになっています。
 なので、NSScrollerで透明化する方法があれば、応用できる可能性が大です。
 調べてみたところ、以下がヒットしました。

 参考サイト(1):objective-c - NSScrollViewでスクロールの背景が透明でない(David氏の投稿)

 drawRect:をオーバーライドするだけなので、シンプルです。
 そのためには、XOJScrollerのサブクラスを作ってもいいのですが、そうするとアクションも独自に実装しなければいけなくなります。
 アクションやプロパティ値はXojoネイティブでいきたいので、以前やった、カテゴリによるクラス拡張でXOJScrollerのdrawRect:を直接オーバライドすることとしました。

 問題は、カテゴリによる拡張は全てのスクロールバーに適用される、という点です。
 スクロールバーは単体の他に、リストボックスやテキストエリアにも内蔵されていますが、それらも影響を受けてしまいます。
S Shot1

 これを回避するには、透明化すべきスクロールバー(インスタンス)とそうでないものを区別できればいい訳ですが、メソッドに渡ってくるパラメータで判定できれば、別途プロパティを保持するとかの必要がなくなるので、今回はタグを使うことにしました。

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

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成
  2. Window1に、ScrollBar(Name:ScrollBar1)、Listbox(Name:Listbox1)を追加
  3. 以下をListbox1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)(注:縦スクロールバーを表示させるためのもので、機能的な意味はない。)
    Sub Open() Handles Open
      me.AddRow "aaaaa"
      me.AddRow "bbbbb"
      me.AddRow "ccccc"
      me.AddRow "ddddd"
      me.AddRow "eeeee"
      me.AddRow "fffff"
      me.AddRow "ggggg"
    End Sub
    
  4. 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      InitScrollbar()
    End Sub
    
  5. 以下をWindow1にペースト
    Private Sub InitScrollbar()
      // 背景を透明化したいスクロールバーを、ハンドルをポインタ化して登録(複数個ある場合は、必要なだけ追加)
      Dim inst(-1) As Ptr
      inst.Append Ptr(ScrollBar1.Handle)
      
      // 初期化
      myXOJScroller.Init(inst)
    End Sub
    
  6. 新規クラス(名前は、ここでは「myXOJScroller」)を作成。
  7. 以下をmyXOJScrollerにペースト
    Public Shared Sub Init(inst() As Ptr)
      // カテゴリ拡張
      SetCategory()
      
      // パラメータカスタマイズ
      Declare Sub setTag Lib "Cocoa" Selector "setTag:" (receiver As Ptr, tag As Integer)
      for i As Integer = 0 to inst.Ubound
        setTag(inst(i), 9999)  // タグをセット(透明化有効フラグとして利用)
      next
    End Sub
    
  8. 以下をmyXOJScrollerにペースト
    Private Shared Sub myDrawRect(id as Ptr, SEL as CString, rect As NSRect)
      // idからタグ(透明化有効フラグとして利用)を取得
      Declare Function myTag Lib "Cocoa" selector "tag" (class_id As Ptr) As Integer
      Dim tag As Integer = myTag(id)
      
      // 透明化したいスクロールバーのみ適用
      if tag=9999 then
        
        // ノブを描画
        Declare Sub drawKnob Lib "Cocoa" Selector "drawKnob" (receiver As Ptr)
        drawKnob(id)
        
      else
        
        // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
        Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
        
        // superの取得(XOJScrollerをカテゴリ拡張しているため、XOJScrollerを呼ぶと自分に戻ってきてしまうので、NSScrollerを呼ぶ)
        Dim sup As objc_super
        sup.receiver = id
        sup.pClass = NSClassFromString("NSScroller")
        
        // superに処理を渡す
        declare sub objc_msgSendSuper lib "Cocoa" (byref sup As objc_super, op As CString, rect As NSRect)
        objc_msgSendSuper(sup, SEL, rect)
        
      end if
    End Sub
    
  9. 以下をmyXOJScrollerにペースト
    Private Shared Sub SetCategory()
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      // カテゴリ拡張したいクラスを取得
      Dim newClassId As Ptr = NSClassFromString("XOJScroller")
      
      // Delegateの対象となるメソッドを追加(drawRect:をXojo側で用意したmyDrawRectメソッドで受け取る。)
      Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean
      if not class_addMethod (newClassId, NSSelectorFromString("drawRect:"), AddressOf myDrawRect, "v24@0:8@16") then
        msgBox "error1."
        return
      end if
    End Sub
    
  10. 以下をmyXOJScrollerにペースト(できなければ構造体に、receiver As Ptr、pClass As Ptr、を追加)
    Private Structure objc_super
      receiver As Ptr
      pClass As Ptr
    End Structure
    
  11. 他に、NSRect(構造体)が必要ですが、macoslibからコピーさせて頂きました。(上記myXOJScrollerまたは別途モジュールを用意してコピーする。)
    注)macoslibではNSRectのメンバーの型にSingleが割り当てられているが、64bitにも対応したい場合は、CGFloatに書き換える。
 実行してみたところ、指定したスクロールバーの背景が透明になることを確認しました。
S Shot1


 おわりに

 画像やリスト上で、MouseEnter&MouseMoveした時だけ表示する、とかすればiOSっぽくできますが、ガイドライン上許容されているのかは不明です。
 調べてからにした方がいいかもしれません。

 なお、今回は利用しませでしたが、drawKnobSlotInRect:highlight:メソッドを使う方法もあります。(本来はこれを使うべき?)
 この方法では「透明色」を指定する必要がありますが、色自体は(例えば)NSColorのclearColorで得られますが、それだけでは透明になりません。
 調べたら、これも以前やったsetWantsLayer:を有効にすればいいことが分かりましたので、設定します。

 参考サイト(2):cocoa - NSScrollview and transparent, overlay NSScroller subclasses - Stack Overflow(Bryan氏の投稿)


 お世話になったサイト

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

 参考サイト(1):objective-c - NSScrollViewでスクロールの背景が透明でない(David氏の投稿)
 参考サイト(2):cocoa - NSScrollview and transparent, overlay NSScroller subclasses - Stack Overflow(Bryan氏の投稿)


 更新履歴

 2019.05.28 新規作成


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