Меню в Xamarin.Mac

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

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

Меню являются неотъемлемой частью пользовательского интерфейса приложения Mac и обычно отображаются в различных частях пользовательского интерфейса:

  • Строка меню приложения — это главное меню, которое отображается в верхней части экрана для каждого приложения Mac.
  • Контекстные меню — они отображаются, когда пользователь щелкает правой кнопкой мыши или щелкает элемент в окне.
  • Строка состояния — это область в правой части строки меню приложения, которая отображается в верхней части экрана (слева от часов строки меню) и растет слева по мере добавления в него элементов.
  • Меню "Закрепление" — меню для каждого приложения в док-станции, которое отображается, когда пользователь щелкает правой кнопкой мыши значок или элемент управления, или когда пользователь слева щелкает значок и удерживает кнопку мыши вниз.
  • Всплывающие кнопки и списки раскрывающегося списка. Всплывающая кнопка отображает выбранный элемент и отображает список параметров для выбора при нажатии пользователем. Раскрывающийся список — это тип всплывающей кнопки, обычно используемой для выбора команд, относящихся к контексту текущей задачи. Оба могут отображаться в любом месте окна.

An example menu

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

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

Строка меню приложения

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

A menu bar

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

Согласно Apple и по умолчанию все приложения macOS имеют стандартный набор меню и элементов меню, которые отображаются в строке меню приложения:

  • Меню Apple — это меню предоставляет доступ к системным широким элементам, доступным пользователю в любое время независимо от того, какое приложение выполняется. Эти элементы нельзя изменить разработчиком.
  • Меню приложения — это меню отображает имя приложения полужирным шрифтом и помогает пользователю определить, какое приложение запущено в данный момент. Он содержит элементы, применяемые к приложению в целом, а не заданный документ или процесс, например выход из приложения.
  • Меню "Файл" — элементы, используемые для создания, открытия или сохранения документов, с которыми работает приложение. Если приложение не основано на документе, это меню можно переименовать или удалить.
  • Меню "Изменить" — содержит такие команды, как "Вырезать", "Копировать" и "Вставить", которые используются для редактирования или изменения элементов в пользовательском интерфейсе приложения.
  • Меню форматирования. Если приложение работает с текстом, это меню содержит команды для настройки форматирования этого текста.
  • Меню представления — содержит команды, влияющие на отображение содержимого (просмотр) в пользовательском интерфейсе приложения.
  • Меню, относящиеся к приложению. Это все меню, относящиеся к приложению (например, меню закладок для веб-браузера). Они должны отображаться между меню "Вид " и "Окно " на панели.
  • Меню окна — содержит команды для работы с окнами в приложении, а также список текущих открытых окон.
  • Меню справки. Если приложение предоставляет справку по экрану, меню справки должно быть правой частью в строке.

Дополнительные сведения о строке меню приложения и стандартных меню и элементах меню см. в руководствах по интерфейсу Apple.

Строка меню приложения по умолчанию

При создании нового проекта Xamarin.Mac вы автоматически получаете стандартную строку меню приложения по умолчанию с типичными элементами, которые обычно имеют приложение macOS (как описано в приведенном выше разделе). Строка меню по умолчанию приложения определена в файле Main.storyboard (а также остальной части пользовательского интерфейса приложения) в проекте на панели решений:

Select the main storyboard

Дважды щелкните файл Main.storyboard , чтобы открыть его для редактирования в построителе интерфейсов Xcode, и вы увидите интерфейс редактора меню:

Editing the UI in Xcode, showing the Main dot storyboard.

Здесь можно щелкнуть такие элементы, как открыть пункт меню "Открыть " в меню "Файл " и изменить его свойства в инспекторе атрибутов:

Editing a menu's attributes

Мы добавим, редактируем и удаляем меню и элементы далее в этой статье. Теперь мы просто хотим узнать, какие меню и элементы меню доступны по умолчанию, и как они были автоматически предоставлены коду с помощью набора предопределенных точек и действий (дополнительные сведения см. в нашей документации по точкам и действиям ).

Например, если щелкнуть инспектор Подключение ion для элемента меню "Открыть", он автоматически подключен к openDocument: действию:

Viewing the attached action

Если выбрать первый ответ в иерархии интерфейса и прокрутите вниз в инспекторе Подключение ion, и вы увидите определение openDocument: действия, к которому подключен элемент меню "Открыть" (а также к нескольким другим действиям по умолчанию для приложения, которые не подключены к элементам управления автоматически).

Viewing all attached actions

Почему это важно? В следующем разделе показано, как эти автоматически определенные действия работают с другими элементами пользовательского интерфейса Cocoa для автоматического включения и отключения элементов меню, а также предоставления встроенных функций для элементов.

Позже мы будем использовать эти встроенные действия для включения и отключения элементов из кода и предоставления собственных функциональных возможностей при их выборе.

Встроенные функции меню

Если вы выполняли только что созданное приложение Xamarin.Mac перед добавлением элементов пользовательского интерфейса или кода, вы заметите, что некоторые элементы автоматически подключены и включены (с полной функциональностью автоматически встроенной), например элемент выхода в меню приложения :

An enabled menu item

В то время как другие пункты меню, такие как вырезать, копировать и вставлять , не являются:

Disabled menu items

Давайте остановим приложение и дважды щелкните файл Main.storyboard на панели решения, чтобы открыть его для редактирования в конструкторе интерфейсов Xcode. Затем перетащите текстовое представление из библиотеки в контроллер представления окна в редакторе интерфейса:

Selecting a Text View from the Library

В редакторе ограничений давайте закрепим текстовое представление к краям окна и установите его, где он растет и сжимается с окном, щелкнув все четыре красных I-луча в верхней части редактора и нажав кнопку "Добавить 4 ограничения":

Editing the contraints

Сохраните изменения в конструкторе пользовательского интерфейса и переключитесь на Visual Studio для Mac, чтобы синхронизировать изменения с проектом Xamarin.Mac. Теперь запустите приложение, введите текст в текстовое представление, выберите его и откройте меню "Изменить ".

The menu items are automatically enabled/disabled

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

В чем причина? Помните встроенные предопределенные действия, которые подключены к элементам меню по умолчанию (как показано выше), большинство элементов пользовательского интерфейса Cocoa, которые являются частью macOS, встроены в привязки к определенным действиям (например copy:). Поэтому, когда они добавляются в окно, активный и выбранный, соответствующий элемент меню или элементы, присоединенные к этому действию, автоматически включены. Если пользователь выбирает этот пункт меню, функция, встроенная в элемент пользовательского интерфейса, вызывается и выполняется без вмешательства разработчика.

Включение и отключение меню и элементов

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

  • Автоматическое включение меню — элемент меню включен, если NSMenu можно найти соответствующий объект, который отвечает на действие, к которому подключен элемент. Например, приведенное выше представление текста с встроенным перехватчиком для copy: действия.
  • Настраиваемые действия и проверкаMenuItem. Для любого элемента меню, привязанного к окну или пользовательскому действию контроллера представления, можно добавить validateMenuItem: действие и вручную включить или отключить элементы меню.
  • Включение меню вручную. Вы вручную задаете Enabled свойство каждого NSMenuItem элемента для включения или отключения каждого элемента в меню по отдельности.

Чтобы выбрать систему, задайте AutoEnablesItems свойство объекта NSMenu. true выполняется автоматически (поведение по умолчанию) и false выполняется вручную.

Внимание

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

Использование validateMenuItem

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

В следующем примере Tag свойство будет использоваться для выбора типа элемента меню, который будет включен или отключен validateMenuItem: действием в зависимости от состояния выделенного текста в объекте NSTextView. Свойство Tag задано в Конструкторе интерфейсов для каждого пункта меню:

Setting the Tag property

И следующий код, добавленный в контроллер представления:

[Action("validateMenuItem:")]
public bool ValidateMenuItem (NSMenuItem item) {

    // Take action based on the menu item type
    // (As specified in its Tag)
    switch (item.Tag) {
    case 1:
        // Wrap menu items should only be available if
        // a range of text is selected
        return (TextEditor.SelectedRange.Length > 0);
    case 2:
        // Quote menu items should only be available if
        // a range is NOT selected.
        return (TextEditor.SelectedRange.Length == 0);
    }

    return true;
}

Когда этот код выполняется, и текст в NSTextViewней не выбран, два элемента меню оболочки отключены (даже если они подключены к действиям на контроллере представления):

Showing disabled items

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

Showing enabled items

Включение и реагирование на элементы меню в коде

Как мы уже видели выше, просто добавив определенные элементы пользовательского интерфейса Какао в наш дизайн пользовательского интерфейса (например, текстовое поле), некоторые элементы меню по умолчанию будут включены и работают автоматически без необходимости писать код. Далее давайте рассмотрим добавление собственного кода C# в проект Xamarin.Mac, чтобы включить элемент меню и предоставить функциональные возможности, когда пользователь выбирает его.

Например, предположим, что пользователь сможет использовать элемент Open в меню "Файл " для выбора папки. Так как мы хотим, чтобы это была функция на уровне приложения, а не ограничена окном или элементом пользовательского интерфейса, мы добавим код для обработки этого делегата приложения.

На панели решений дважды щелкните AppDelegate.CS файл, чтобы открыть его для редактирования:

Selecting the app delegate

Добавьте следующий код под методом DidFinishLaunching :

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

    if (dlg.RunModal () == 1) {
        var alert = new NSAlert () {
            AlertStyle = NSAlertStyle.Informational,
            InformativeText = "At this point we should do something with the folder that the user just selected in the Open File Dialog box...",
            MessageText = "Folder Selected"
        };
        alert.RunModal ();
    }
}

Давайте запустите приложение и откройте меню "Файл ":

The File menu

Обратите внимание, что теперь включен элемент меню "Открыть ". Если выбрать его, откроется диалоговое окно:

An open dialog

Если нажать кнопку "Открыть", отобразится наше оповещение:

An example dialog message

Здесь была [Export ("openDocument:")]ключевая строка, она говоритNSMenu, что в AppDelegateесть методvoid OpenDialog (NSObject sender), который отвечает на openDocument: действие. Если вы помните выше, элемент меню "Открыть " автоматически подключен к этому действию по умолчанию в конструкторе интерфейсов:

Viewing the attached actions

Далее рассмотрим создание собственного меню, элементов меню и действий и реагирование на них в коде.

Работа с открытым меню

По умолчанию меню "Файл" содержит элемент Open Recent , который отслеживает последние несколько файлов, открытых пользователем в приложении. Если вы создаете NSDocument приложение Xamarin.Mac, это меню будет обрабатываться автоматически. Для любого другого типа приложения Xamarin.Mac вы будете отвечать за управление и реагирование на этот пункт меню вручную.

Чтобы вручную обработать меню "Открыть последние" , сначала необходимо сообщить о том, что новый файл был открыт или сохранен, используя следующее:

// Add document to the Open Recent menu
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

Несмотря на то, что ваше приложение не используетсяNSDocuments, вы по-прежнему используете NSDocumentController меню Open Recent, отправляя NSUrl расположение файла NoteNewRecentDocumentURL в метод.SharedDocumentController

Затем необходимо переопределить OpenFile метод делегата приложения, чтобы открыть любой файл, выбранный пользователем в меню "Открыть последние ". Например:

public override bool OpenFile (NSApplication sender, string filename)
{
    // Trap all errors
    try {
        filename = filename.Replace (" ", "%20");
        var url = new NSUrl ("file://"+filename);
        return OpenFile(url);
    } catch {
        return false;
    }
}

Возвращается true , если файл может быть открыт, в противном случае false возвращается встроенное предупреждение, которое будет отображаться пользователю, которому не удалось открыть файл.

Так как имя файла и путь, возвращаемые из меню "Открыть последние ", может содержать пробел, необходимо правильно экранировать этот символ перед созданием NSUrl или ошибкой. Мы делаем это с помощью следующего кода:

filename = filename.Replace (" ", "%20");

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

var url = new NSUrl ("file://"+filename);
return OpenFile(url);

Чтобы собрать все вместе, давайте рассмотрим пример реализации в файле AppDelegate.cs :

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

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

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void DidFinishLaunching (NSNotification notification)
        {
            // Insert code here to initialize your application
        }

        public override void WillTerminate (NSNotification notification)
        {
            // Insert code here to tear down your application
        }

        public override bool OpenFile (NSApplication sender, string filename)
        {
            // Trap all errors
            try {
                filename = filename.Replace (" ", "%20");
                var url = new NSUrl ("file://"+filename);
                return OpenFile(url);
            } catch {
                return false;
            }
        }
        #endregion

        #region Private Methods
        private bool OpenFile(NSUrl url) {
            var good = false;

            // Trap all errors
            try {
                var path = url.Path;

                // 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;
                    }
                }

                // 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.SetLanguageFromPath(path);
                viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
                viewController.View.Window.RepresentedUrl = url;

                // Add document to the Open Recent menu
                NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

                // Make as successful
                good = true;
            } catch {
                // Mark as bad file on error
                good = false;
            }

            // Return results
            return good;
        }
        #endregion

        #region actions
        [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) {
                    // Open the document in a new window
                    OpenFile (url);
                }
            }
        }
        #endregion
    }
}

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

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

var path = url.Path;

// 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 . Затем мы прокрутим все открытые окна в приложении. Если файл уже открыт в одном из окон, он будет доставлен в передней части всех остальных окон, используя следующее:

NSApplication.SharedApplication.Windows[n].MakeKeyAndOrderFront(this);

Если совпадение не найдено, откроется новое окно с загруженным файлом , и файл отмечается в меню "Открыть последние ".

// 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.SetLanguageFromPath(path);
viewController.View.Window.SetTitleWithRepresentedFilename (Path.GetFileName(path));
viewController.View.Window.RepresentedUrl = url;

// Add document to the Open Recent menu
NSDocumentController.SharedDocumentController.NoteNewRecentDocumentURL(url);

Работа с пользовательскими действиями окна

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

Сначала определите настраиваемое действие на одном из контроллеров окна приложения. Например:

[Action("defineKeyword:")]
public void defineKeyword (NSObject sender) {
    // Preform some action when the menu is selected
    Console.WriteLine ("Request to define keyword");
}

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

The Attributes Inspector

+ Нажмите кнопку в нижней части инспектора атрибутов, чтобы добавить новое настраиваемое действие:

Adding a new action

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

Editing the action name

Щелкните элемент управления и перетащите элемент меню в первый ответ на сцену приложения. В всплывающем списке выберите только что созданное действие (defineKeyword: в этом примере):

Attaching an action

Сохраните изменения в раскадровке и вернитесь к Visual Studio для Mac для синхронизации изменений. Если вы запускаете приложение, элемент меню, подключенный к пользовательскому действию, будет автоматически включен или отключен (в зависимости от окна с открытым действием) и выбор элемента меню приведет к срабочему действию:

Testing the new action

Добавление, редактирование и удаление меню

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

В этом разделе мы рассмотрим удаление элементов меню, которые нам не нужны, реорганизация меню и добавление новых меню, элементов меню и действий.

Дважды щелкните файл Main.storyboard на панели решения, чтобы открыть его для редактирования:

Double-clicking the storyboard file to edit the UI in Xcode.

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

Selecting the View menu item

Нажмите клавишу DELETE или backspace, чтобы удалить меню. Далее мы не будем использовать все элементы в меню "Формат ", и мы хотим переместить элементы, которые мы будем использовать из подмены. В иерархии интерфейса выберите следующие пункты меню:

Highlighting multiple items

Перетащите элементы в родительское меню из вложенного меню, где они находятся в данный момент:

Dragging menu items to the parent menu

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

The items in the new location

Затем перетащите вложенное меню "Текст " из меню "Формат " и поместите его в главную строку меню между меню "Формат " и "Окно ":

The Text menu

Давайте вернемся к меню "Формат " и удалим пункт вложенного меню шрифта . Затем выберите меню "Формат " и переименуйте его "Шрифт":

The Font menu

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

The Library Inspector

Теперь давайте создадим наше меню:

  1. Перетащите элемент меню из инспектора библиотеки в строку меню "Текст " и "Окно ":

    Selecting a new menu item in the Library

  2. Переименуйте элемент "Фразы":

    Setting the menu name

  3. Затем перетащите меню из инспектора библиотеки:

    Selecting a menu from the Library

  4. Затем удалите меню в новом элементе меню, который мы только что создали, и измените его имя на "Фразы":

    Editing the menu name

  5. Теперь давайте переименуем три пункта меню по умолчанию "Адрес", "Дата" и "Приветствие":

    The Phrases menu

  6. Давайте добавим четвертый элемент меню, перетащив элемент меню из инспектора библиотеки и вызвав его "Подпись":

    Editing the menu item name

  7. Сохраните изменения в строке меню.

Теперь давайте создадим набор пользовательских действий, чтобы новые элементы меню предоставлялись коду C#. В Xcode давайте переключимся в представление Помощника:

Creating the required actions

Давайте сделаем следующее:

  1. Перетащите элемент управления из пункта меню "Адрес" в файл AppDelegate.h.

  2. Переключите тип Подключение ion на Action:

    Selecting the action type

  3. Введите имя "phraseAddress" и нажмите кнопку Подключение, чтобы создать новое действие:

    Configuring the action by entering a name.

  4. Повторите описанные выше действия для элементов меню "Дата", "Приветствие" и "Подпись ".

    The completed actions

  5. Сохраните изменения в строке меню.

Затем необходимо создать выход для представления текста, чтобы можно было настроить его содержимое из кода. Выберите файл ViewController.h в редакторе помощника и создайте новый выходdocumentText:

Creating an outlet

Вернитесь к Visual Studio для Mac синхронизации изменений из Xcode. Затем измените файл ViewController.cs и сделайте его следующим образом:

using System;

using AppKit;
using Foundation;

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

        #region Computed Properties
        public override NSObject RepresentedObject {
            get {
                return base.RepresentedObject;
            }
            set {
                base.RepresentedObject = value;
                // Update the view, if already loaded.
            }
        }

        public string Text {
            get { return documentText.Value; }
            set { documentText.Value = value; }
        } 
        #endregion

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

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

            // Do any additional setup after loading the view.
        }

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

            App.textEditor = this;
        }

        public override void ViewWillDisappear ()
        {
            base.ViewDidDisappear ();

            App.textEditor = null;
        }
        #endregion
    }
}

Это предоставляет текст нашего текстового представления за пределами ViewController класса и сообщает делегату приложения, когда окно получает или теряет фокус. Теперь измените файл AppDelegate.cs и сделайте его следующим образом:

using AppKit;
using Foundation;
using System;

namespace MacMenus
{
    [Register ("AppDelegate")]
    public partial class AppDelegate : NSApplicationDelegate
    {
        #region Computed Properties
        public ViewController textEditor { get; set;} = null;
        #endregion

        #region Constructors
        public AppDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void DidFinishLaunching (NSNotification notification)
        {
            // Insert code here to initialize your application
        }

        public override void WillTerminate (NSNotification notification)
        {
            // Insert code here to tear down your application
        }
        #endregion

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

            if (dlg.RunModal () == 1) {
                var alert = new NSAlert () {
                    AlertStyle = NSAlertStyle.Informational,
                    InformativeText = "At this point we should do something with the folder that the user just selected in the Open File Dialog box...",
                    MessageText = "Folder Selected"
                };
                alert.RunModal ();
            }
        }

        partial void phrasesAddress (Foundation.NSObject sender) {

            textEditor.Text += "Xamarin HQ\n394 Pacific Ave, 4th Floor\nSan Francisco CA 94111\n\n";
        }

        partial void phrasesDate (Foundation.NSObject sender) {

            textEditor.Text += DateTime.Now.ToString("D");
        }

        partial void phrasesGreeting (Foundation.NSObject sender) {

            textEditor.Text += "Dear Sirs,\n\n";
        }

        partial void phrasesSignature (Foundation.NSObject sender) {

            textEditor.Text += "Sincerely,\n\nKevin Mullins\nXamarin,Inc.\n";
        }
        #endregion
    }
}

Здесь мы сделали AppDelegate частичный класс, чтобы мы могли использовать действия и точки, определенные в конструкторе интерфейсов. Мы также предлагаем textEditor отслеживать, какое окно в настоящее время находится в фокусе.

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

partial void phrasesAddress (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Xamarin HQ\n394 Pacific Ave, 4th Floor\nSan Francisco CA 94111\n\n";
}

partial void phrasesDate (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += DateTime.Now.ToString("D");
}

partial void phrasesGreeting (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Dear Sirs,\n\n";
}

partial void phrasesSignature (Foundation.NSObject sender) {

    if (textEditor == null) return;
    textEditor.Text += "Sincerely,\n\nKevin Mullins\nXamarin,Inc.\n";
}

Теперь, если мы запускаем наше приложение, все элементы в меню фраз будут активными и добавят фразу в текстовое представление при выборе:

An example of the app running

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

Создание меню из кода

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

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

using System;
using System.Collections.Generic;
using Foundation;
using AppKit;

namespace AppKit.TextKit.Formatter
{
    public class LanguageFormatCommand : NSObject
    {
        #region Computed Properties
        public string Title { get; set; } = "";
        public string Prefix { get; set; } = "";
        public string Postfix { get; set; } = "";
        public List<LanguageFormatCommand> SubCommands { get; set; } = new List<LanguageFormatCommand>();
        #endregion

        #region Constructors
        public LanguageFormatCommand () {

        }

        public LanguageFormatCommand (string title)
        {
            // Initialize
            this.Title = title;
        }

        public LanguageFormatCommand (string title, string prefix)
        {
            // Initialize
            this.Title = title;
            this.Prefix = prefix;
        }

        public LanguageFormatCommand (string title, string prefix, string postfix)
        {
            // Initialize
            this.Title = title;
            this.Prefix = prefix;
            this.Postfix = postfix;
        }
        #endregion
    }
}

Добавление меню и элементов

При определении этого класса следующая подпрограмма анализирует коллекцию LanguageFormatCommandобъектов и рекурсивно создает новые меню и элементы меню, добавляя их в нижней части существующего меню (созданного в построителе интерфейсов), который был передан:

private void AssembleMenu(NSMenu menu, List<LanguageFormatCommand> commands) {
    NSMenuItem menuItem;

    // Add any formatting commands to the Formatting menu
    foreach (LanguageFormatCommand command in commands) {
        // Add separator or item?
        if (command.Title == "") {
            menuItem = NSMenuItem.SeparatorItem;
        } else {
            menuItem = new NSMenuItem (command.Title);

            // Submenu?
            if (command.SubCommands.Count > 0) {
                // Yes, populate submenu
                menuItem.Submenu = new NSMenu (command.Title);
                AssembleMenu (menuItem.Submenu, command.SubCommands);
            } else {
                // No, add normal menu item
                menuItem.Activated += (sender, e) => {
                    // Apply the command on the selected text
                    TextEditor.PerformFormattingCommand (command);
                };
            }
        }
        menu.AddItem (menuItem);
    }
}

Для любого LanguageFormatCommand объекта, имеющего пустое Title свойство, эта подпрограмма создает элемент меню Разделителя (тонкая серая линия) между разделами меню:

menuItem = NSMenuItem.SeparatorItem;

Если указан заголовок, создается новый элемент меню с этим заголовком:

menuItem = new NSMenuItem (command.Title);

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

menuItem.Submenu = new NSMenu (command.Title);
AssembleMenu (menuItem.Submenu, command.SubCommands);

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

menuItem.Activated += (sender, e) => {
    // Do something when the menu item is selected
    ...
};

Тестирование создания меню

При наличии всего приведенного выше кода, если была создана следующая коллекция LanguageFormatCommand объектов:

// Define formatting commands
FormattingCommands.Add(new LanguageFormatCommand("Strong","**","**"));
FormattingCommands.Add(new LanguageFormatCommand("Emphasize","_","_"));
FormattingCommands.Add(new LanguageFormatCommand("Inline Code","`","`"));
FormattingCommands.Add(new LanguageFormatCommand("Code Block","```\n","\n```"));
FormattingCommands.Add(new LanguageFormatCommand("Comment","<!--","-->"));
FormattingCommands.Add (new LanguageFormatCommand ());
FormattingCommands.Add(new LanguageFormatCommand("Unordered List","* "));
FormattingCommands.Add(new LanguageFormatCommand("Ordered List","1. "));
FormattingCommands.Add(new LanguageFormatCommand("Block Quote","> "));
FormattingCommands.Add (new LanguageFormatCommand ());

var Headings = new LanguageFormatCommand ("Headings");
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 1","# "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 2","## "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 3","### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 4","#### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 5","##### "));
Headings.SubCommands.Add(new LanguageFormatCommand("Heading 6","###### "));
FormattingCommands.Add (Headings);

FormattingCommands.Add(new LanguageFormatCommand ());
FormattingCommands.Add(new LanguageFormatCommand("Link","[","]()"));
FormattingCommands.Add(new LanguageFormatCommand("Image","![](",")"));
FormattingCommands.Add(new LanguageFormatCommand("Image Link","[![](",")](LinkImageHere)"));

И эта коллекция, переданная AssembleMenu функции (с набором меню форматирования в качестве базовой), будут созданы следующие динамические меню и элементы меню:

The new menu items in the running app

Удаление меню и элементов

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

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

public void UnpopulateFormattingMenu(NSMenu menu) {

    // Remove any additional items
    for (int n = (int)menu.Count - 1; n > 4; --n) {
        menu.RemoveItemAt (n);
    }
}

В приведенном выше коде первые четыре пункта меню создаются в построителе интерфейсов Xcode и недоступны в приложении, поэтому они не удаляются динамически.

Контекстные меню

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

Давайте отредактируем файл Main.storyboard в Xcode и добавьте окно окна окна в наш дизайн, задайте для него значение "NSPanel" в инспекторе удостоверений, добавьте новый элемент помощника в меню "Окно" и вложим его в новое окно с помощью окна Show Segue:

Setting the segue type in the Main dot storyboard file.

Давайте сделаем следующее:

  1. Перетащите метку из инспектора библиотеки в окно панели и задайте для нее значение Property:

    Editing the label's value

  2. Затем перетащите меню из инспектора библиотеки на контроллер представления в иерархии представлений и переименуйте три элемента меню по умолчанию Document, Text и Font:

    The required menu items

  3. Теперь элемент управления перетащите метку свойства в меню:

    Dragging to create a segue

  4. В диалоговом окне всплывающего окна выберите меню:

    Setting the segue type by selecting menu from Outlets in the Label context menu.

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

    Setting the segue class

  6. Вернитесь к Visual Studio для Mac синхронизации, а затем вернитесь к построителю интерфейсов.

  7. Перейдите в редактор помощника и выберите файл PanelViewController.h.

  8. Создайте действие для элемента меню "Документ"propertyDocument:

    Configuring the action named propertyDocument.

  9. Повторите создание действий для остальных элементов меню:

    Repeating actions for the remaining menu items.

  10. Наконец, создайте выход для метки свойства:propertyLabel

    Configuring the outlet

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

Измените файл PanelViewController.cs и добавьте следующий код:

partial void propertyDocument (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Document";
}

partial void propertyFont (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Font";
}

partial void propertyText (Foundation.NSObject sender) {
    propertyLabel.StringValue = "Text";
}

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

The contextual menu running

Далее рассмотрим создание меню строки состояния.

Меню строки состояния

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

Давайте отредактируем файл AppDelegate.cs и создадим DidFinishLaunching следующий метод:

public override void DidFinishLaunching (NSNotification notification)
{
    // Create a status bar menu
    NSStatusBar statusBar = NSStatusBar.SystemStatusBar;

    var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable);
    item.Title = "Text";
    item.HighlightMode = true;
    item.Menu = new NSMenu ("Text");

    var address = new NSMenuItem ("Address");
    address.Activated += (sender, e) => {
        PhraseAddress(address);
    };
    item.Menu.AddItem (address);

    var date = new NSMenuItem ("Date");
    date.Activated += (sender, e) => {
        PhraseDate(date);
    };
    item.Menu.AddItem (date);

    var greeting = new NSMenuItem ("Greeting");
    greeting.Activated += (sender, e) => {
        PhraseGreeting(greeting);
    };
    item.Menu.AddItem (greeting);

    var signature = new NSMenuItem ("Signature");
    signature.Activated += (sender, e) => {
        PhraseSignature(signature);
    };
    item.Menu.AddItem (signature);
}

NSStatusBar statusBar = NSStatusBar.SystemStatusBar; предоставляет нам доступ к строке состояния на уровне системы. var item = statusBar.CreateStatusItem (NSStatusItemLength.Variable); создает новый элемент строки состояния. Там мы создадим меню и ряд элементов меню и вложим меню в только что созданный элемент строки состояния.

При запуске приложения отобразится новый элемент строки состояния. При выборе элемента из меню текст в текстовом представлении изменится:

The status bar menu running

Далее давайте рассмотрим создание настраиваемых элементов меню док-станции.

Настраиваемые меню док-станции

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

A custom dock menu

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

  1. В Visual Studio для Mac щелкните правой кнопкой мыши проект приложения и выберите "Добавить>новый файл". В диалоговом окне создания файла выберите определение пустого интерфейса Xamarin.Mac>, используйте "DockMenu" для имени и нажмите кнопку "Создать", чтобы создать новый файл DockMenu.xib:

    Adding an empty interface definition

  2. На панели решений дважды щелкните файл DockMenu.xib, чтобы открыть его для редактирования в Xcode. Создайте меню со следующими элементами: адрес, дата, приветствие и подпись

    Laying out the UI

  3. Затем давайте подключим новые элементы меню к существующим действиям, созданным для пользовательского меню, в разделе "Добавление, редактирование и удаление меню " выше. Перейдите к инспектору Подключение ion и выберите первый ответчик в иерархии интерфейсов. Прокрутите phraseAddress: вниз и найдите действие. Перетащите строку из круга в этом действии в пункт меню "Адрес" :

    Dragging a line to the Address menu item.

  4. Повторите все остальные элементы меню, которые присоединяют их к соответствующим действиям:

    Repeating for other menu items attaching them to their corresponding actions.

  5. Затем выберите приложение в иерархии интерфейса. В инспекторе Подключение ion перетащите строку из круга в выходе dockMenu в только что созданное меню:

    Dragging the wire up the outlet

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

  7. Дважды щелкните файл Info.plist , чтобы открыть его для редактирования:

    Editing the Info.plist file

  8. Щелкните вкладку "Источник" в нижней части экрана:

    Selecting the Source view

  9. Нажмите кнопку "Добавить новую запись", нажмите зеленую кнопку "плюс", задайте имя свойства "AppleDockMenu" и значение "DockMenu" (имя нового XIB-файла без расширения):

    Adding the DockMenu item

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

An example of the dock menu running

Если выбрать один из настраиваемых элементов в меню, текст в текстовом представлении будет изменен.

Всплывающие кнопки и раскрывающийся список

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

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

  1. Измените файл Main.storyboard в Xcode и перетащите кнопку всплывающего окна из инспекторабиблиотеки в окно панели , созданное в разделе контекстных меню :

    Adding a popup button

  2. Добавьте новый пункт меню и задайте заголовки элементов во всплывающем параметров: Address, Date, Greeting и Signature

    Configuring the menu items

  3. Далее давайте подключим наши новые элементы меню к существующим действиям, созданным для нашего настраиваемого меню, в разделе "Добавление, редактирование и удаление меню " выше. Перейдите к инспектору Подключение ion и выберите первый ответчик в иерархии интерфейсов. Прокрутите phraseAddress: вниз и найдите действие. Перетащите строку из круга в этом действии в пункт меню "Адрес" :

    Dragging to wire up an action

  4. Повторите все остальные элементы меню, которые присоединяют их к соответствующим действиям:

    All required actions

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

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

An example of the popup running

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

Итоги

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