Диалоги в Xamarin.Mac

При работе с C# и .NET в приложении Xamarin.Mac у вас есть доступ к тем же диалогам и модальным Windows, в которых работает Objective-C разработчик и Xcode . Так как Xamarin.Mac интегрируется непосредственно с Xcode, вы можете использовать построитель интерфейсов Xcode для создания и поддержания модального Windows (или при необходимости создания их непосредственно в коде C#).

Диалоговое окно отображается в ответ на действие пользователя и обычно предоставляет способы выполнения действия пользователями. Для закрытия диалогового окна требуется ответ от пользователя.

Windows можно использовать в режиме без режима (например, текстовый редактор, который может одновременно открывать несколько документов) или модал (например, диалоговое окно экспорта, которое должно быть отклонено до продолжения приложения).

An open dialog box

В этой статье мы рассмотрим основы работы с диалогами и модальными окнами в приложении Xamarin.Mac. Настоятельно рекомендуется сначала ознакомиться со статьей Hello, Mac , в частности в разделах "Введение в Xcode" и "Конструктор интерфейсов" и "Торговых точек" и "Действия ", поскольку рассматриваются основные понятия и методы, которые мы будем использовать в этой статье.

Вы можете ознакомиться с классами И методами C# вObjective-Cразделе документа Xamarin.Mac Internals, а также объяснить RegisterExport команды, используемые для подключения классов C# к Objective-C объектам и элементам пользовательского интерфейса.

Общие сведения о диалогах

Диалоговое окно отображается в ответ на действие пользователя (например, сохранение файла) и позволяет пользователям выполнить это действие. Для закрытия диалогового окна требуется ответ от пользователя.

По словам Apple, существует три способа представления диалога:

  • Модальное диалоговое окно документа — диалоговое окно "Модальный документ" запрещает пользователю делать что-либо другое в заданном документе до тех пор, пока он не будет закрыт.
  • Модальное окно приложения — диалоговое окно модального приложения запрещает пользователю взаимодействовать с приложением до тех пор, пока он не будет закрыт.
  • Бессерверное диалоговое окно A позволяет пользователям изменять параметры в диалоговом окне при взаимодействии с окном документа.

Любой стандарт NSWindow можно использовать в качестве настраиваемого диалогового окна, отображая его модально:

An example modal window

Модальные листы диалоговых окон документа

Лист — это модальное диалоговое окно, которое присоединено к заданному окну документа, предотвращая взаимодействие пользователей с окном, пока они не уволят диалоговое окно. Лист присоединяется к окну, из которого она возникает, и только один лист может быть открыт для окна в любое время.

An example modal sheet

Параметры Windows

Окно параметров — это бессерверное диалоговое окно, содержащее параметры приложения, которые пользователь редко изменяет. Параметры Windows часто включают панель инструментов, которая позволяет пользователю переключаться между различными группами параметров:

An example preference window

Открыть диалоговое окно

Диалоговое окно "Открыть" предоставляет пользователям согласованный способ поиска и открытия элемента в приложении:

A open dialog box

macOS предоставляет стандартные диалоговые окна настройки печати и страницы, которые могут отображаться в приложении, чтобы пользователи могли иметь согласованные возможности печати в каждом приложении, которое они используют.

Диалоговое окно печати может отображаться как свободное плавающее диалоговое окно:

A print dialog box

Или его можно отобразить как лист:

A print sheet

Диалоговое окно установки страницы может отображаться как свободное диалоговое окно с плавающей запятой:

A page setup dialog

Или его можно отобразить как лист:

A page setup sheet

Сохранение диалогов

Диалоговое окно "Сохранить" предоставляет пользователям согласованный способ сохранения элемента в приложении. Диалоговое окно сохранения имеет два состояния: минимальное (также известное как свернутый):

A save dialog

И развернутое состояние:

An expanded save dialog

Диалоговое окно "Минимальное сохранение" также можно отобразить в виде листа:

A minimal save sheet

Как можно развернуть диалоговое окно сохранения:

An expanded save sheet

Дополнительные сведения см. в разделе "Диалоги " руководства по пользовательскому интерфейсу Apple OS X

Добавление модального окна в проект

Помимо основного окна документа, приложению Xamarin.Mac может потребоваться отобразить другие типы окон для пользователя, например настройки или панели инспекторов.

Чтобы добавить новое окно, сделайте следующее:

  1. В Обозреватель решений откройте Main.storyboard файл для редактирования в построителе интерфейсов Xcode.

  2. Перетащите новый контроллер представления в область конструктора:

    Selecting a View Controller from the Library

  3. В инспекторе удостоверений введите CustomDialogControllerимя класса:

    Setting the class name to CustomDialogController.

  4. Вернитесь к Visual Studio для Mac, разрешите ему синхронизироваться с Xcode и создать CustomDialogController.h файл.

  5. Вернитесь к Xcode и создайте интерфейс:

    Designing the UI in Xcode

  6. Создайте модальный segue из главного окна приложения к новому контроллеру представления, перетаскивая элемент управления из элемента пользовательского интерфейса, который откроет диалоговое окно в окно диалогового окна. Назначьте идентификаторModalSegue:

    A modal segue

  7. Проводка любых действий и выходов:

    Configuring an Action

  8. Сохраните изменения и вернитесь к Visual Studio для Mac для синхронизации с Xcode.

Сделайте CustomDialogController.cs файл следующим образом:

using System;
using Foundation;
using AppKit;

namespace MacDialog
{
    public partial class CustomDialogController : NSViewController
    {
        #region Private Variables
        private string _dialogTitle = "Title";
        private string _dialogDescription = "Description";
        private NSViewController _presentor;
        #endregion

        #region Computed Properties
        public string DialogTitle {
            get { return _dialogTitle; }
            set { _dialogTitle = value; }
        }

        public string DialogDescription {
            get { return _dialogDescription; }
            set { _dialogDescription = value; }
        }

        public NSViewController Presentor {
            get { return _presentor; }
            set { _presentor = value; }
        }
        #endregion

        #region Constructors
        public CustomDialogController (IntPtr handle) : base (handle)
        {
        }
        #endregion

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

            // Set initial title and description
            Title.StringValue = DialogTitle;
            Description.StringValue = DialogDescription;
        }
        #endregion

        #region Private Methods
        private void CloseDialog() {
            Presentor.DismissViewController (this);
        }
        #endregion

        #region Custom Actions
        partial void AcceptDialog (Foundation.NSObject sender) {
            RaiseDialogAccepted();
            CloseDialog();
        }

        partial void CancelDialog (Foundation.NSObject sender) {
            RaiseDialogCanceled();
            CloseDialog();
        }
        #endregion

        #region Events
        public EventHandler DialogAccepted;

        internal void RaiseDialogAccepted() {
            if (this.DialogAccepted != null)
                this.DialogAccepted (this, EventArgs.Empty);
        }

        public EventHandler DialogCanceled;

        internal void RaiseDialogCanceled() {
            if (this.DialogCanceled != null)
                this.DialogCanceled (this, EventArgs.Empty);
        }
        #endregion
    }
}

Этот код предоставляет несколько свойств, чтобы задать заголовок и описание диалогового окна, а также несколько событий для реагирования на отмену или принятие диалогового окна.

Затем измените ViewController.cs файл, переопределите PrepareForSegue метод и сделайте его следующим образом:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on the segue name
    switch (segue.Identifier) {
    case "ModalSegue":
        var dialog = segue.DestinationController as CustomDialogController;
        dialog.DialogTitle = "MacDialog";
        dialog.DialogDescription = "This is a sample dialog.";
        dialog.DialogAccepted += (s, e) => {
            Console.WriteLine ("Dialog accepted");
            DismissViewController (dialog);
        };
        dialog.Presentor = this;
        break;
    }
}

Этот код инициализирует segue, определенный в построителе интерфейсов Xcode в нашем диалоговом окне, и настраивает заголовок и описание. Он также обрабатывает выбор пользователя в диалоговом окне.

Мы можем запустить приложение и отобразить настраиваемое диалоговое окно:

An example dialog

Дополнительные сведения об использовании окон в приложении Xamarin.Mac см. в нашей документации по Работе с Windows .

Создание настраиваемого листа

Лист — это модальное диалоговое окно, которое присоединено к заданному окну документа, предотвращая взаимодействие пользователей с окном, пока они не уволят диалоговое окно. Лист присоединяется к окну, из которого она возникает, и только один лист может быть открыт для окна в любое время.

Чтобы создать пользовательский лист в Xamarin.Mac, давайте сделаем следующее:

  1. В Обозреватель решений откройте Main.storyboard файл для редактирования в построителе интерфейсов Xcode.

  2. Перетащите новый контроллер представления в область конструктора:

    Selecting a View Controller from the Library

  3. Проектирование пользовательского интерфейса:

    The UI design

  4. Создайте сег листа из главного окна на новый контроллер представления:

    Selecting the Sheet segue type

  5. В инспекторе удостоверений назовите классSheetViewController контроллера представления:

    Setting the class name to SheetViewController.

  6. Определите все необходимые точки и действия:

    Defining the required Outlets and Actions

  7. Сохраните изменения и вернитесь к Visual Studio для Mac для синхронизации.

Затем измените SheetViewController.cs файл и сделайте его следующим образом:

using System;
using Foundation;
using AppKit;

namespace MacDialog
{
    public partial class SheetViewController : NSViewController
    {
        #region Private Variables
        private string _userName = "";
        private string _password = "";
        private NSViewController _presentor;
        #endregion

        #region Computed Properties
        public string UserName {
            get { return _userName; }
            set { _userName = value; }
        }

        public string Password {
            get { return _password;}
            set { _password = value;}
        }

        public NSViewController Presentor {
            get { return _presentor; }
            set { _presentor = value; }
        }
        #endregion

        #region Constructors
        public SheetViewController (IntPtr handle) : base (handle)
        {
        }
        #endregion

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

            // Set initial values
            NameField.StringValue = UserName;
            PasswordField.StringValue = Password;

            // Wireup events
            NameField.Changed += (sender, e) => {
                UserName = NameField.StringValue;
            };
            PasswordField.Changed += (sender, e) => {
                Password = PasswordField.StringValue;
            };
        }
        #endregion

        #region Private Methods
        private void CloseSheet() {
            Presentor.DismissViewController (this);
        }
        #endregion

        #region Custom Actions
        partial void AcceptSheet (Foundation.NSObject sender) {
            RaiseSheetAccepted();
            CloseSheet();
        }

        partial void CancelSheet (Foundation.NSObject sender) {
            RaiseSheetCanceled();
            CloseSheet();
        }
        #endregion

        #region Events
        public EventHandler SheetAccepted;

        internal void RaiseSheetAccepted() {
            if (this.SheetAccepted != null)
                this.SheetAccepted (this, EventArgs.Empty);
        }

        public EventHandler SheetCanceled;

        internal void RaiseSheetCanceled() {
            if (this.SheetCanceled != null)
                this.SheetCanceled (this, EventArgs.Empty);
        }
        #endregion
    }
}

Затем измените ViewController.cs файл, измените PrepareForSegue метод и сделайте его следующим образом:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on the segue name
    switch (segue.Identifier) {
    case "ModalSegue":
        var dialog = segue.DestinationController as CustomDialogController;
        dialog.DialogTitle = "MacDialog";
        dialog.DialogDescription = "This is a sample dialog.";
        dialog.DialogAccepted += (s, e) => {
            Console.WriteLine ("Dialog accepted");
            DismissViewController (dialog);
        };
        dialog.Presentor = this;
        break;
    case "SheetSegue":
        var sheet = segue.DestinationController as SheetViewController;
        sheet.SheetAccepted += (s, e) => {
            Console.WriteLine ("User Name: {0} Password: {1}", sheet.UserName, sheet.Password);
        };
        sheet.Presentor = this;
        break;
    }
}

Если мы запускаем приложение и открываем лист, он будет присоединен к окну:

An example sheet

Создание диалогового окна параметров

Прежде чем выложить представление предпочтений в построителе интерфейсов, необходимо добавить настраиваемый тип segue для обработки переключения параметров. Добавьте новый класс в проект и вызовите его ReplaceViewSeque. Измените класс и сделайте его следующим образом:

using System;
using AppKit;
using Foundation;

namespace MacWindows
{
    [Register("ReplaceViewSeque")]
    public class ReplaceViewSeque : NSStoryboardSegue
    {
        #region Constructors
        public ReplaceViewSeque() {

        }

        public ReplaceViewSeque (string identifier, NSObject sourceController, NSObject destinationController) : base(identifier,sourceController,destinationController) {

        }

        public ReplaceViewSeque (IntPtr handle) : base(handle) {
        }

        public ReplaceViewSeque (NSObjectFlag x) : base(x) {
        }
        #endregion

        #region Override Methods
        public override void Perform ()
        {
            // Cast the source and destination controllers
            var source = SourceController as NSViewController;
            var destination = DestinationController as NSViewController;

            // Is there a source?
            if (source == null) {
                // No, get the current key window
                var window = NSApplication.SharedApplication.KeyWindow;

                // Swap the controllers
                window.ContentViewController = destination;

                // Release memory
                window.ContentViewController?.RemoveFromParentViewController ();
            } else {
                // Swap the controllers
                source.View.Window.ContentViewController = destination;

                // Release memory
                source.RemoveFromParentViewController ();
            }
        
        }
        #endregion

    }

}

С помощью созданного пользовательского segue можно добавить новое окно в построителе интерфейсов Xcode для обработки наших предпочтений.

Чтобы добавить новое окно, сделайте следующее:

  1. В Обозреватель решений откройте Main.storyboard файл для редактирования в построителе интерфейсов Xcode.

  2. Перетащите новый контроллер окна в область конструктора:

    Select a Window Controller from the Library

  3. Упорядочение окна рядом с конструктором строк меню:

    Adding the new Window

  4. Создайте копии подключенного контроллера представления, так как в представлении предпочтений будут вкладки:

    Adding the required View Controllers

  5. Перетащите новый контроллер панели инструментов из библиотеки:

    Select a Toolbar Controller from the Library

  6. И удалите его в окне в области конструктора:

    Adding a new Toolbar Controller

  7. Макет макета панели инструментов:

    Layout the toolbar

  8. Щелкните элемент управления и перетащите с каждой кнопки панели инструментов в созданные выше представления. Выберите тип пользовательского segue:

    Setting a Custom segue type.

  9. Выберите новый segue и задайте для классаReplaceViewSegueзначение :

    Setting the segue class

  10. В конструкторе панели меню в области конструктора в меню "Приложение" выберите "Параметры"..., щелкните элемент управления и перетащите его в окно "Настройки", чтобы создать сеге "Показать":

    Setting the segue type by dragging Preferences to the Preferences Window.

  11. Сохраните изменения и вернитесь к Visual Studio для Mac для синхронизации.

Если вы запустите код и выберите параметрыиз меню приложения, откроется окно:

An example preferences window displaying the word Profile.

Дополнительные сведения о работе с Windows и панели инструментов см. в нашей документации по Windows и панели инструментов.

Сохранение и загрузка параметров

В обычном приложении macOS, когда пользователь вносит изменения в любой из параметров пользователя приложения, эти изменения сохраняются автоматически. Самый простой способ справиться с этим в приложении Xamarin.Mac — создать один класс для управления всеми предпочтениями пользователя и совместно использовать его на уровне системы.

Сначала добавьте новый AppPreferences класс в проект и наследуйте от NSObjectнего. Параметры будут предназначены для использования привязки данных и кодирования ключа, что сделает процесс создания и поддержания форм предпочтений гораздо проще. Так как параметры будут состоять из небольшого количества простых типов данных, используйте встроенный для NSUserDefaults хранения и извлечения значений.

Измените AppPreferences.cs файл и сделайте его следующим образом:

using System;
using Foundation;
using AppKit;

namespace SourceWriter
{
    [Register("AppPreferences")]
    public class AppPreferences : NSObject
    {
        #region Computed Properties
        [Export("DefaultLanguage")]
        public int DefaultLanguage {
            get { 
                var value = LoadInt ("DefaultLanguage", 0);
                return value; 
            }
            set {
                WillChangeValue ("DefaultLanguage");
                SaveInt ("DefaultLanguage", value, true);
                DidChangeValue ("DefaultLanguage");
            }
        }

        [Export("SmartLinks")]
        public bool SmartLinks {
            get { return LoadBool ("SmartLinks", true); }
            set {
                WillChangeValue ("SmartLinks");
                SaveBool ("SmartLinks", value, true);
                DidChangeValue ("SmartLinks");
            }
        }

        // Define any other required user preferences in the same fashion
        ...

        [Export("EditorBackgroundColor")]
        public NSColor EditorBackgroundColor {
            get { return LoadColor("EditorBackgroundColor", NSColor.White); }
            set {
                WillChangeValue ("EditorBackgroundColor");
                SaveColor ("EditorBackgroundColor", value, true);
                DidChangeValue ("EditorBackgroundColor");
            }
        }
        #endregion

        #region Constructors
        public AppPreferences ()
        {
        }
        #endregion

        #region Public Methods
        public int LoadInt(string key, int defaultValue) {
            // Attempt to read int
            var number = NSUserDefaults.StandardUserDefaults.IntForKey(key);

            // Take action based on value
            if (number == null) {
                return defaultValue;
            } else {
                return (int)number;
            }
        }
            
        public void SaveInt(string key, int value, bool sync) {
            NSUserDefaults.StandardUserDefaults.SetInt(value, key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }

        public bool LoadBool(string key, bool defaultValue) {
            // Attempt to read int
            var value = NSUserDefaults.StandardUserDefaults.BoolForKey(key);

            // Take action based on value
            if (value == null) {
                return defaultValue;
            } else {
                return value;
            }
        }

        public void SaveBool(string key, bool value, bool sync) {
            NSUserDefaults.StandardUserDefaults.SetBool(value, key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }

        public string NSColorToHexString(NSColor color, bool withAlpha) {
            //Break color into pieces
            nfloat red=0, green=0, blue=0, alpha=0;
            color.GetRgba (out red, out green, out blue, out alpha);

            // Adjust to byte
            alpha *= 255;
            red *= 255;
            green *= 255;
            blue *= 255;

            //With the alpha value?
            if (withAlpha) {
                return String.Format ("#{0:X2}{1:X2}{2:X2}{3:X2}", (int)alpha, (int)red, (int)green, (int)blue);
            } else {
                return String.Format ("#{0:X2}{1:X2}{2:X2}", (int)red, (int)green, (int)blue);
            }
        }

        public NSColor NSColorFromHexString (string hexValue)
        {
            var colorString = hexValue.Replace ("#", "");
            float red, green, blue, alpha;

            // Convert color based on length
            switch (colorString.Length) {
            case 3 : // #RGB
                red = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(0, 1)), 16) / 255f;
                green = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(1, 1)), 16) / 255f;
                blue = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(2, 1)), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, 1.0f);
            case 6 : // #RRGGBB
                red = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
                green = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
                blue = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, 1.0f);
            case 8 : // #AARRGGBB
                alpha = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
                red = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
                green = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
                blue = Convert.ToInt32(colorString.Substring(6, 2), 16) / 255f;
                return NSColor.FromRgba(red, green, blue, alpha);
            default :
                throw new ArgumentOutOfRangeException(string.Format("Invalid color value '{0}'. It should be a hex value of the form #RBG, #RRGGBB or #AARRGGBB", hexValue));
            }
        }

        public NSColor LoadColor(string key, NSColor defaultValue) {

            // Attempt to read color
            var hex = NSUserDefaults.StandardUserDefaults.StringForKey(key);

            // Take action based on value
            if (hex == null) {
                return defaultValue;
            } else {
                return NSColorFromHexString (hex);
            }
        }

        public void SaveColor(string key, NSColor color, bool sync) {
            // Save to default
            NSUserDefaults.StandardUserDefaults.SetString(NSColorToHexString(color,true), key);
            if (sync) NSUserDefaults.StandardUserDefaults.Synchronize ();
        }
        #endregion
    }
}

Этот класс содержит несколько вспомогательных подпрограмм, таких как SaveInt, LoadInt, SaveColorи LoadColorт. д. для упрощения работы NSUserDefaults . Кроме того, так как NSUserDefaults не имеет встроенного способа обработки NSColors, NSColorToHexString методы NSColorFromHexString используются для преобразования цветов в шестнадцатеричные строки на основе веб-сайтов (#RRGGBBAA где AA альфа-прозрачность), которые можно легко хранить и извлекать.

AppDelegate.cs В файле создайте экземпляр объекта AppPreferences, который будет использоваться на уровне приложения:

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

namespace SourceWriter
{
    [Register ("AppDelegate")]
    public class AppDelegate : NSApplicationDelegate
    {
        #region Computed Properties
        public int NewWindowNumber { get; set;} = -1;

        public AppPreferences Preferences { get; set; } = new AppPreferences();
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion
        
        ...

Параметры подключения к представлениям предпочтений

Затем подключите класс предпочтения к элементам пользовательского интерфейса в окне предпочтения и представлениях, созданных выше. В построителе интерфейсов выберите контроллер представления предпочтений и перейдите в инспектор удостоверений, создайте пользовательский класс для контроллера:

The Identity Inspector

Вернитесь к Visual Studio для Mac для синхронизации изменений и откройте только что созданный класс для редактирования. Сделайте класс следующим образом:

using System;
using Foundation;
using AppKit;

namespace SourceWriter
{
    public partial class EditorPrefsController : NSViewController
    {
        #region Application Access
        public static AppDelegate App {
            get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
        }
        #endregion

        #region Computed Properties
        [Export("Preferences")]
        public AppPreferences Preferences {
            get { return App.Preferences; }
        }
        #endregion

        #region Constructors
        public EditorPrefsController (IntPtr handle) : base (handle)
        {
        }
        #endregion
    }
}

Обратите внимание, что этот класс сделал два действия здесь: во-первых, есть вспомогательное App свойство, чтобы упростить доступ к AppDelegate . Во-вторых, Preferences свойство предоставляет глобальный класс AppPreferences для привязки данных с любыми элементами управления пользовательского интерфейса, размещенными в этом представлении.

Затем дважды щелкните файл раскадровки, чтобы повторно открыть его в построителе интерфейсов (и просмотреть изменения, внесенные выше). Перетащите все элементы управления пользовательского интерфейса, необходимые для создания интерфейса параметров в представлении. Для каждого элемента управления перейдите к инспектору привязки и привязать к отдельным свойствам класса AppPreference :

The Binding Inspector

Повторите описанные выше действия для всех панелей (контроллеров представления) и необходимых свойств предпочтения.

Применение изменений предпочтений ко всем открытым окнам

Как упоминалось выше, в обычном приложении macOS, когда пользователь вносит изменения в любой из параметров пользователя приложения, эти изменения сохраняются автоматически и применяются к любым окнам, которые пользователь может открыть в приложении.

Тщательное планирование и проектирование настроек и окон приложения позволит этому процессу плавно и прозрачно работать с конечным пользователем с минимальным объемом работы по программированию.

Для любого окна, которое будет использовать параметры приложения, добавьте следующее вспомогательное свойство в контроллер представления содержимого, чтобы упростить доступ к нашему AppDelegate :

#region Application Access
public static AppDelegate App {
    get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
}
#endregion

Затем добавьте класс для настройки содержимого или поведения на основе предпочтений пользователя:

public void ConfigureEditor() {

    // General Preferences
    TextEditor.AutomaticLinkDetectionEnabled = App.Preferences.SmartLinks;
    TextEditor.AutomaticQuoteSubstitutionEnabled = App.Preferences.SmartQuotes;
    ...

}

Необходимо вызвать метод конфигурации при первом открытии окна, чтобы убедиться, что он соответствует предпочтениям пользователя:

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

    // Configure editor from user preferences
    ConfigureEditor ();
    ...
}

Затем измените AppDelegate.cs файл и добавьте следующий метод, чтобы применить любые изменения предпочтений ко всем открытым окнам:

public void UpdateWindowPreferences() {

    // Process all open windows
    for(int n=0; n<NSApplication.SharedApplication.Windows.Length; ++n) {
        var content = NSApplication.SharedApplication.Windows[n].ContentViewController as ViewController;
        if (content != null ) {
            // Reformat all text
            content.ConfigureEditor ();
        }
    }

}

Затем добавьте PreferenceWindowDelegate класс в проект и сделайте его следующим образом:

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

namespace SourceWriter
{
    public class PreferenceWindowDelegate : NSWindowDelegate
    {
        #region Application Access
        public static AppDelegate App {
            get { return (AppDelegate)NSApplication.SharedApplication.Delegate; }
        }
        #endregion

        #region Computed Properties
        public NSWindow Window { get; set;}
        #endregion

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

        }
        #endregion

        #region Override Methods
        public override bool WindowShouldClose (Foundation.NSObject sender)
        {
            
            // Apply any changes to open windows
            App.UpdateWindowPreferences();

            return true;
        }
        #endregion
    }
}

Это приведет к тому, что все изменения предпочтений будут отправляться во все открытые Окна при закрытии окна предпочтений.

Наконец, измените контроллер окна предпочтения и добавьте делегат, созданный выше:

using System;
using Foundation;
using AppKit;

namespace SourceWriter
{
    public partial class PreferenceWindowController : NSWindowController
    {
        #region Constructors
        public PreferenceWindowController (IntPtr handle) : base (handle)
        {
        }
        #endregion

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

            // Initialize
            Window.Delegate = new PreferenceWindowDelegate(Window);
            Toolbar.SelectedItemIdentifier = "General";
        }
        #endregion
    }
}

При всех этих изменениях, если пользователь изменяет настройки приложения и закрывает окно предпочтения, изменения будут применены ко всем открытым Окнам Windows:

An example Preferences Window, displayed with several other open windows.

Диалоговое окно "Открыть"

Диалоговое окно "Открыть" предоставляет пользователям согласованный способ поиска и открытия элемента в приложении. Чтобы открыть диалоговое окно в приложении Xamarin.Mac, используйте следующий код:

var dlg = NSOpenPanel.OpenPanel;
dlg.CanChooseFiles = true;
dlg.CanChooseDirectories = false;
dlg.AllowedFileTypes = new string[] { "txt", "html", "md", "css" };

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

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

        // Create a new window to hold the text
        var newWindowController = new MainWindowController ();
        newWindowController.Window.MakeKeyAndOrderFront (this);

        // Load the text into the window
        var window = newWindowController.Window as MainWindow;
        window.Text = File.ReadAllText(path);
        window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
        window.RepresentedUrl = url;

    }
}

В приведенном выше коде мы открываем новое окно документа для отображения содержимого файла. Вам потребуется заменить этот код функциональными возможностями приложения.

Следующие свойства доступны при работе с:NSOpenPanel

  • CanChooseFiles — если true пользователь может выбрать файлы.
  • CanChooseDirectory — если true пользователь может выбрать каталоги.
  • РазрешитьMultipleSelection — если true пользователь может выбрать несколько файлов одновременно.
  • ResolveAliases — если true выбран и псевдоним, разрешает его путь к исходному файлу.
  • AllowedFileTypes — это строковый массив типов файлов, которые пользователь может выбрать как расширение или UTI. Значением по умолчанию является null, что позволяет открывать любой файл.

Метод RunModal () отображает диалоговое окно "Открыть" и позволяет пользователю выбирать файлы или каталоги (как указано в свойствах) и возвращается 1 , если пользователь нажимает кнопку "Открыть ".

Диалоговое окно "Открыть" возвращает выбранные файлы или каталоги пользователя в виде массива URL-адресов в свойстве URL .

Если запустить программу и выбрать элемент Open... в меню "Файл ", отобразится следующее:

An open dialog box

Диалоговые окна настройки печати и страницы

macOS предоставляет стандартные диалоговые окна настройки печати и страницы, которые могут отображаться в приложении, чтобы пользователи могли иметь согласованные возможности печати в каждом приложении, которое они используют.

В следующем коде показан стандартный диалог печати:

public bool ShowPrintAsSheet { get; set;} = true;
...

[Export ("showPrinter:")]
void ShowDocument (NSObject sender) {
    var dlg = new NSPrintPanel();

    // Display the print dialog as dialog box
    if (ShowPrintAsSheet) {
        dlg.BeginSheet(new NSPrintInfo(),this,this,null,new IntPtr());
    } else {
        if (dlg.RunModalWithPrintInfo(new NSPrintInfo()) == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to print the document here...",
                MessageText = "Print Document",
            };
            alert.RunModal ();
        }
    }
}

Если для свойства задано ShowPrintAsSheet значение false, запустите приложение и отобразите диалоговое окно печати, отобразится следующее:

A print dialog box

Если для свойства задано ShowPrintAsSheet значение true, запустите приложение и отобразите диалоговое окно печати, отобразится следующее:

A print sheet

В следующем коде отобразится диалоговое окно макета страницы:

[Export ("showLayout:")]
void ShowLayout (NSObject sender) {
    var dlg = new NSPageLayout();

    // Display the print dialog as dialog box
    if (ShowPrintAsSheet) {
        dlg.BeginSheet (new NSPrintInfo (), this);
    } else {
        if (dlg.RunModal () == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to print the document here...",
                MessageText = "Print Document",
            };
            alert.RunModal ();
        }
    }
}

Если для свойства задано ShowPrintAsSheet значение false, запустите приложение и отобразите диалоговое окно макета печати, отобразится следующее:

A page setup dialog

Если для свойства задано ShowPrintAsSheet значение true, запустите приложение и отобразите диалоговое окно макета печати, отобразится следующее:

A page setup sheet

Дополнительные сведения о работе с диалогами настройки печати и страниц см. в документации apple NSPrintPanel и NSPageLayout .

Диалоговое окно сохранения

Диалоговое окно "Сохранить" предоставляет пользователям согласованный способ сохранения элемента в приложении.

В следующем коде отобразится стандартное диалоговое окно сохранения:

public bool ShowSaveAsSheet { get; set;} = true;
...

[Export("saveDocumentAs:")]
void ShowSaveAs (NSObject sender)
{
    var dlg = new NSSavePanel ();
    dlg.Title = "Save Text File";
    dlg.AllowedFileTypes = new string[] { "txt", "html", "md", "css" };

    if (ShowSaveAsSheet) {
        dlg.BeginSheet(mainWindowController.Window,(result) => {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to save the document here...",
                MessageText = "Save Document",
            };
            alert.RunModal ();
        });
    } else {
        if (dlg.RunModal () == 1) {
            var alert = new NSAlert () {
                AlertStyle = NSAlertStyle.Critical,
                InformativeText = "We need to save the document here...",
                MessageText = "Save Document",
            };
            alert.RunModal ();
        }
    }

}

Это AllowedFileTypes строковый массив типов файлов, которые пользователь может выбрать, чтобы сохранить файл как. Тип файла можно указать как расширение или UTI. Значение по умолчанию — nullэто значение, позволяющее использовать любой тип файла.

Если для свойства задано ShowSaveAsSheet значение false, запустите приложение и выберите "Сохранить как... " в меню "Файл ", отобразится следующее:

A save dialog box

Пользователь может развернуть диалоговое окно:

An expanded save dialog box

Если для свойства задано ShowSaveAsSheet значение true, запустите приложение и выберите "Сохранить как... " в меню "Файл ", отобразится следующее:

A save sheet

Пользователь может развернуть диалоговое окно:

An expanded save sheet

Дополнительные сведения о работе с диалоговым окном сохранения см. в документации Apple NSSavePanel .

Итоги

В этой статье подробно рассматривается работа с модальными окнами Windows, листами и стандартными системными диалоговых окнами в приложении Xamarin.Mac. Мы видели различные типы и использование модальных окон, листов и диалоговых окон, как создавать модальные окна и листы в построителе интерфейсов Xcode и как работать с модальными окнами, листами и диалогами в коде C#.