Создание пользовательских элементов управления в Xamarin.Mac

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

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

Example of a custom UI control

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

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

Общие сведения о пользовательских элементах управления

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

В таких ситуациях можно легко наследовать и NSControl создать пользовательское средство, которое можно добавить в пользовательский интерфейс приложения с помощью кода C# или с помощью конструктора интерфейсов Xcode. Наследуя от NSControl пользовательского элемента управления, автоматически будут иметь все стандартные функции, имеющие встроенный элемент управления пользовательским интерфейсом (например NSButton, ).

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

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

В этой статье описано, как создать пользовательский компонент Flip Switch, который предоставляет уникальную тему пользовательского интерфейса и пример создания полнофункциональным пользовательским элементом управления пользовательским интерфейсом.

Создание пользовательского элемента управления

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

В Visual Studio для Mac откройте проект Xamarin.Mac, для которого требуется создать пользовательский элемент управления пользовательским интерфейсом (или создать новый). Добавьте новый класс и вызовите его NSFlipSwitch:

Adding a new class

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

using Foundation;
using System;
using System.CodeDom.Compiler;
using AppKit;
using CoreGraphics;

namespace MacCustomControl
{
    [Register("NSFlipSwitch")]
    public class NSFlipSwitch : NSControl
    {
        #region Private Variables
        private bool _value = false;
        #endregion

        #region Computed Properties
        public bool Value {
            get { return _value; }
            set {
                // Save value and force a redraw
                _value = value;
                NeedsDisplay = true;
            }
        }
        #endregion

        #region Constructors
        public NSFlipSwitch ()
        {
            // Init
            Initialize();
        }

        public NSFlipSwitch (IntPtr handle) : base (handle)
        {
            // Init
            Initialize();
        }

        [Export ("initWithFrame:")]
        public NSFlipSwitch (CGRect frameRect) : base(frameRect) {
            // Init
            Initialize();
        }

        private void Initialize() {
            this.WantsLayer = true;
            this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
        }
        #endregion

        #region Draw Methods
        public override void DrawRect (CGRect dirtyRect)
        {
            base.DrawRect (dirtyRect);

            // Use Core Graphic routines to draw our UI
            ...

        }
        #endregion

        #region Private Methods
        private void FlipSwitchState() {
            // Update state
            Value = !Value;
        }
        #endregion

    }
}

Первое, что следует заметить о пользовательском классе в том, что мы наследуем и NSControl используем команду Register, чтобы предоставить этот класс Objective-C и построитель интерфейсов Xcode:

[Register("NSFlipSwitch")]
public class NSFlipSwitch : NSControl

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

Отслеживание состояния элемента управления

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

private bool _value = false;
...

public bool Value {
    get { return _value; }
    set {
        // Save value and force a redraw
        _value = value;
        NeedsDisplay = true;
    }
}

При изменении состояния коммутатора нам нужен способ обновления пользовательского интерфейса. Мы делаем это, заставляя элемент управления перерисовывать его пользовательский интерфейс.NeedsDisplay = true

Если наш элемент управления требует больше одного состояния on/Off (например, переключателя с несколькими состояниями с 3 позициями), мы могли бы использовать перечисление для отслеживания состояния. В нашем примере простой логическое значение будет выполняться.

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

private void FlipSwitchState() {
    // Update state
    Value = !Value;
}

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

Рисование интерфейса элемента управления

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

private void Initialize() {
    this.WantsLayer = true;
    this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}

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

public NSFlipSwitch (IntPtr handle) : base (handle)
{
    // Init
    Initialize();
}

Затем необходимо переопределить DrawRect метод и добавить основные графические подпрограммы для рисования элемента управления:

public override void DrawRect (CGRect dirtyRect)
{
    base.DrawRect (dirtyRect);

    // Use Core Graphic routines to draw our UI
    ...

}

Мы изменим визуальное представление элемента управления при изменении состояния элемента управления (например, переход с "Вкл. вкл.). В любой момент изменения состояния можно использовать NeedsDisplay = true команду, чтобы принудительно перераскрыть элемент управления для нового состояния.

Реагирование на входные данные пользователя

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

Внимание

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

Обработка ввода пользователей с помощью методов переопределения

Объекты, наследуемые от NSControl (или NSView) имеют несколько методов переопределения для обработки ввода мыши или клавиатуры. В нашем примере управления мы хотим переключить состояние переключателя между вкл . и выкл . Когда пользователь щелкает элемент управления с помощью левой кнопки мыши. Для обработки этого можно добавить следующие методы NSFlipSwitch переопределения в класс:

#region Mouse Handling Methods
// --------------------------------------------------------------------------------
// Handle mouse with Override Methods.
// NOTE: Use either this method or Gesture Recognizers, NOT both!
// --------------------------------------------------------------------------------
public override void MouseDown (NSEvent theEvent)
{
    base.MouseDown (theEvent);

    FlipSwitchState ();
}

public override void MouseDragged (NSEvent theEvent)
{
    base.MouseDragged (theEvent);
}

public override void MouseUp (NSEvent theEvent)
{
    base.MouseUp (theEvent);
}

public override void MouseMoved (NSEvent theEvent)
{
    base.MouseMoved (theEvent);
}
## endregion

В приведенном выше коде метод (определенный выше) вызывается FlipSwitchState для перевернутого состояния переключателя в методе MouseDown . Это также приведет к повторному выводу элемента управления для отражения текущего состояния.

Обработка ввода пользователей с помощью распознавателей жестов

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

private void Initialize() {
    this.WantsLayer = true;
    this.LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;

    // --------------------------------------------------------------------------------
    // Handle mouse with Gesture Recognizers.
    // NOTE: Use either this method or the Override Methods, NOT both!
    // --------------------------------------------------------------------------------
    var click = new NSClickGestureRecognizer (() => {
        FlipSwitchState();
    });
    AddGestureRecognizer (click);
}

Здесь мы создадим новый NSClickGestureRecognizer метод и вызываем наш FlipSwitchState метод, чтобы изменить состояние переключателя, когда пользователь щелкает его с помощью левой кнопки мыши. Метод AddGestureRecognizer (click) добавляет распознаватель жестов в элемент управления.

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

Реагирование на события изменения состояния

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

Чтобы предоставить эту функциональность, измените NSFlipSwitch класс и добавьте следующий код:

#region Events
public event EventHandler ValueChanged;

internal void RaiseValueChanged() {
    if (this.ValueChanged != null)
        this.ValueChanged (this, EventArgs.Empty);

    // Perform any action bound to the control from Interface Builder
    // via an Action.
    if (this.Action !=null)
        NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);
}
## endregion

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

private void FlipSwitchState() {
    // Update state
    Value = !Value;
    RaiseValueChanged ();
}

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

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

if (this.Action !=null)
    NSApplication.SharedApplication.SendAction (this.Action, this.Target, this);

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

Использование пользовательского элемента управления

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

Чтобы добавить элемент управления с помощью конструктора интерфейсов, сначала выполните чистую сборку проекта Xamarin.Mac, а затем дважды щелкните Main.storyboard файл, чтобы открыть его в Interface Builder для редактирования:

Editing the storyboard in Xcode

Затем перетащите фигуру Custom View в конструктор пользовательского интерфейса:

Selecting a Custom View from the Library

При выборе пользовательского представления перейдите в инспектор удостоверений и измените класс NSFlipSwitch представления на:

Setting the View's class

Перейдите в редактор помощника и создайте выход для пользовательского элемента управления (обязательно привязав его в ViewController.h файле, а не .m в файле):

Configuring a new Outlet

Сохраните изменения, вернитесь к Visual Studio для Mac и разрешите синхронизацию изменений. Измените ViewController.cs файл и сделайте ViewDidLoad метод следующим образом:

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

    // Do any additional setup after loading the view.
    OptionTwo.ValueChanged += (sender, e) => {
        // Display the state of the option switch
        Console.WriteLine("Option Two: {0}", OptionTwo.Value);
    };
}

Здесь мы реагируем на ValueChanged событие, указанное выше в NSFlipSwitch классе, и выписываем текущее значение , когда пользователь щелкает элемент управления.

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

Configuring a new Action

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

partial void OptionTwoFlipped (Foundation.NSObject sender) {
    // Display the state of the option switch
    Console.WriteLine("Option Two: {0}", OptionTwo.Value);
}

Внимание

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

Итоги

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