在 Xamarin.Mac 中创建自定义控件

在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,可以访问与使用 、SwiftXcodeObjective-C开发人员相同的用户控件。 由于 Xamarin.Mac 与 Xcode 直接集成,因此可以使用 Xcode 的 Interface Builder 创建和维护用户控件 (,也可以选择直接在 C# 代码) 中创建它们。

虽然 macOS 提供了大量内置用户控件,但有时可能需要创建自定义控件,以提供未现成提供的功能或匹配自定义 UI 主题 (,例如游戏界面) 。

自定义 UI 控件的示例

本文介绍在 Xamarin.Mac 应用程序中创建可重用的自定义用户界面控件的基础知识。 强烈建议先完成 Hello, Mac 一文,特别是 Xcode 和接口生成器简介 以及 输出口和操作 部分,因为它涵盖了我们将在本文中使用的关键概念和技术。

你可能还想要查看 Xamarin.Mac Internals 文档的向 Objective-CC# 类/方法公开部分,其中介绍了Register用于将 C# 类Objective-C连接到对象和 UI 元素的 和 Export 命令。

自定义控件简介

如上所述,有时可能需要创建可重用的自定义用户界面控件,以便为 Xamarin.Mac 应用的 UI 提供独特的功能,或创建自定义 UI 主题 (,例如游戏界面) 。

在这些情况下,可以轻松继承 NSControl 并创建自定义工具,该工具可以通过 C# 代码或通过 Xcode 的 Interface Builder 添加到应用的 UI。 继承自 NSControl 自定义控件将自动具有内置用户界面控件 (的所有标准功能,例如 NSButton) 。

如果自定义用户界面控件仅显示 (的信息(如自定义图表和图形工具) ),则可能需要继承自 NSView 而不是 NSControl

无论使用哪个基类,创建自定义控件的基本步骤都是相同的。

在本文中,将创建一个自定义翻转开关组件,该组件提供唯一的用户界面主题和生成功能齐全的自定义用户界面控件的示例。

生成自定义控件

由于我们创建的自定义控件将响应用户输入 (鼠标左键单击) ,因此我们将继承自 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。 为此,我们强制控件使用 NeedsDisplay = true重绘其 UI。

如果我们的控件需要单个开/关状态 (例如) 有 3 个位置的多状态开关,我们本可以使用 枚举 来跟踪状态。 对于我们的示例,一个简单的 bool 将执行此操作。

我们还添加了一个帮助程序方法,用于在“开”和“关”之间交换开关的状态:

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 命令强制控件重新绘制新状态。

响应用户输入

有两种基本方法可以将用户输入添加到自定义控件: 替代鼠标处理例程手势识别器。 我们使用哪种方法将基于控件所需的功能。

重要

对于创建的任何自定义控件,应使用 替代方法手势识别器,但不能同时使用两者,因为它们可能会相互冲突。

使用重写方法处理用户输入

继承自 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) 将笔势识别器添加到 控件。

同样,使用哪种方法取决于我们尝试使用自定义控件完成的操作。 如果需要对用户交互进行低级别访问,请使用 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 的 Interface Builder 中分配的 Action 。 若要在状态 更改时调用 此操作,请使用以下代码:

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 文件以在 Interface Builder 中将其打开进行编辑:

在 Xcode 中编辑情节提要

接下来,将 拖动 Custom View 到用户界面设计中:

从库中选择自定义视图

在自定义视图仍处于选中状态的情况下,切换到 标识检查器 ,并将视图的 更改为 NSFlipSwitch

设置 View 的类

切换到 “助理编辑器” 并为自定义控件创建一个 输出口 (确保将其绑定在 ViewController.h 文件中,而不是 .m) 的文件:

配置新的输出口

保存更改,返回到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 并在用户单击控件时写出当前

(可选)我们可以返回到接口生成器,并在控件上定义 操作

配置新操作

同样,编辑 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,这两种main响应鼠标和用户输入的方法,以及如何在 Xcode 的 Interface Builder 中向 Actions 公开新控件。