Xamarin.Mac でのカスタム コントロールの作成

Xamarin.Mac アプリケーションで C# と .NET を操作する場合は、、Swift および Xcode で作業している開発者と同じユーザー コントロールにObjective-Cアクセスできます。 Xamarin.Mac は Xcode と直接統合されるため、Xcode の Interface Builder を使用してユーザー コントロールを作成および管理できます (または必要に応じて C# コードで直接作成することもできます)。

macOS には豊富な組み込みのユーザー コントロールが用意されていますが、すぐに使用できない機能を提供したり、カスタム UI テーマ (ゲーム インターフェイスなど) に一致させたりするために、カスタム コントロールを作成する必要がある場合があります。

カスタム UI コントロールの例

この記事では、Xamarin.Mac アプリケーションで再利用可能なカスタム ユーザー インターフェイス コントロールを作成する基本について説明します。 この記事で使用する主要な概念と手法について説明するため 、まず Hello、Mac の記事、特 に Xcode とインターフェイス ビルダーアウトレットとアクション の概要に関するセクションを使用することを強くお勧めします。

Xamarin.Mac Internals ドキュメントの「C# クラス/メソッドをに公開するObjective-C」セクションも参照してください。C# クラスObjective-Cをオブジェクトと UI 要素に結び付けるために使用される コマンドと Export コマンドについても説明Registerします。

カスタム コントロールの概要

前述のように、Xamarin.Mac アプリの UI に固有の機能を提供したり、カスタム UI テーマ (ゲーム インターフェイスなど) を作成したりするために、再利用可能なカスタム ユーザー インターフェイス コントロールを作成する必要がある場合があります。

このような状況では、C# コードまたは Xcode の Interface Builder を使用してアプリの UI に追加できるカスタム ツールを簡単に継承 NSControl して作成できます。 カスタム コントロールから NSControl 継承すると、組み込みのユーザー インターフェイス コントロールに含まれるすべての標準機能 (など NSButton) が自動的に取得されます。

カスタム ユーザー インターフェイス コントロールに情報 (カスタム グラフやグラフィック ツールなど) が表示されるだけの場合は、 ではなく NSControlNSView継承できます。

どの基本クラスを使用しても、カスタム コントロールを作成するための基本的な手順は同じです。

この記事では、独自のユーザー インターフェイス テーマと、完全に機能するカスタム ユーザー インターフェイス コントロールを構築する例を提供するカスタム Flip Switch コンポーネントを作成します。

カスタム コントロールのビルド

作成するカスタム コントロールはユーザー入力 (マウスの左ボタンクリック) に応答するため、 から NSControl継承します。 このようにして、カスタム コントロールには、組み込みのユーザー インターフェイス コントロールに標準の macOS コントロールのように対応する標準機能がすべて自動的に含まれます。

Visual Studio for Macで、カスタム ユーザー インターフェイス コントロールを作成する Xamarin.Mac プロジェクトを開きます (または、新しいユーザー インターフェイス コントロールを作成します)。 新しいクラスを追加し、 を呼び出します NSFlipSwitch

新しいクラスの追加

次に、 クラスを NSFlipSwitch.cs 編集し、次のようにします。

using Foundation;
using System;
using System.CodeDom.Compiler;
using AppKit;
using CoreGraphics;

namespace MacCustomControl
{
    [Register("NSFlipSwitch")]
    public class NSFlipSwitch : NSControl
    {
        #region Private Variables
        private bool _value = false;
        #endregion

        #region Computed Properties
        public bool Value {
            get { return _value; }
            set {
                // Save value and force a redraw
                _value = value;
                NeedsDisplay = true;
            }
        }
        #endregion

        #region Constructors
        public NSFlipSwitch ()
        {
            // Init
            Initialize();
        }

        public NSFlipSwitch (IntPtr handle) : base (handle)
        {
            // Init
            Initialize();
        }

        [Export ("initWithFrame:")]
        public NSFlipSwitch (CGRect frameRect) : base(frameRect) {
            // Init
            Initialize();
        }

        private void Initialize() {
            this.WantsLayer = true;
            this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
        }
        #endregion

        #region Draw Methods
        public override void DrawRect (CGRect dirtyRect)
        {
            base.DrawRect (dirtyRect);

            // Use Core Graphic routines to draw our UI
            ...

        }
        #endregion

        #region Private Methods
        private void FlipSwitchState() {
            // Update state
            Value = !Value;
        }
        #endregion

    }
}

カスタム クラスについて最初に注目するのは、Register コマンドを使用してこのクラスを NSControl Xcode の Interface Builder にObjective-C公開するという点です。

[Register("NSFlipSwitch")]
public class NSFlipSwitch : NSControl

以降のセクションでは、上記のコードの残りの部分について詳しく説明します。

コントロールの状態の追跡

カスタム コントロールはスイッチであるため、スイッチのオン/オフ状態を追跡する方法が必要です。 これは、 の次のコードで NSFlipSwitch処理します。

private bool _value = false;
...

public bool Value {
    get { return _value; }
    set {
        // Save value and force a redraw
        _value = value;
        NeedsDisplay = true;
    }
}

スイッチの状態が変わったら、UI を更新する方法が必要です。 これを行うには、 を使用してコントロールの UI NeedsDisplay = trueを強制的に再描画します。

コントロールで単一のオン/オフ状態 (3 つの位置を持つマルチステート スイッチなど) が必要な場合は、 Enum を使用して状態を追跡できます。 この例では、単純な ブール が行います。

また、スイッチの状態をオンとオフの間で入れ替えるヘルパー メソッドも追加しました。

private void FlipSwitchState() {
    // Update state
    Value = !Value;
}

後で、スイッチの状態が変更されたときに呼び出し元に通知するために、このヘルパー クラスを展開します。

コントロールのインターフェイスの描画

コア グラフィック描画ルーチンを使用して、実行時にカスタム コントロールのユーザー インターフェイスを描画します。 これを行う前に、コントロールのレイヤーをオンにする必要があります。 これを行うには、次のプライベート メソッドを使用します。

private void Initialize() {
    this.WantsLayer = true;
    this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}

このメソッドは、コントロールが正しく構成されていることを確認するために、コントロールの各コンストラクターから呼び出されます。 次に例を示します。

public NSFlipSwitch (IntPtr handle) : base (handle)
{
    // Init
    Initialize();
}

次に、 メソッドをオーバーライド DrawRect し、Core Graphic ルーチンを追加してコントロールを描画する必要があります。

public override void DrawRect (CGRect dirtyRect)
{
    base.DrawRect (dirtyRect);

    // Use Core Graphic routines to draw our UI
    ...

}

コントロールの状態が変化したときに 、コントロールの視覚的表現を調整します ( オン から オフなど)。 状態が変更されるたびに、 コマンドを NeedsDisplay = true 使用して、新しい状態に対してコントロールを強制的に再描画できます。

ユーザー入力への応答

カスタム コントロールにユーザー入力を追加するには、2 つの基本的な方法があります。 マウス処理ルーチン または ジェスチャ認識エンジンをオーバーライドします。 使用する方法は、コントロールに必要な機能に基づいています。

重要

作成するカスタム コントロールの場合は、 オーバーライド メソッドまたはジェスチャ認識エンジンを使用する必要がありますが、相互に競合する可能性があるため、両方を同時に使用しないでください。

オーバーライド メソッドを使用したユーザー入力の処理

(またはNSView) をNSControl継承するオブジェクトには、マウスまたはキーボード入力を処理するためのオーバーライド メソッドがいくつかあります。 この例のコントロールでは、ユーザーがマウスの左ボタンでコントロールをクリックしたときに、スイッチの状態を [オン ] と [オフ ] の間で反転します。 これを処理するために、 クラスに次の NSFlipSwitch オーバーライド メソッドを追加できます。

#region Mouse Handling Methods
// --------------------------------------------------------------------------------
// Handle mouse with Override Methods.
// NOTE: Use either this method or Gesture Recognizers, NOT both!
// --------------------------------------------------------------------------------
public override void MouseDown (NSEvent theEvent)
{
    base.MouseDown (theEvent);

    FlipSwitchState ();
}

public override void MouseDragged (NSEvent theEvent)
{
    base.MouseDragged (theEvent);
}

public override void MouseUp (NSEvent theEvent)
{
    base.MouseUp (theEvent);
}

public override void MouseMoved (NSEvent theEvent)
{
    base.MouseMoved (theEvent);
}
## endregion

上記のコードでは、 メソッド (上記で定義) を FlipSwitchState 呼び出して、 メソッド内のスイッチの On/Off 状態を MouseDown 反転します。 これにより、コントロールが強制的に再描画され、現在の状態が反映されます。

ジェスチャ認識エンジンを使用したユーザー入力の処理

必要に応じて、ジェスチャ認識エンジンを使用して、コントロールを操作するユーザーを処理できます。 上記で追加したオーバーライドを削除し、 メソッドを Initialize 編集して、次のようにします。

private void Initialize() {
    this.WantsLayer = true;
    this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;

    // --------------------------------------------------------------------------------
    // Handle mouse with Gesture Recognizers.
    // NOTE: Use either this method or the Override Methods, NOT both!
    // --------------------------------------------------------------------------------
    var click = new NSClickGestureRecognizer (() => {
        FlipSwitchState();
    });
    AddGestureRecognizer (click);
}

ここでは、新 NSClickGestureRecognizer しい を作成し、 メソッドを呼び出して FlipSwitchState 、ユーザーがマウスの左ボタンでクリックしたときのスイッチの状態を変更します。 メソッドは AddGestureRecognizer (click) 、Gesture Recognizer をコントロールに追加します。

ここでも、どの方法を使用するかは、カスタム コントロールで何を実行しようとしているかによって異なります。 ユーザー操作への低レベルのアクセスが必要な場合は、Override メソッドを使用します。 マウス クリックなどの定義済みの機能が必要な場合は、ジェスチャ認識エンジンを使用します。

状態変更イベントへの応答

ユーザーがカスタム コントロールの状態を変更する場合、コードの状態の変化に対応する方法 (カスタム ボタンをクリックしたときに何かを行うなど) が必要です。

この機能を提供するには、 クラスを NSFlipSwitch 編集し、次のコードを追加します。

#region Events
public event EventHandler ValueChanged;

internal void RaiseValueChanged() {
    if (this.ValueChanged != null)
        this.ValueChanged (this, EventArgs.Empty);

    // Perform any action bound to the control from Interface Builder
    // via an Action.
    if (this.Action !=null)
        NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);
}
## endregion

次に、 メソッドを FlipSwitchState 編集し、次のようになります。

private void FlipSwitchState() {
    // Update state
    Value = !Value;
    RaiseValueChanged ();
}

最初に、ユーザーがスイッチの ValueChanged 状態を変更したときにアクションを実行できるように、C# コードで ハンドラーを追加できるイベントを提供します。

次に、カスタム コントロールは から NSControl継承されるため、Xcode のインターフェイス ビルダーで割り当てることができる アクション が自動的に設定されます。 状態が変更されたときにこの アクション を呼び出すには、次のコードを使用します。

if (this.Action !=null)
    NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);

まず、アクションがコントロールに割り当てられているかどうかを確認チェック。 次に、定義されている場合は Action を 呼び出します。

カスタム コントロールの使用

カスタム コントロールを完全に定義すると、C# コードまたは Xcode の Interface Builder を使用して Xamarin.Mac アプリの UI に追加できます。

Interface Builder を使用してコントロールを追加するには、まず Xamarin.Mac プロジェクトのクリーンビルドを実行してから、ファイルをMain.storyboardダブルクリックしてインターフェイス ビルダーで開いて編集します。

Xcode でのストーリーボードの編集

次に、 を Custom View ユーザー インターフェイス デザインにドラッグします。

ライブラリからカスタム ビューを選択する

[カスタム ビュー] がまだ選択されている状態で、[ ID インスペクター ] に切り替え、ビューの クラス を に NSFlipSwitch変更します。

ビューのクラスを設定する

アシスタント エディターに切り替え、カスタム コントロールのアウトレットを作成します (ファイルではなく.mファイルにViewController.hバインドしてください)。

新しいアウトレットの構成

変更を保存し、Visual Studio for Macに戻り、変更を同期できるようにします。ファイルをViewController.cs編集し、メソッドをViewDidLoad次のようにします。

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    // Do any additional setup after loading the view.
    OptionTwo.ValueChanged += (sender, e) => {
        // Display the state of the option switch
        Console.WriteLine("Option Two: {0}", OptionTwo.Value);
    };
}

ここでは、クラスで上記で定義した ValueChanged イベントに NSFlipSwitch 応答し、ユーザーがコントロールをクリックしたときに現在の を書き出します。

必要に応じて、Interface Builder に戻り、コントロールに 対してアクション を定義できます。

新しいアクションの構成

ここでも、ファイルを ViewController.cs 編集し、次のメソッドを追加します。

partial void OptionTwoFlipped (Foundation.NSObject sender) {
    // Display the state of the option switch
    Console.WriteLine("Option Two: {0}", OptionTwo.Value);
}

重要

イベントを使用するか、インターフェイス ビルダーでアクションを定義する必要がありますが、両方のメソッドを同時に使用しないか、相互に競合する可能性があります。

まとめ

この記事では、Xamarin.Mac アプリケーションで再利用可能なカスタム ユーザー インターフェイス コントロールを作成する方法について詳しく説明しました。 カスタム コントロール UI を描画する方法、マウスとユーザーの入力に応答する 2 つのメイン方法、および Xcode の Interface Builder で新しいコントロールを Actions に公開する方法について説明しました。