在 Xamarin.Mac 中不使用 .storyboard/.xib 的用户界面设计
本文介绍如何直接通过 C# 代码创建 Xamarin.Mac 应用程序的用户界面,而无需使用情节提要文件、.xib 文件或 Interface Builder。
概述
在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,你可以访问的用户界面元素和工具与使用 Objective-C 和 Xcode 的开发人员访问的元素和工具相同。 通常,创建 Xamarin.Mac 应用程序时,会将 Xcode 的 Interface Builder 与 .storyboard 或 .xib 文件配合使用来创建和维护应用程序的用户界面。
也可以直接使用 C# 代码创建部分或全部的 Xamarin.Mac 应用程序用户界面。 本文将会介绍使用 C# 代码创建用户界面和 UI 元素的基础知识。
切换窗口以使用代码
创建新的 Xamarin.Mac Cocoa 应用程序时,默认情况下会获得标准空白窗口。 此窗口自动在包含在项目中的 Main.storyboard(或过去的 MainWindow.xib)文件中定义。 其中还包括一个 ViewController.cs 文件,用于管理应用程序的主视图(或过去的 MainWindow.cs 和 MainWindowController.cs 文件)。
若要切换到应用程序的 Xibless 窗口,请执行以下操作:
打开想要停止使用
.storyboard
或 .xib 文件的应用程序,在 Visual Studio for Mac 中定义用户界面。在 Solution Pad 中,右键单击 Main.storyboard 或 MainWindow.xib 文件,然后选择“删除”:
在“删除对话框”中,单击“删除”按钮,彻底删除项目中的 .storyboard 或 .xib 文件:
接下来,我们需要修改 MainWindow.cs 文件来定义窗口的布局,并修改 ViewController.cs 或 MainWindowController.cs 文件以创建 MainWindow
类的实例,因为我们不再使用 .storyboard 或 .xib 文件。
用户界面使用情节提要的新式 Xamarin.Mac 应用程序可能不会自动包含 MainWindow.cs、ViewController.cs 或 MainWindowController.cs 文件。 根据需要,只需向项目添加一个新的空的 C# 类(添加>新建文件…>常规>空类),并将其命名为与缺少的文件相同的名称。
在代码中定义窗口
接下来,编辑 MainWindow.cs 文件,使其如下所示:
using System;
using Foundation;
using AppKit;
using CoreGraphics;
namespace MacXibless
{
public partial class MainWindow : NSWindow
{
#region Private Variables
private int NumberOfTimesClicked = 0;
#endregion
#region Computed Properties
public NSButton ClickMeButton { get; set;}
public NSTextField ClickMeLabel { get ; set;}
#endregion
#region Constructors
public MainWindow (IntPtr handle) : base (handle)
{
}
[Export ("initWithCoder:")]
public MainWindow (NSCoder coder) : base (coder)
{
}
public MainWindow(CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation): base (contentRect, aStyle,bufferingType,deferCreation) {
// Define the user interface of the window here
Title = "Window From Code";
// Create the content view for the window and make it fill the window
ContentView = new NSView (Frame);
// Add UI elements to window
ClickMeButton = new NSButton (new CGRect (10, Frame.Height-70, 100, 30)){
AutoresizingMask = NSViewResizingMask.MinYMargin
};
ContentView.AddSubview (ClickMeButton);
ClickMeLabel = new NSTextField (new CGRect (120, Frame.Height - 65, Frame.Width - 130, 20)) {
BackgroundColor = NSColor.Clear,
TextColor = NSColor.Black,
Editable = false,
Bezeled = false,
AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.MinYMargin,
StringValue = "Button has not been clicked yet."
};
ContentView.AddSubview (ClickMeLabel);
}
#endregion
#region Override Methods
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Wireup events
ClickMeButton.Activated += (sender, e) => {
// Update count
ClickMeLabel.StringValue = (++NumberOfTimesClicked == 1) ? "Button clicked one time." : string.Format("Button clicked {0} times.",NumberOfTimesClicked);
};
}
#endregion
}
}
让我们来介绍几个关键元素。
首先,我们添加了几个计算属性,它们的作用类似于插座(如同该窗口是在 .storyboard 或 .xib 文件中创建的一样):
public NSButton ClickMeButton { get; set;}
public NSTextField ClickMeLabel { get ; set;}
这样,我们就能访问想要在窗口上显示的 UI 元素。 由于该窗口并不是从 .storyboard 或 .xib 文件放大的,因此我们需要将其实例化(如我们之后会在 MainWindowController
类中看到的那样)。 这就是这个新的构造函数的作用:
public MainWindow(CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation): base (contentRect, aStyle,bufferingType,deferCreation) {
...
}
在这里,我们会设计窗口的布局,并放置创建用户界面所需的任何 UI 元素。 需要一个内容视图来包含这些元素,然后才能将任何 UI 元素添加到窗口:
ContentView = new NSView (Frame);
这会创建一个填充该窗口的内容视图。 接下来,我们将第一个 UI 元素 NSButton
添加到窗口:
ClickMeButton = new NSButton (new CGRect (10, Frame.Height-70, 100, 30)){
AutoresizingMask = NSViewResizingMask.MinYMargin
};
ContentView.AddSubview (ClickMeButton);
这里首先要注意的是,与 iOS 不同,macOS 使用数学表示法来定义其窗口坐标系。 因此,原点位于窗口的左下角,值向着窗口的右侧和右上角不断增加。 创建新的 NSButton
时,我们会考虑到这一点,因为我们在屏幕上定义其位置和大小。
AutoresizingMask = NSViewResizingMask.MinYMargin
属性告诉按钮,当窗口垂直调整大小时,我们希望保持窗口顶部的位置不变。 这同样也是必需的,因为 (0,0) 位于窗口的左下角。
最后,ContentView.AddSubview (ClickMeButton)
方法将 NSButton
添加到内容视图。这样,当应用程序正在运行且窗口显示时,它会显示在屏幕上。
接下来,向会显示 NSButton
单击次数的窗口添加一个标签:
ClickMeLabel = new NSTextField (new CGRect (120, Frame.Height - 65, Frame.Width - 130, 20)) {
BackgroundColor = NSColor.Clear,
TextColor = NSColor.Black,
Editable = false,
Bezeled = false,
AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.MinYMargin,
StringValue = "Button has not been clicked yet."
};
ContentView.AddSubview (ClickMeLabel);
由于 macOS 没有特定的标签 UI 元素,因此我们添加了一个特殊样式且不可编辑的 NSTextField
来作为标签使用。 就像之前的按钮一样,考虑到大小和位置,(0,0) 位于窗口的左下角。 AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.MinYMargin
属性使用 or 运算符合并两个 NSViewResizingMask
功能。 这会让窗口垂直调整大小,以及横向调整大小导致宽度缩小和扩大时,该标签留在窗口顶部的相同位置。
再次调整,ContentView.AddSubview (ClickMeLabel)
方法将 NSTextField
添加到内容视图。这样,当应用程序正在运行且窗口打开时,它会显示在屏幕上。
调整窗口控制器
由于 MainWindow
的设计不再从 .storyboard 或 .xib 文件加载,我们将需要对窗口控制器进行一些调整。 编辑 MainWindowController.cs 文件,使其如下所示:
using System;
using Foundation;
using AppKit;
using CoreGraphics;
namespace MacXibless
{
public partial class MainWindowController : NSWindowController
{
public MainWindowController (IntPtr handle) : base (handle)
{
}
[Export ("initWithCoder:")]
public MainWindowController (NSCoder coder) : base (coder)
{
}
public MainWindowController () : base ("MainWindow")
{
// Construct the window from code here
CGRect contentRect = new CGRect (0, 0, 1000, 500);
base.Window = new MainWindow(contentRect, (NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Miniaturizable | NSWindowStyle.Resizable), NSBackingStore.Buffered, false);
// Simulate Awaking from Nib
Window.AwakeFromNib ();
}
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
}
public new MainWindow Window {
get { return (MainWindow)base.Window; }
}
}
}
让我们介绍此修改的关键元素。
首先,我们定义 MainWindow
类的一个新的实例,并将其分配给基础窗口的 Window
属性:
CGRect contentRect = new CGRect (0, 0, 1000, 500);
base.Window = new MainWindow(contentRect, (NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Miniaturizable | NSWindowStyle.Resizable), NSBackingStore.Buffered, false);
使用 CGRect
定义屏幕的窗口的位置。 与窗口的坐标系类似,屏幕将 (0,0) 定义为左下方的最低点。 接下来,我们使用 Or 运算符定义窗口的样式,以结合两个或多个 NSWindowStyle
功能:
... (NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Miniaturizable | NSWindowStyle.Resizable) ...
可以使用以下 NSWindowStyle
功能:
- 无边框 - 窗口将不会有边框。
- 有标题 - 窗口将会有标题栏。
- 可关闭 - 窗口有一个最小化按钮,可以缩小到最小化。
- 可最小化 - 窗口有一个最小化按钮,可以缩小到最小化。
- 可重设大小 - 窗口将会有一个重设大小按钮,可以重设大小。
- 实用工具 - 窗口是实用工具样式窗口(面板)。
- DocModal - 如果窗口是面板,将会是文档模式而不是系统模式。
- NonactivatingPanel - 如果窗口是面板,则不会将其设置为主窗口。
- TexturedBackground - 窗口将会具有纹理背景。
- 未缩放 - 窗口不会缩放。
- UnifiedTitleAndToolbar - 将联接窗口的标题和工具栏区域。
- Hud - 窗口将显示为抬头显示面板。
- FullScreenWindow - 窗口可以进入全屏模式。
- FullSizeContentView - 窗口的内容视图位于标题和工具栏区域后面。
最后两个属性定义窗口的缓冲类型,以及是否会延迟绘制窗口。 有关 NSWindows
的详细信息,请参阅 Apple 的窗口简介文档。
最后,由于窗口并不是从 .storyboard 或 .xib 文件放大的,因此我们需要通过调用窗口 AwakeFromNib
方法,在 MainWindowController.cs 中进行模拟。
Window.AwakeFromNib ();
这样就可以像从 .storyboard 或 .xib 文件加载的标准窗口一样针对窗口编写代码。
显示窗口
在删除了 .storyboard 或 .xib 文件,并修改了 MainWindow.cs 和 MainWindowController.cs 文件之后,你将会像在 Xcode 的 Interface Builder 中使用 .xib 文件创建的任何普通窗口一样使用该窗口。
下面会创建窗口的新实例及其控制器,并在屏幕上显示该窗口:
private MainWindowController mainWindowController;
...
mainWindowController = new MainWindowController ();
mainWindowController.Window.MakeKeyAndOrderFront (this);
此时,如果应用程序正在运行,并且单击了几次按钮,刚会显示以下内容:
添加仅代码窗口
如果希望将仅包含代码的 xibless 窗口添加到现有的 Xamarin.Mac 应用程序,请在 Solution Pad 中右键单击项目,然后依次选择“添加”>“新建文件…”。在“新建文件”对话框中,选择“Xamarin.Mac”>“带有控制器的 Cocoa 窗口”,如下所示:
像之前一样,我们将会从项目中删除默认的 .storyboard 或 .xib 文件(在本例中为 SecondWindow.xib),并按照上面切换窗口以使用代码部分中的步骤进行操作,覆盖要编写代码的窗口的定义。
使用代码将 UI 元素添加到窗口
无论是使用代码创建窗口,还是从 .storyboard 或 .xib 文件加载窗口,有时都有可能希望从代码将 UI 元素添加到窗口。 例如:
var ClickMeButton = new NSButton (new CGRect (10, 10, 100, 30)){
AutoresizingMask = NSViewResizingMask.MinYMargin
};
MyWindow.ContentView.AddSubview (ClickMeButton);
以上这段代码会创建一个新的 NSButton
,并将其添加到 MyWindow
窗口实例进行显示。 可以在 Xcode 的 Interface Builder 中以 .storyboard 或 .xib 文件形式进行定义的任何 UI 元素,基本上都可以使用在代码中创建,并显示在窗口中。
在代码中定义菜单栏
由于 Xamarin.Mac 中当前存在的限制,不建议使用 NSMenuBar
代码创建 Xamarin.Mac 应用程序的菜单,而是建议继续使用 Main.storyboard 或 MainMenu.xib 文件来定义它。 也就是说,可以在 C# 代码中添加和删除菜单和菜单项。
例如,编辑 AppDelegate.cs 文件,使 DidFinishLaunching
方法如下所示:
public override void DidFinishLaunching (NSNotification notification)
{
mainWindowController = new MainWindowController ();
mainWindowController.Window.MakeKeyAndOrderFront (this);
// Create a Status Bar Menu
NSStatusBar statusBar = NSStatusBar.SystemStatusBar;
var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable);
item.Title = "Phrases";
item.HighlightMode = true;
item.Menu = new NSMenu ("Phrases");
var address = new NSMenuItem ("Address");
address.Activated += (sender, e) => {
Console.WriteLine("Address Selected");
};
item.Menu.AddItem (address);
var date = new NSMenuItem ("Date");
date.Activated += (sender, e) => {
Console.WriteLine("Date Selected");
};
item.Menu.AddItem (date);
var greeting = new NSMenuItem ("Greeting");
greeting.Activated += (sender, e) => {
Console.WriteLine("Greetings Selected");
};
item.Menu.AddItem (greeting);
var signature = new NSMenuItem ("Signature");
signature.Activated += (sender, e) => {
Console.WriteLine("Signature Selected");
};
item.Menu.AddItem (signature);
}
上面从代码创建状态栏菜单,并在应用程序启动时显示它。 有关使用菜单的详细信息,请参阅我们的菜单文档。
总结
本文详细介绍了如何在 C# 代码中创建 Xamarin.Mac 应用程序的用户界面,而不是将 Xcode 的 Interface Builder 与 .storyboard 或 .xib 文件配合使用。