次の方法で共有


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

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

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

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

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

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

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

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

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

カスタム ユーザー インターフェイス コントロールに情報 (カスタム グラフやグラフィック ツールなど) が表示されるだけの場合は、NSControl の代わりに NSView を継承することをお勧めします。

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

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

カスタム コントロールの構築

この記事で作成するカスタム コントロールは、ユーザー入力 (マウスの左ボタンのクリック) に応答するものなので、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

    }
}

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

[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 を更新するには、コントロールで UI を NeedsDisplay = trueで 強制的に再描画させます。

コントロールで 1 つ以上オン/オフ状態 (たとえば、3 つの位置を持つマルチステート スイッチ) が必要な場合は、Enum を使用して状態を追跡できます。 この例では、単純な bool 値で十分です。

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

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

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

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

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

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 つの基本的な方法があります。 どの方法を使用するかは、コントロールが必要とする機能に基づいています。

重要

作成するカスタム コントロールの場合は、 Override Methods or Gesture Recognizers を使用する必要がありますが、相互に競合する可能性があるため、両方を同時に使用することはできません。

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

NSControl (または NSView) を継承したオブジェクトには、マウスやキーボード入力を処理するためのオーバーライド メソッドがいくつかあります。 この例のコントロールでは、ユーザーがマウスの左ボタンでコントロールをクリックしたときに、スイッチの状態をオンオフの間で切り替えしたいとします。 これを処理するために、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 メソッド (上記で定義) を呼び出して、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) メソッドは、ジェスチャ認識エンジンをコントロールに追加します。

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

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

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

この機能を提供するには、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 ();
}

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

次に、カスタム コントロールが NSControl を継承しているため、Xcode の Interface Builder で割り当てることができるアクションを自動的に持ちます。 状態が変化したときにこのアクションを呼び出すには、次のコードを使用します。

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

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

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

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

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

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

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

ライブラリからのカスタム ビューの選択

カスタム ビューを選択したまま、Identity Inspector に切り替え、ビューのクラスNSFlipSwitch に変更します。

ビューのクラスの設定

Assistant Editor に切り替え、カスタム コントロールのアウトレットを作成 します (.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);
    };
}

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

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

新しいアクションの構成

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

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

重要

Interface Builder でイベントを使用するか、アクションを定義する必要がありますが、相互に競合する可能性があるため、両方のメソッドを同時に使用することはお控えください。

まとめ

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