Xamarin.Mac 中的窗口

本文介绍如何在 Xamarin.Mac 应用程序中使用窗口和窗格。 其中描述了如何在 Xcode 和 Interface Builder 中创建窗口和面板、从情节提要和 .xib 文件加载它们,以及以编程方式使用这些窗口和面板。

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

根据用途,Xamarin.Mac 应用程序可以在屏幕上显示一个或多个窗口来管理并协调显示和处理的信息。 窗口的主要功能包括:

  1. 提供可在其中放置和管理视图和控件的区域。
  2. 接受和响应事件,以响应用户与键盘和鼠标的交互。

窗口可以处于无模式状态(例如可以同时打开多个文档的文本编辑器)或模态状态(例如必须先关闭导出对话框应用程序才能继续)。

面板是一种特殊的 Window(基 NSWindow 类的子类),通常在应用程序中提供辅助功能,例如文本格式检查器和系统颜色选取器等实用工具窗口。

在 Xcode 中编辑窗口

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

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

窗口简介

如上所述,窗口提供了一个区域,用于放置和管理视图和控件,并响应用户交互(通过键盘或鼠标)产生的事件。

根据 Apple 的说法,macOS 应用中有五种主要类型的窗口:

  • 文档窗口 - 文档窗口包含基于文件的用户数据,例如电子表格或文本文档。
  • 应用窗口 - 应用窗口是非基于文档的应用程序的主窗口(如 Mac 上的日历应用)。
  • 面板 - 面板浮动在其他窗口上方,提供用户在打开文档时可以使用的工具或控件。 在某些情况下,面板可以是半透明的(例如处理大型图形时)。
  • 对话框 - 对话框在响应用户操作时显示,通常提供用户完成操作的方式。 对话框需要用户做出响应,然后才能将其关闭。 (请参阅使用对话框
  • 警报 - 警报是一种特殊类型的对话框,在发生严重问题(例如错误)或警告(例如准备删除文件)时出现。 由于警报是一个对话框,因此也需要用户响应才能将其关闭。 (请参阅使用警报

有关详细信息,请参阅 Apple macOS 设计主题关于窗口部分。

主窗口、键窗口和非活动窗口

在 Xamarin.Mac 应用程序中,窗口的外观和行为会根据用户当前的交互方式而有所不同。 当前关注用户关注的最重要的文档或应用窗口称为主窗口。 在大多数情况下,此窗口也是键窗口(当前接受用户输入的窗口)。 但情况并非总是如此,例如,颜色选择器可能处于打开状态,并且是用户与之交互以更改文档窗口(它仍然是主窗口)中项的状态的键窗口。

主窗口和键窗口(如果它们是独立的)始终处于活动状态,非活动窗口则是未出现在前台的已打开窗口。 例如,文本编辑器应用程序一次可以打开多个文档,只有主窗口处于活动状态,所有其他窗口都处于非活动状态。

有关详细信息,请参阅 Apple macOS 设计主题关于窗口部分。

命名窗口

窗口可以显示标题栏,当标题显示时,它通常是应用程序的名称、正在处理的文档的名称或窗口的功能(如检查器)。 有些应用程序不显示标题栏,因为可以通过视觉识别它们,并且它们不与文档一起工作。

Apple 建议以下原则:

  • 将应用程序名称用于主要非文档窗口的标题。
  • 将新文档窗口命名为 untitled。 对于第一个新文档,不要在标题上追加数字(如 untitled 1)。 如果用户在保存第一个文档之前创建了另一个新文档,请调用该窗口 untitled 2untitled 3 等。

有关详细信息,请参阅 Apple macOS 设计主题命名窗口部分。

全屏窗口

在 macOS 中,应用程序的窗口可以全屏显示,隐藏所有内容,包括应用程序菜单栏(将光标移至屏幕顶部即可显示),从而提供无干扰的内容交互。

Apple 建议以下原则:

  • 确定窗口是否适合全屏显示。 提供简短交互的应用程序(如计算器)不应提供全屏模式。
  • 如果全屏任务需要工具栏,则显示工具栏。 通常,在全屏模式下隐藏工具栏。
  • 全屏窗口应具有用户完成任务所需的所有功能。
  • 尽可能避免在用户处于全屏窗口中时进行 Finder 交互。
  • 利用增加的屏幕空间,而不将焦点从主任务移开。

有关详细信息,请参阅 Apple macOS 设计主题全屏窗口部分。

面板

面板是一个辅助窗口,其中包含影响活动文档或选择的控件和选项(如系统颜色选取器):

颜色面板

面板可以是特定于应用全系统的。 应用程序特定的面板浮动在应用程序的文档窗口顶部,当应用程序进入后台时消失。 系统范围的面板(如字体 面板),无论应用程序如何,都浮在所有打开的窗口之上。

Apple 建议以下原则:

  • 一般情况下,使用标准面板时,应谨慎使用透明面板,并且仅用于图形密集型任务。
  • 请考虑使用面板让用户轻松访问直接影响其任务的重要控件或信息。
  • 根据需要隐藏和显示面板。
  • 面板应始终包含标题栏。
  • 面板不应包含活动的最小化按钮。

检查器

大多数新式 macOS 应用程序将影响活动文档或选择的辅助控件和选项作为主窗口的检查器(如下面所示的页面应用),而不是使用面板窗口:

示例检查器

有关更多信息,请参阅 Apple macOS 设计主题的“面板”部分。

在 Xcode 中创建和维护窗口

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

选择主情节提要

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

在 Xcode 中编辑 UI

属性检查器中,可以使用这些属性来定义和控制窗口:

  • 标题 - 这是将在窗口标题栏中显示的文本。
  • 自动保存 - 这是自动保存窗口位置和设置时用于 ID 窗口的
  • 标题栏 - 窗口是否显示标题栏。
  • 统一标题和工具栏 - 如果窗口包含工具栏,则应是标题栏的一部分。
  • 全屏内容视图 - 允许窗口的内容区域位于标题栏下方。
  • 阴影 - 窗口是否具有阴影。
  • 纹理化 - 纹理化窗口可以使用特效(如振动)并可以通过拖动其主体的任何位置来移动。
  • 关闭 - 窗口是否具有关闭按钮。
  • 最小化 - 窗口是否具有最小化按钮。
  • 调整大小 - 窗口是否具有调整大小控件。
  • 工具栏按钮 - 窗口是否具有隐藏/显示工具栏按钮。
  • 可还原 - 窗口的位置和设置是否自动保存和还原。
  • 启动时可见 - 加载 .xib 文件时自动显示窗口。
  • 停用时隐藏 - 应用程序进入后台时是否隐藏窗口。
  • 关闭时释放 - 关闭时窗口是否从内存中清除。
  • 始终显示工具提示 - 工具提示是否持续显示。
  • 重新计算视图循环 - 在绘制窗口之前重新计算视图顺序。
  • 空格公开循环 - 全部用于定义窗口在这些 macOS 环境中的行为方式。
  • 全屏 - 确定此窗口是否可以进入全屏模式。
  • 动画 - 控制可用于窗口的动画类型。
  • 外观 - 控制窗口的外观。 目前只有一种外观,即 Aqua。

有关更多详细信息,请参阅 Apple 的窗口简介NSWindow 文档。

设置默认大小和位置

若要设置窗口的初始位置并控制其大小,请切换到大小检查器

设置默认大小和位置

从这里可以设置窗口的初始大小,为其指定最小和最大大小,设置屏幕上的初始位置,并控制窗口周围的边框。

设置自定义主窗口控制器

为了能够创建输出口和操作以向 C# 代码公开 UI 元素,Xamarin.Mac 应用需要使用自定义窗口控制器。

请执行以下操作:

  1. 在 Xcode 的 Interface Builder 中打开应用的情节提要。

  2. 在 Design Surface 中选择 NSWindowController

  3. 切换到“标识检查器”视图,并输入 WindowController 作为“类名称”

    设置类名

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

  5. WindowController.cs 文件将添加到 Visual Studio for Mac 的解决方案资源管理器中的项目:

    选择 Windows 控制器

  6. 在 Xcode 的 Interface Builder 中重新打开情节提要。

  7. WindowController.h 文件可供使用:

    编辑 WindowController.h 文件

添加 UI 元素

若要定义窗口的内容,请将控件从库检查器拖到接口编辑器。 有关使用 Interface Builder 创建和启用控件的详细信息,请参阅 Xcode 和 Interface Builder 简介文档。

例如,让我们将工具栏从库检查器拖到接口编辑器的窗口中:

从库中选择工具栏

接下来,拖动文本视图并调整其大小以填充工具栏下的区域:

添加文本视图

由于我们希望文本视图随着窗口大小的变化而缩小和扩展,因此让我们切换到约束编辑器并添加以下约束:

编辑约束

单击编辑器顶部的四个红色 I-Beams 并单击添加 4 个约束,我们告诉文本视图要固定在给定的 X、Y 坐标上,并随着窗口的大小调整而水平或垂直地扩展或缩小。

最后,使用出口向代码公开文本视图(确保选择 ViewController.h 文件):

配置出口

保存更改并切换回 Visual Studio for Mac,以便与 Xcode 同步。

有关使用出口操作的详细信息,请参阅我们的出口和操作文档。

标准窗口工作流

对于在 Xamarin.Mac 应用程序中创建和使用的任何窗口,此过程基本上与上述操作相同:

  1. 对于非默认自动添加到项目的新窗口,请向项目添加新窗口定义。 下面将详细介绍这一点。
  2. 双击 Main.storyboard 文件打开窗口设计,以便在 Xcode 的 Interface Builder 中编辑。
  3. 将新窗口拖到用户界面的设计中,并使用 Segues 将窗口连接到主窗口(有关详细信息,请参阅使用情节提要文档的 Segues 部分)。
  4. “属性检查器”“大小检查器”中设置任何必需的窗口属性。
  5. 拖动生成界面所需的控件,并在“属性检查器”中对其进行配置。
  6. 使用“大小检查器”处理 UI 元素的大小调整。
  7. 通过出口操作向 C# 代码公开窗口的 UI 元素。
  8. 保存更改并切换回 Visual Studio for Mac,以便与 Xcode 同步。

创建基本窗口后,我们来看看使用窗口时 Xamarin.Mac 应用程序执行的典型过程。

显示默认窗口

默认情况下,新的 Xamarin.Mac 应用程序将在启动时自动显示 MainWindow.xib 文件中定义的窗口:

正在运行的示例窗口

由于我们修改了上述窗口的设计,因此它现在包括默认工具栏和文本视图控件。 Info.plist 文件中的以下部分负责显示此窗口:

编辑 Info.plist

主界面下拉列表用于选择将用作主应用 UI 的情节提要(在本例中为 Main.storyboard)。

视图控制器会自动添加到项目中,以控制显示的主窗口(及其主视图)。 它在 ViewController.cs 文件中定义,并附加到标识检查器下的 Interface Builder 中的文件所有者

设置文件的所有者

对于窗口,我们希望它在首次打开时具有 untitled 标题,因此让我们在 ViewController.cs 中重写 ViewWillAppear 方法,如下所示:

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

    // Set Window Title
    this.View.Window.Title = "untitled";
}

注意

窗口的 Title 属性在 ViewWillAppear 方法(而不是 ViewDidLoad 方法)中设置,因为视图可能加载到内存中,但尚未完全实例化。 在 ViewDidLoad 方法中访问 Title 属性时,我们会收到 null 异常,因为该窗口尚未构造并连接到该属性。

以编程方式关闭窗口

你可能希望以编程方式关闭 Xamarin.Mac 应用程序中的窗口,而不是让用户单击窗口的关闭按钮或使用菜单项来关闭。 macOS 提供了两种不同的方式以编程方式关闭 NSWindowPerformCloseClose

PerformClose

调用 NSWindowPerformClose 方法可模拟用户单击窗口的关闭按钮,方法是暂时高亮显示该按钮,然后关闭窗口。

如果应用程序实现 NSWindowWillClose 事件,则会在关闭窗口之前引发该事件。 如果事件返回 false,则不会关闭窗口。 如果窗口没有关闭按钮或因任何原因无法关闭,OS 将发出警报音。

例如:

MyWindow.PerformClose(this);

将尝试关闭MyWindowNSWindow实例。 如果尝试成功,窗口将关闭,否则将发出警报音,并且保持打开状态。

Close

调用 NSWindowClose 方法并不会模拟用户点击窗口的关闭按钮以暂时高亮显示该按钮,它只是关闭窗口。

窗口不必可见才能关闭,NSWindowWillCloseNotification 通知将发布到要关闭的窗口的默认通知中心。

Close 方法与 PerformClose 方法有两个重要区别:

  1. 它不会尝试引发 WillClose 事件。
  2. 它并不会通过暂时高亮显示按钮来模拟用户点击关闭按钮。

例如:

MyWindow.Close();

将关闭MyWindowNSWindow实例。

修改后的窗口内容

在 macOS 中,Apple 提供了一种方法来通知用户窗口 (NSWindow) 的内容已被用户修改,需要保存。 如果窗口包含修改的内容,则会在窗口关闭小组件中显示一个小黑点:

具有修改标记的窗口

如果用户尝试关闭窗口或退出 Mac 应用,而窗口内容有未保存的更改,则应显示一个对话框模式工作表,并允许用户首先保存其更改:

关闭窗口时显示的保存表

将窗口标记为已修改

若要将窗口标记为已修改内容,请使用以下代码:

// Mark Window content as modified
Window.DocumentEdited = true;

保存更改后,使用以下命令清除修改后的标志:

// Mark Window content as not modified
Window.DocumentEdited = false;

在关闭窗口之前保存更改

若要监视用户关闭窗口并允许他们事先保存修改的内容,需要创建 NSWindowDelegate 的子类并重写其 WindowShouldClose 方法。 例如:

using System;
using AppKit;
using System.IO;
using Foundation;

namespace SourceWriter
{
    public class EditorWindowDelegate : NSWindowDelegate
    {
        #region Computed Properties
        public NSWindow Window { get; set;}
        #endregion

        #region constructors
        public EditorWindowDelegate (NSWindow window)
        {
            // Initialize
            this.Window = window;

        }
        #endregion

        #region Override Methods
        public override bool WindowShouldClose (Foundation.NSObject sender)
        {
            // is the window dirty?
            if (Window.DocumentEdited) {
                var alert = new NSAlert () {
                    AlertStyle = NSAlertStyle.Critical,
                    InformativeText = "Save changes to document before closing window?",
                    MessageText = "Save Document",
                };
                alert.AddButton ("Save");
                alert.AddButton ("Lose Changes");
                alert.AddButton ("Cancel");
                var result = alert.RunSheetModal (Window);

                // Take action based on result
                switch (result) {
                case 1000:
                    // Grab controller
                    var viewController = Window.ContentViewController as ViewController;

                    // Already saved?
                    if (Window.RepresentedUrl != null) {
                        var path = Window.RepresentedUrl.Path;

                        // Save changes to file
                        File.WriteAllText (path, viewController.Text);
                        return true;
                    } else {
                        var dlg = new NSSavePanel ();
                        dlg.Title = "Save Document";
                        dlg.BeginSheet (Window, (rslt) => {
                            // File selected?
                            if (rslt == 1) {
                                var path = dlg.Url.Path;
                                File.WriteAllText (path, viewController.Text);
                                Window.DocumentEdited = false;
                                viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
                                viewController.View.Window.RepresentedUrl = dlg.Url;
                                Window.Close();
                            }
                        });
                        return true;
                    }
                    return false;
                case 1001:
                    // Lose Changes
                    return true;
                case 1002:
                    // Cancel
                    return false;
                }
            }

            return true;
        }
        #endregion
    }
}

使用以下代码将此委托的实例附加到窗口:

// Set delegate
Window.Delegate = new EditorWindowDelegate(Window);

在关闭应用之前保存更改

最后,Xamarin.Mac 应用应检查其任何窗口是否包含修改的内容,并允许用户在退出之前保存更改。 为此,请编辑 AppDelegate.cs 文件,重写 ApplicationShouldTerminate 方法,使其如下所示:

public override NSApplicationTerminateReply ApplicationShouldTerminate (NSApplication sender)
{
    // See if any window needs to be saved first
    foreach (NSWindow window in NSApplication.SharedApplication.Windows) {
        if (window.Delegate != null && !window.Delegate.WindowShouldClose (this)) {
            // Did the window terminate the close?
            return NSApplicationTerminateReply.Cancel;
        }
    }

    // Allow normal termination
    return NSApplicationTerminateReply.Now;
}

使用多个窗口

大多数基于文档的 Mac 应用程序可以同时编辑多个文档。 例如,文本编辑器可以同时打开多个文本文件进行编辑。 默认情况下,新的 Xamarin.Mac 应用程序具有一个“文件”菜单,其中包含“新建”项自动连接到newDocument:操作

下面的代码将激活此新项目,并允许用户打开主窗口的多个副本,以便一次编辑多个文档。

编辑 AppDelegate.cs 文件并添加以下计算属性:

public int UntitledWindowCount { get; set;} =1;

使用此方法可跟踪未保存的文件数,以便我们向用户提供反馈(根据上面所述的 Apple 指南)。

接下来,添加以下方法:

[Export ("newDocument:")]
void NewDocument (NSObject sender) {
    // Get new window
    var storyboard = NSStoryboard.FromName ("Main", null);
    var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

    // Display
    controller.ShowWindow(this);

    // Set the title
    controller.Window.Title = (++UntitledWindowCount == 1) ? "untitled" : string.Format ("untitled {0}", UntitledWindowCount);
}

此代码将创建一个新版本的窗口控制器,加载新窗口,使其成为主窗口和键窗口,并设置其标题。 现在,如果我们运行应用程序,并从“文件”菜单中选择“新建”,将打开并显示新的编辑器窗口:

新增了一个无标题窗口

如果我们打开“窗口”菜单,则可以看到应用程序会自动跟踪和处理打开的窗口:

窗口菜单

有关在 Xamarin.Mac 应用程序中使用菜单的详细信息,请参阅我们的使用菜单文档。

获取当前活动的窗口

在可以打开多个窗口(文档)的 Xamarin.Mac 应用程序中,有时需要获取当前最顶层的窗口(键窗口)。 以下代码将返回键窗口:

var window = NSApplication.SharedApplication.KeyWindow;

它可以在需要访问当前键窗口的任何类或方法中调用。 如果当前未打开任何窗口,它将返回 null

访问所有应用窗口

有时可能需要访问 Xamarin.Mac 应用当前打开的所有窗口。 例如,查看用户要打开的文件是否已在退出窗口中打开。

NSApplication.SharedApplication 维护 Windows 属性,其中包含应用中所有打开窗口的数组。 可以循环访问此数组以访问应用的所有当前窗口。 例如:

// Is the file already open?
for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
    var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
    if (content != null && path == content.FilePath) {
        // Bring window to front
        NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);
        return true;
    }
}

在示例代码中,我们将每个返回的窗口强制转换为应用中的自定义 ViewController 类,并针对用户要打开的文件的路径测试自定义 Path 属性的值。 如果文件已打开,我们将该窗口置于前面。

在代码中调整窗口大小

有时应用程序需要在代码中调整窗口的大小。 若要调整窗口的大小并重新定位,请调整其 Frame 属性。 在调整窗口大小时,由于 macOS 的坐标系,通常还需要调整其原点,以保持窗口在同一位置。

与左上角代表 (0,0) 的 iOS 不同,macOS 使用数学坐标系,其中屏幕左下角表示 (0,0)。 在 iOS 中,坐标会随着向右下方移动而增加。 在 macOS 中,坐标值随着向右上方移动而增加。

以下示例代码调整窗口的大小:

nfloat y = 0;

// Calculate new origin
y = Frame.Y - (768 - Frame.Height);

// Resize and position window
CGRect frame = new CGRect (Frame.X, y, 1024, 768);
SetFrame (frame, true);

重要

在代码中调整窗口大小和位置时,需要确保遵循在 Interface Builder 中设置的最小和最大大小。 这些限制不会自动生效,你可以将窗口的大小设置为超出这些限制。

监视窗口大小更改

有时可能需要监视 Xamarin.Mac 应用中窗口大小的更改。 例如,要重新绘制内容以适应新大小。

若要监视大小更改,请先确保在 Xcode 的 Interface Builder 中为窗口控制器分配了自定义类。 例如,以下 MasterWindowController

标识检查器

接下来,编辑自定义窗口控制器类并监视控制器窗口上的 DidResize 事件,以通知实时大小更改。 例如:

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

    Window.DidResize += (sender, e) => {
        // Do something as the window is being live resized
    };
}

(可选)可以使用 DidEndLiveResize 事件,在用户完成更改窗口大小后仅接收通知。 例如:

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

        Window.DidEndLiveResize += (sender, e) => {
        // Do something after the user's finished resizing
        // the window
    };
}

设置窗口的标题和表示的文件

当处理代表文档的窗口时,NSWindow 有一个 DocumentEdited 属性,如果将其设置为 true,将在关闭按钮中显示一个小点,以向用户指示该文件已被修改,应在关闭前保存。

让我们编辑 ViewController.cs 文件并进行以下更改:

public bool DocumentEdited {
    get { return View.Window.DocumentEdited; }
    set { View.Window.DocumentEdited = value; }
}
...

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

    // Set Window Title
    this.View.Window.Title = "untitled";

    View.Window.WillClose += (sender, e) => {
        // is the window dirty?
        if (DocumentEdited) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to give the user the ability to save the document here...",
                MessageText = "Save Document",
            };
            alert.RunModal ();
        }
    };
}

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

    // Show when the document is edited
    DocumentEditor.TextDidChange += (sender, e) => {
        // Mark the document as dirty
        DocumentEdited = true;
    };

    // Overriding this delegate is required to monitor the TextDidChange event
    DocumentEditor.ShouldChangeTextInRanges += (NSTextView view, NSValue[] values, string[] replacements) => {
        return true;
    };

}

我们还会监视窗口上的 WillClose 事件,并检查 DocumentEdited 属性的状态。 如果是 true,我们需要让用户能够保存对文件的更改。 如果我们运行应用并输入一些文本,将显示点:

已更改窗口

如果尝试关闭窗口,将收到警报:

显示保存对话框

如果要从文件加载文档,请使用 window.SetTitleWithRepresentedFilename (Path.GetFileName(path)); 方法将窗口的标题设置为文件名(前提是 path 是表示要打开的文件的字符串)。 此外,还可以使用 window.RepresentedUrl = url; 方法设置文件的 URL。

如果 URL 指向 OS 已知的文件类型,则其图标将显示在标题栏中。 如果用户右键单击图标,将显示文件的路径。

编辑 AppDelegate.cs 文件并添加以下方法:

[Export ("openDocument:")]
void OpenDialog (NSObject sender)
{
    var dlg = NSOpenPanel.OpenPanel;
    dlg.CanChooseFiles = true;
    dlg.CanChooseDirectories = false;

    if (dlg.RunModal () == 1) {
        // Nab the first file
        var url = dlg.Urls [0];

        if (url != null) {
            var path = url.Path;

            // Get new window
            var storyboard = NSStoryboard.FromName ("Main", null);
            var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

            // Display
            controller.ShowWindow(this);

            // Load the text into the window
            var viewController = controller.Window.ContentViewController as ViewController;
            viewController.Text = File.ReadAllText(path);
                    viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
            viewController.View.Window.RepresentedUrl = url;

        }
    }
}

现在,如果我们运行应用,请从“文件”菜单中选择“打开...”,从 “打开”对话框中选择一个文本文件,然后打开它:

打开的对话框

将显示该文件,标题将使用文件的图标进行设置:

加载的文件的内容

向项目添加新窗口

除主文档窗口外,Xamarin.Mac 应用程序可能需要向用户显示其他类型的窗口,例如“首选项”或“检查器面板”。

要添加新窗口,请执行以下操作:

  1. “解决方案资源管理器”中,双击 Main.storyboard 文件将其打开,以便在 Xcode 的 Interface Builder 中进行编辑。

  2. 拖动新的窗口控制器,并将其放到 Design Surface 上:

    在库中选择新的窗口控制器

  3. “标识检查器”中,为“情节提要 ID” 输入 PreferencesWindow

    设置情节提要 ID

  4. 设计界面:

    设计 UI

  5. 打开应用菜单 (MacWindows),选择“首选项...”,按住 Control 单击并拖动到新窗口:

    创建 segue

  6. 从弹出菜单中选择“显示”

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

如果我们运行代码并从应用程序菜单中选择“首选项...”,即可显示窗口:

示例首选项菜单

使用面板

如本文开头所述,面板浮动在其他窗口上方,提供用户在打开文档时可以使用的工具或控件。

与在 Xamarin.Mac 应用程序中创建和使用的任何其他类型的窗口一样,过程基本相同:

  1. 向项目添加新窗口定义。
  2. 双击 .xib 文件打开窗口设计,以便在 Xcode 的 Interface Builder 中编辑。
  3. “属性检查器”“大小检查器”中设置任何必需的窗口属性。
  4. 拖动生成界面所需的控件,并在“属性检查器”中对其进行配置。
  5. 使用“大小检查器”处理 UI 元素的大小调整。
  6. 通过出口操作向 C# 代码公开窗口的 UI 元素。
  7. 保存更改并切换回 Visual Studio for Mac,以便与 Xcode 同步。

属性检查器中,有以下特定于面板的选项:

属性检查器

  • 样式 - 允许你调整面板的样式:常规面板(看起来像标准窗口),实用工具面板(具有较小的标题栏),HUD 面板(半透明,标题栏是背景的一部分)。
  • 非激活 - 在面板中确定将变为键窗口。
  • 文档模式 - 如果是文档模式,面板将仅浮动在应用程序的窗口上方,否则它将浮动在所有窗口之上。

若要添加新面板,请执行以下操作:

  1. “解决方案资源管理器”中,右键单击项目并选择“添加”>“新建文件...”

  2. 在“新建文件”对话框中,选择 Xamarin.Mac >“具有控制器的 Cocoa 窗口”

    新增窗口控制器

  3. 对“名称”输入 DocumentPanel,然后单击“新建”按钮。

  4. 双击 DocumentPanel.xib 该文件将其打开,以便在 Interface Builder 中编辑:

    编辑面板

  5. 删除现有窗口,并将面板从库检查器拖动到接口编辑器

    删除现有窗口

  6. 将面板挂接到文件的所有者 - 窗口 - 出口

    拖动以连接面板

  7. 切换到标识检查器并将面板的类设置为 DocumentPanel

    设置面板的类

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

  9. 编辑 DocumentPanel.cs 文件并将类定义更改为以下内容:

    public partial class DocumentPanel : NSPanel

  10. 保存对文件所做的更改。

编辑 AppDelegate.cs 文件,使 DidFinishLaunching 方法如下所示:

public override void DidFinishLaunching (NSNotification notification)
{
        // Display panel
    var panel = new DocumentPanelController ();
    panel.Window.MakeKeyAndOrderFront (this);
}

如果运行应用程序,将显示面板:

正在运行的应用中的面板

重要

面板窗口已被 Apple 弃用,应替换为检查器接口

总结

本文详细介绍了如何使用 Xamarin.Mac 应用程序中的窗口和面板。 我们介绍了窗口和面板的不同类型和用法,如何在 Xcode 的 Interface Builder 中创建和维护窗口和面板,以及如何在 C# 代码中使用窗口和面板。