Xamarin.Mac 中的标准控件

本文介绍如何在 Xamarin.Mac 应用程序中使用标准 AppKit 控件,例如按钮、标签、文本字段、复选框和分段控件。 其中介绍了如何使用 Interface Builder 将其添加到界面,并在代码中与其交互。

在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,你可以访问的 AppKit 控件与使用 Objective-C 和 Xcode 的开发人员访问的控件相同。 由于 Xamarin.Mac 与 Xcode 直接集成,你可以使用 Xcode 的 Interface Builder 来创建和维护 AppKit 控件(或选择直接使用 C# 代码创建)

AppKit 控件是用于创建 Xamarin.Mac 应用程序用户界面的 UI 元素。 它们由按钮、标签、文本字段、复选框和分段控件等元素组成,当用户操控它们时会产生即时操作或可见结果。

示例应用主屏幕

本文将介绍在 Xamarin.Mac 应用程序中使用 AppKit 控件的基础知识。 强烈建议先阅读 Hello, Mac 一文,特别是 Xcode 和 Interface Builder 简介输出口和操作部分,因为其中介绍了我们将在本文中使用的关键概念和技术。

你可能还需要查看 Xamarin.Mac 内部机制文档的向 Objective-C 公开 C# 类/方法部分,因为其中介绍了用于将 C# 类连接到 Objective-C 对象和 UI 元素的 RegisterExport 命令。

控件和视图简介

macOS(以前称为 Mac OS X)通过 AppKit 框架提供了一组标准的用户界面控件。 它们由按钮、标签、文本字段、复选框和分段控件等元素组成,当用户操控它们时会产生即时操作或可见结果。

所有 AppKit 控件都具有适合大多数用途的标准内置外观,其中一些控件指定了在窗口框架区域或“振动效果”上下文中(例如在侧边栏区域或通知中心小组件中)使用的替代外观

Apple 建议在使用 AppKit 控件时遵循以下准则:

  • 避免在同一视图中混合控件大小。
  • 一般情况下,应避免垂直调整控件大小。
  • 在控件中使用系统字体和正确的文本大小。
  • 在控件之间使用适当的间距。

有关详细信息,请参阅 Apple 的 OS X 人机界面准则关于控件和视图部分。

在窗口框架中使用控件

一部分 AppKit 控件包含的显示样式允许它们包含在窗口的框架区域中。 有关示例,请参阅邮件应用的工具栏:

Mac 窗口边框

  • 圆形纹理按钮 - 样式为 NSTexturedRoundedBezelStyleNSButton
  • 纹理圆形分段控件 - 样式为 NSSegmentStyleTexturedRoundedNSSegmentedControl
  • 纹理圆形分段控件 - 样式为 NSSegmentStyleSeparatedNSSegmentedControl
  • 圆形纹理弹出菜单 - 样式为 NSTexturedRoundedBezelStyleNSPopUpButton
  • 圆形纹理下拉菜单 - 样式为 NSTexturedRoundedBezelStyleNSPopUpButton
  • 搜索栏 - NSSearchField

Apple 建议在窗口框架中使用 AppKit 控件时遵循以下准则:

  • 不要在窗口主体中使用窗口框架特定的控件样式。
  • 不要在窗口框架中使用窗口主体控件或样式。

有关详细信息,请参阅 Apple 的 OS X 人机界面准则关于控件和视图部分。

在 Interface Builder 中创建用户界面

创建新的 Xamarin.Mac Cocoa 应用程序时,默认情况下会获得标准空白窗口。 此窗口在项目中自动包含的 .storyboard 文件中定义。 若要编辑窗口设计,请在“解决方案资源管理器”中双击 Main.storyboard 文件:

在解决方案资源管理器中选择主情节提要

这将在 Xcode 的 Interface Builder 中打开窗口设计:

在 Xcode 中编辑情节提要

若要创建用户界面,需要将 UI 元素(AppKit 控件)从库检查器拖放到 Interface Builder 中的“界面编辑器”。 在以下示例中,已将一个垂直拆分视图控件从“库检查器”拖放到“界面编辑器”中的窗口

从库中选择拆分视图

有关在 Interface Builder 中创建用户界面的详细信息,请参阅我们的 Xcode 和 Interface Builder 简介文档。

调整大小和定位

将控件包含在用户界面中后,使用约束编辑器通过手动输入值来设置其位置和大小,并控制在调整父窗口或视图大小时控件的自动定位和大小调整

设置约束

使用“自动调整大小”框外部的“红色工字梁”将控件粘贴到给定的 (x,y) 位置。 例如:

编辑约束

指定所选控件(在“层次结构视图”和“界面编辑器”中)在调整大小或移动时将粘贴在窗口或视图的顶部和右侧位置

编辑器控件属性的其他元素,例如“高度”和“宽度”:

设置高度

还可以使用“对齐编辑器”控制具有约束的元素的对齐方式

对齐编辑器

重要

与 iOS 不同,(0,0) 是屏幕的左上角,而在 macOS 中,(0,0) 是左下角。 这是因为 macOS 使用数学坐标系,数值向上和向右递增。 在用户界面上放置 AppKit 控件时需要考虑这一点。

设置自定义类

有时,在使用 AppKit 控件时,需要对现有控件进行子类化,并为该类创建自己的自定义版本。 例如,定义源列表的自定义版本:

using System;
using AppKit;
using Foundation;

namespace AppKit
{
    [Register("SourceListView")]
    public class SourceListView : NSOutlineView
    {
        #region Computed Properties
        public SourceListDataSource Data {
            get {return (SourceListDataSource)this.DataSource; }
        }
        #endregion

        #region Constructors
        public SourceListView ()
        {

        }

        public SourceListView (IntPtr handle) : base(handle)
        {

        }

        public SourceListView (NSCoder coder) : base(coder)
        {

        }

        public SourceListView (NSObjectFlag t) : base(t)
        {

        }
        #endregion

        #region Override Methods
        public override void AwakeFromNib ()
        {
            base.AwakeFromNib ();

        }
        #endregion

        #region Public Methods
        public void Initialize() {

            // Initialize this instance
            this.DataSource = new SourceListDataSource (this);
            this.Delegate = new SourceListDelegate (this);

        }

        public void AddItem(SourceListItem item) {
            if (Data != null) {
                Data.Items.Add (item);
            }
        }
        #endregion

        #region Events
        public delegate void ItemSelectedDelegate(SourceListItem item);
        public event ItemSelectedDelegate ItemSelected;

        internal void RaiseItemSelected(SourceListItem item) {
            // Inform caller
            if (this.ItemSelected != null) {
                this.ItemSelected (item);
            }
        }
        #endregion
    }
}

其中,[Register("SourceListView")] 指令将 SourceListView 类公开给 Objective-C,以便可以在 Interface Builder 中使用。 有关详细信息,请参阅 Xamarin.Mac 内部文档中的向 Objective-C 公开 C# 类/方法部分,其中解释了用于将 C# 类连接到 Objective-C 对象和 UI 元素的 RegisterExport 命令。

完成上述代码后,可以将要扩展的基类型的 AppKit 控件拖放到设计图面上(在以下示例中为源列表),然后切换到“标识检查器”,并将“自定义类”设置为向 Objective-C 公开的名称(例如 SourceListView):

在 Xcode 中设置自定义类

公开出口和操作

在通过 C# 代码访问 AppKit 控件之前,需要将其公开为出口或操作。 为此,请在“界面层次结构”或“界面编辑器”中选择给定的控件,然后切换到“助手视图”(确保获取了选择进行编辑的窗口的 .h):

选择要编辑的正确文件

按住 Ctrl 键,并从 AppKit 控件拖动到给定的 .h 文件,以开始创建出口或操作

拖动以创建输出口或操作

选择要创建的公开类型,并为出口或操作指定名称

配置输出口或操作

有关使用输出口和操作的详细信息,请参阅我们的 Xcode 和 Interface Builder 简介文档中的出口和操作部分

与 Xcode 同步更改

从 Xcode 切换回 Visual Studio for Mac 时,在 Xcode 中所作的任何更改将会自动与 Xamarin.Mac 项目同步。

如果在“解决方案资源管理器”中选择 SplitViewController.designer.cs,你将可以看到输出和操作在 C# 代码中是如何连接的

与 Xcode 同步更改

注意 SplitViewController.designer.cs 文件中的定义:

[Outlet]
AppKit.NSSplitViewItem LeftController { get; set; }

[Outlet]
AppKit.NSSplitViewItem RightController { get; set; }

[Outlet]
AppKit.NSSplitView SplitView { get; set; }

与 Xcode 的 MainWindow.h 文件中的定义对齐:

@interface SplitViewController : NSSplitViewController {
    NSSplitViewItem *_LeftController;
    NSSplitViewItem *_RightController;
    NSSplitView *_SplitView;
}

@property (nonatomic, retain) IBOutlet NSSplitViewItem *LeftController;

@property (nonatomic, retain) IBOutlet NSSplitViewItem *RightController;

@property (nonatomic, retain) IBOutlet NSSplitView *SplitView;

如你所见,Visual Studio for Mac 会侦听对 .h 文件的更改,然后在相应的 .designer.cs 文件中自动同步这些更改,以将其公开给应用程序。 你可能还会注意到,SplitViewController.designer.cs 是一个分部类,因此 Visual Studio for Mac 不必修改 SplitViewController.cs,该项将覆盖我们对类所做的任何更改。

通常,你永远都不需要自行打开 SplitViewController.designer.cs,这里提供它只是为了进行介绍。

重要

大多数情况下,Visual Studio for Mac 会自动发现 Xcode 中所作的任何更改,并将其同步到 Xamarin.Mac 项目。 如果同步不自动进行,请切换回 Xcode,然后再次切换到 Visual Studio for Mac。 这通常会开始同步周期。

使用按钮

AppKit 提供了多种可在用户界面设计中使用的按钮类型。 有关详细信息,请参阅 Apple OS X 人机界面准则按钮部分。

不同按钮类型的示例

如果按钮已通过输出口公开,则按下该按钮时,以下代码将做出响应

ButtonOutlet.Activated += (sender, e) => {
        FeedbackLabel.StringValue = "Button Outlet Pressed";
};

对于通过操作公开的按钮,系统会自动使用在 Xcode 中选择的名称创建一个 public partial 方法。 若要响应操作,请完成定义了该操作的类中的分部方法。 例如:

partial void ButtonAction (Foundation.NSObject sender) {
    // Do something in response to the Action
    FeedbackLabel.StringValue = "Button Action Pressed";
}

对于具有状态(例如“On”和“Off”)的按钮,可以使用 State 属性针对 NSCellStateValue 枚举检查或设置状态。 例如:

DisclosureButton.Activated += (sender, e) => {
    LorumIpsum.Hidden = (DisclosureButton.State == NSCellStateValue.On);
};

其中 NSCellStateValue 可以是:

  • On - 按下了按钮或选择了控件(例如选中了复选框)
  • Off - 未按下按钮或未选择控件
  • Mixed - On 和 Off 状态的混合形式

将按钮标记为默认按钮并设置等效键

对于添加到用户界面设计中的任何按钮,可以将该按钮标记为默认按钮,当用户按键盘上的回车键/Enter 键时将激活该按钮。 在 macOS 中,此按钮默认具有蓝色背景色。

若要将按钮设置为默认按钮,请在 Xcode 的 Interface Builder 中选择它。 接下来,在“属性检查器”中,选择“等效键”字段并按回车键/Enter 键

编辑等效键

同样,可以分配任何可用于通过键盘而不是鼠标激活按钮的按键序列。 例如,按上图中所示的 Command-C 键。

当应用运行并且包含按钮的窗口为按键并获得焦点时,如果用户按 Command-C,按钮的操作将被激活(就像用户单击了该按钮一样)。

使用复选框和单选按钮

AppKit 提供了多种类型的复选框和单选按钮组,可以在用户界面设计中使用。 有关详细信息,请参阅 Apple OS X 人机界面准则按钮部分。

可用复选框类型的示例

复选框和单选按钮(通过出口公开)具有状态(例如 On 和 Off),可以使用 State 属性针对 NSCellStateValue 枚举检查或设置状态。 例如:

AdjustTime.Activated += (sender, e) => {
    FeedbackLabel.StringValue = string.Format("Adjust Time: {0}",AdjustTime.State == NSCellStateValue.On);
};

其中 NSCellStateValue 可以是:

  • On - 按下了按钮或选择了控件(例如选中了复选框)
  • Off - 未按下按钮或未选择控件
  • Mixed - On 和 Off 状态的混合形式

若要选择单选按钮组中的按钮,请公开单选按钮以将其选择为出口,并设置其 State 属性。 例如:

partial void SelectCar (Foundation.NSObject sender) {
    TransportationCar.State = NSCellStateValue.On;
    FeedbackLabel.StringValue = "Car Selected";
}

若要获取单选按钮集合作为一个组并自动处理所选状态,请创建新的操作,并将该组中的每个按钮附加到该操作

创建新操作

接下来,为“属性检查器”中的每个单选按钮分配唯一的 Tag

编辑单选按钮标记

保存更改并返回 Visual Studio for Mac,添加代码来处理所有单选按钮附加到的操作

partial void NumberChanged(Foundation.NSObject sender)
{
    var check = sender as NSButton;
    Console.WriteLine("Changed to {0}", check.Tag);
}

可以使用 Tag 属性来查看选择了哪个单选按钮。

使用菜单控件

AppKit 提供了多种类型的菜单控件,可以在用户界面设计中使用。 有关详细信息,请参阅 Apple OS X 人机界面准则菜单控件部分。

示例菜单控件

提供菜单控件数据

可将 macOS 可用的菜单控件设置为根据内部列表(可以在 Interface Builder 中预定义或通过代码填充)填充下拉列表,或者通过提供自己的自定义外部数据源进行填充。

使用内部数据

除了在 Interface Builder 中定义项之外,菜单控件(例如 NSComboBox)还提供了一整套方法用于在它们维护的内部列表中添加、编辑或删除项:

  • Add - 将新项添加到列表末尾。
  • GetItem - 返回给定索引处的项。
  • Insert - 在列表中的给定位置插入新项。
  • IndexOf - 返回给定项的索引。
  • Remove - 从列表中删除给定项。
  • RemoveAll - 从列表中删除所有项。
  • RemoveAt - 删除给定索引处的项。
  • Count - 返回列表中的项数。

重要

如果使用外部数据源 (UsesDataSource = true),则调用上述任何方法都会引发异常。

使用外部数据源

可以选择性地使用外部数据源并为项提供自己的后备存储(例如 SQLite 数据库),而不是使用内置内部数据来为菜单控件提供行。

若要使用外部数据源,需要创建菜单控件数据源的实例(例如 NSComboBoxDataSource),并重写多个方法以提供所需的数据:

  • ItemCount - 返回列表中的项数。
  • ObjectValueForItem - 返回给定索引的项的值。
  • IndexOfItem - 返回给定项值的索引。
  • CompletedString - 返回部分类型化项值的第一个匹配项值。 仅当已启用自动完成 (Completes = true) 时,才会调用此方法。

有关更多详细信息,请参阅使用数据库文档的数据库和组合框部分。

调整列表的外观

以下方法可用于调整菜单控件的外观:

  • HasVerticalScroller - 如果为 true,则控件将显示垂直滚动条。
  • VisibleItems - 调整打开控件时显示的项数。 默认值为五 (5)。
  • IntercellSpacing - 通过提供 NSSize 来调整给定项周围的空间,其中 Width 指定左右边距,Height 指定项前后的空间。
  • ItemHeight - 指定列表中每个项的高度。

对于 NSPopupButtons 下拉菜单类型,第一个菜单项提供控件的标题。 例如:

示例菜单控件

若要更改标题,请将此项公开为出口并使用如下所示的代码

DropDownSelected.Title = "Item 1";

操控选定的项

可以使用以下方法和属性来操控菜单控件列表中的选定项:

  • SelectItem - 选择给定索引处的项。
  • Select - 选择给定的项值。
  • DeselectItem - 取消选择给定索引处的项。
  • SelectedIndex - 返回当前所选项的索引。
  • SelectedValue - 返回当前所选项的值。

使用 ScrollItemAtIndexToTop 可将给定索引处的项显示在列表顶部,使用 ScrollItemAtIndexToVisible 可滚动列表,直到给定索引处的项可见。

对事件作出响应

菜单控件提供以下事件来响应用户交互:

  • SelectionChanged - 当用户从列表中选择一个值时调用。
  • SelectionIsChanging - 在新用户选择的项成为活动选择之前调用。
  • WillPopup - 在显示项下拉列表之前调用。
  • WillDismiss - 在关闭项下拉列表之前调用。

对于 NSComboBox 控件,它们包含与 NSTextField 相同的所有事件,例如每当用户编辑组合框中文本的值时就会调用的 Changed 事件。

(可选)可以通过将 Interface Builder 中定义的内部数据菜单项附加到操作来响应该菜单项的选择,并使用如下所示的代码来响应由用户触发的操作

partial void ItemOne (Foundation.NSObject sender) {
    DropDownSelected.Title = "Item 1";
    FeedbackLabel.StringValue = "Item One Selected";
}

有关使用菜单和菜单控件的详细信息,请参阅我们的菜单弹出按钮和下拉列表文档。

使用选择控件

AppKit 提供了多种类型的选择控件,可以在用户界面设计中使用。 有关详细信息,请参阅 Apple OS X 人机界面准则选择控件部分。

示例选择控件

可以通过两种方式来跟踪选择控件何时发生了用户交互:将此控件公开为操作; 例如:

partial void SegmentButtonPressed (Foundation.NSObject sender) {
    FeedbackLabel.StringValue = string.Format("Button {0} Pressed",SegmentButtons.SelectedSegment);
}

将一个委托附加到 Activated 事件。 例如:

TickedSlider.Activated += (sender, e) => {
    FeedbackLabel.StringValue = string.Format("Stepper Value: {0:###}",TickedSlider.IntValue);
};

若要设置或读取选择控件的值,请使用 IntValue 属性。 例如:

FeedbackLabel.StringValue = string.Format("Stepper Value: {0:###}",TickedSlider.IntValue);

特殊控件(例如颜色条和图像条)具有其值类型的特定属性。 例如:

ColorWell.Color = NSColor.Red;
ImageWell.Image = NSImage.ImageNamed ("tag.png");

NSDatePicker 具有以下可直接处理日期和时间的属性:

  • DateValue - 以 NSDate 形式表示的当前日期和时间值。
  • Local - 以 NSLocal 形式表示的用户位置。
  • TimeInterval - 以 Double 形式表示的时间值。
  • TimeZone - 以 NSTimeZone 形式表示的用户时区。

使用指示器控件

AppKit 提供了多种类型的指示器控件,可以在用户界面设计中使用。 有关详细信息,请参阅 Apple OS X 人机界面准则指示器控件部分。

示例指示器控件

可以通过两种方式来跟踪指示器控件何时发生了用户交互:将此控件公开为操作或出口;将一个委托附加到 Activated 事件。 例如:

LevelIndicator.Activated += (sender, e) => {
    FeedbackLabel.StringValue = string.Format("Level: {0:###}",LevelIndicator.DoubleValue);
};

若要读取或设置指示器控件的值,请使用 DoubleValue 属性。 例如:

FeedbackLabel.StringValue = string.Format("Rating: {0:###}",Rating.DoubleValue);

“不确定”和“异步进度”指示器应以动画显示。 当它们显示时,使用 StartAnimation 方法来启动动画。 例如:

Indeterminate.StartAnimation (this);
AsyncProgress.StartAnimation (this);

调用 StopAnimation 方法会停止动画。

使用文本控件

AppKit 提供了多种类型的文本控件,可以在用户界面设计中使用。 有关详细信息,请参阅 Apple OS X 人机界面准则文本控件部分。

示例文本控件

对于文本字段 (NSTextField),以下事件可用于跟踪用户交互:

  • Changed - 每当用户更改字段值时就会触发。 例如,在键入每个字符时。
  • EditingBegan - 当用户选择要编辑的字段时触发
  • EditingEnded - 当用户在字段中按 Enter 键或离开字段时

使用 StringValue 属性读取或设置字段的值。 例如:

FeedbackLabel.StringValue = string.Format("User ID: {0}",UserField.StringValue);

对于显示或编辑数值的字段,可以使用 IntValue 属性。 例如:

FeedbackLabel.StringValue = string.Format("Number: {0}",NumberField.IntValue);

NSTextView 提供具有内置格式的全功能文本编辑和显示区域。 与 NSTextField 一样,可以使用 StringValue 属性读取或设置区域的值。

使用内容视图

AppKit 提供了多种类型的内容视图,可以在用户界面设计中使用。 有关详细信息,请参阅 Apple OS X 人机界面指南内容视图部分。

示例内容视图

弹出窗口

弹出窗口是一种暂时性的 UI 元素,提供与特定控件或屏幕区域直接相关的功能。 弹出窗口漂浮在包含与其相关的控件或区域的窗口上方,其边框包含一个箭头用于指示它出现的位置。

若要创建弹出窗口,请执行以下操作:

  1. 打开要将弹出窗口添加到的窗口的 .storyboard 文件,在“解决方案资源管理器”中双击该文件即可

  2. 将一个视图控制器从“库检查器”拖放到“界面编辑器”上

    从库中选择视图控制器

  3. 定义“自定义视图”的大小和布局

    编辑布局

  4. 按住 Ctrl 键,同时单击弹出窗口的源并将其拖放到“视图控制器”上

    拖动以创建 segue

  5. 从弹出菜单中选择“弹出窗口”

    设置 segue 类型

  6. 保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。

选项卡视图

选项卡视图由选项卡列表(类似于分段控件)和一组称为“窗格”的视图组成。 当用户选择一个新选项卡时,将显示附加到该选项卡的窗格。 每个窗格包含自身的一组控件。

在 Xcode 的 Interface Builder 中使用选项卡视图时,请使用“属性检查器”设置选项卡的数量

编辑选项卡数

选择“界面层次结构”中的每个选项卡,以设置其“标题”并将 UI 元素添加到其窗格

在 Xcode 中编辑选项卡

数据绑定 AppKit 控件

通过在 Xamarin.Mac 应用程序中使用键值编码和数据绑定技术,可以大大减少必须编写和维护的代码量,以填充和使用 UI 元素。 还可以从前端用户界面 (Model-View-Controller)进一步分离支持数据(数据模型),从而更轻松地维护、更灵活的应用程序设计。

键值编码 (KVC) 是间接访问对象属性的机制,使用键(特殊格式的字符串)来标识属性,而不是通过实例变量或访问器方法 (get/set) 访问它们。 通过在 Xamarin.Mac 应用程序中实现符合键值编码的访问器,可以访问其他 macOS 功能,例如键值观察 (KVO)、数据绑定、核心数据、Cocoa 绑定和可脚本性。

有关详细信息,请参阅数据绑定和键值编码文档中的简单数据绑定部分。

总结

本文详细介绍了如何在 Xamarin.Mac 应用程序中使用标准 AppKit 控件,例如按钮、标签、文本字段、复选框和分段控件。 其中介绍了如何将这些控件添加到 Xcode 的 Interface Builder 中的用户界面设计中,通过出口和操作向代码公开它们,以及在 C# 代码中使用 AppKit 控件。