Listas de origem no Xamarin.Mac

Este artigo aborda o trabalho com listas de origem em um aplicativo Xamarin.Mac. Ele descreve a criação e a manutenção de listas de origem no Xcode e no Construtor de Interfaces e a interação com elas no código C#.

Ao trabalhar com C# e .NET em um aplicativo Xamarin.Mac, você tem acesso às mesmas Listas de Origem que um desenvolvedor que trabalha e Objective-C o Xcode tem. Como o Xamarin.Mac se integra diretamente ao Xcode, você pode usar o Construtor de Interfaces do Xcode para criar e manter suas Listas de Origem (ou, opcionalmente, criá-las diretamente no código C#).

Uma Lista de Origens é um tipo especial de Visualização de Estrutura de Tópicos usado para mostrar a origem de uma ação, como a barra lateral no Finder ou no iTunes.

Um exemplo de lista de fontes

Neste artigo, abordaremos as noções básicas de como trabalhar com Listas de Origem em um aplicativo Xamarin.Mac. É altamente recomendável que você trabalhe primeiro no artigo Olá, Mac , especificamente nas seções Introdução ao Xcode e ao Construtor de Interfaces e Saídas e Ações , pois ele aborda os principais conceitos e técnicas que usaremos neste artigo.

Talvez você queira dar uma olhada na seção Expondo classes/métodos C# para Objective-Cdo documento Internos do Xamarin.Mac também, ele explica os Register comandos e Export usados para conectar suas classes C# a Objective-C objetos e elementos de interface do usuário.

Introdução às listas de fontes

Como dito acima, uma Lista de Origem é um tipo especial de Visualização de Estrutura de Tópicos usado para mostrar a origem de uma ação, como a barra lateral no Finder ou iTunes. Uma Lista de Origem é um tipo de Tabela que permite ao usuário expandir ou recolher linhas de dados hierárquicos. Ao contrário de uma Exibição de Tabela, os itens em uma Lista de Origem não estão em uma lista simples, eles são organizados em uma hierarquia, como arquivos e pastas em um disco rígido. Se um item em uma Lista de Origem contiver outros itens, ele poderá ser expandido ou recolhido pelo usuário.

A Lista de Fontes é uma Visualização de Estrutura de Tópicos (NSOutlineView), que por sua vez é uma subclasse da Visualização de Tabela (NSTableView) e, como tal, herda muito de seu comportamento de sua classe pai. Como resultado, muitas operações suportadas por uma Exibição de Estrutura de Tópicos também são suportadas por uma Lista de Origem. Um aplicativo Xamarin.Mac tem controle desses recursos e pode configurar os parâmetros da Lista de Origem (no código ou no Construtor de Interfaces) para permitir ou não permitir determinadas operações.

Uma Lista de Fontes não armazena seus próprios dados, em vez disso, depende de uma Fonte de Dados (NSOutlineViewDataSource) para fornecer as linhas e colunas necessárias, conforme necessário.

O comportamento de uma Lista de Origem pode ser personalizado fornecendo uma subclasse do Delegado de Exibição de Estrutura de Tópicos (NSOutlineViewDelegate) para dar suporte ao tipo de Estrutura de Tópicos para selecionar funcionalidade, seleção e edição de itens, rastreamento personalizado e exibições personalizadas para itens individuais.

Como uma Lista de Origem compartilha muito de seu comportamento e funcionalidade com um Modo de Exibição de Tabela e um Modo de Exibição de Estrutura de Tópicos, talvez você queira examinar nossa documentação de Modos de Exibição de Tabela e Modos de Exibição de Estrutura de Tópicos antes de continuar com este artigo.

Trabalhando com listas de origem

Uma Lista de Origens é um tipo especial de Visualização de Estrutura de Tópicos usado para mostrar a origem de uma ação, como a barra lateral no Finder ou no iTunes. Ao contrário dos Modos de Exibição de Estrutura de Tópicos, antes de definirmos nossa Lista de Origem no Construtor de Interfaces, vamos criar as classes de suporte no Xamarin.Mac.

Primeiro, vamos criar uma nova SourceListItem classe para armazenar os dados de nossa Lista de Origem. No Gerenciador de Soluções, clique com o botão direito do mouse no Projeto e selecione Adicionar>Novo Arquivo... Selecione Geral>Classe Vazia, digite SourceListItem para o Nome e clique no botão Novo:

Adicionando uma classe vazia

Faça com que o SourceListItem.cs arquivo tenha a seguinte aparência:

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

namespace MacOutlines
{
    public class SourceListItem: NSObject, IEnumerator, IEnumerable
    {
        #region Private Properties
        private string _title;
        private NSImage _icon;
        private string _tag;
        private List<SourceListItem> _items = new List<SourceListItem> ();
        #endregion

        #region Computed Properties
        public string Title {
            get { return _title; }
            set { _title = value; }
        }

        public NSImage Icon {
            get { return _icon; }
            set { _icon = value; }
        }

        public string Tag {
            get { return _tag; }
            set { _tag=value; }
        }
        #endregion

        #region Indexer
        public SourceListItem this[int index]
        {
            get
            {
                return _items[index];
            }

            set
            {
                _items[index] = value;
            }
        }

        public int Count {
            get { return _items.Count; }
        }

        public bool HasChildren {
            get { return (Count > 0); }
        }
        #endregion

        #region Enumerable Routines
        private int _position = -1;

        public IEnumerator GetEnumerator()
        {
            _position = -1;
            return (IEnumerator)this;
        }

        public bool MoveNext()
        {
            _position++;
            return (_position < _items.Count);
        }

        public void Reset()
        {_position = -1;}

        public object Current
        {
            get
            {
                try
                {
                    return _items[_position];
                }

                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }
        #endregion

        #region Constructors
        public SourceListItem ()
        {
        }

        public SourceListItem (string title)
        {
            // Initialize
            this._title = title;
        }

        public SourceListItem (string title, string icon)
        {
            // Initialize
            this._title = title;
            this._icon = NSImage.ImageNamed (icon);
        }

        public SourceListItem (string title, string icon, ClickedDelegate clicked)
        {
            // Initialize
            this._title = title;
            this._icon = NSImage.ImageNamed (icon);
            this.Clicked = clicked;
        }

        public SourceListItem (string title, NSImage icon)
        {
            // Initialize
            this._title = title;
            this._icon = icon;
        }

        public SourceListItem (string title, NSImage icon, ClickedDelegate clicked)
        {
            // Initialize
            this._title = title;
            this._icon = icon;
            this.Clicked = clicked;
        }

        public SourceListItem (string title, NSImage icon, string tag)
        {
            // Initialize
            this._title = title;
            this._icon = icon;
            this._tag = tag;
        }

        public SourceListItem (string title, NSImage icon, string tag, ClickedDelegate clicked)
        {
            // Initialize
            this._title = title;
            this._icon = icon;
            this._tag = tag;
            this.Clicked = clicked;
        }
        #endregion

        #region Public Methods
        public void AddItem(SourceListItem item) {
            _items.Add (item);
        }

        public void AddItem(string title) {
            _items.Add (new SourceListItem (title));
        }

        public void AddItem(string title, string icon) {
            _items.Add (new SourceListItem (title, icon));
        }

        public void AddItem(string title, string icon, ClickedDelegate clicked) {
            _items.Add (new SourceListItem (title, icon, clicked));
        }

        public void AddItem(string title, NSImage icon) {
            _items.Add (new SourceListItem (title, icon));
        }

        public void AddItem(string title, NSImage icon, ClickedDelegate clicked) {
            _items.Add (new SourceListItem (title, icon, clicked));
        }

        public void AddItem(string title, NSImage icon, string tag) {
            _items.Add (new SourceListItem (title, icon, tag));
        }

        public void AddItem(string title, NSImage icon, string tag, ClickedDelegate clicked) {
            _items.Add (new SourceListItem (title, icon, tag, clicked));
        }

        public void Insert(int n, SourceListItem item) {
            _items.Insert (n, item);
        }

        public void RemoveItem(SourceListItem item) {
            _items.Remove (item);
        }

        public void RemoveItem(int n) {
            _items.RemoveAt (n);
        }

        public void Clear() {
            _items.Clear ();
        }
        #endregion

        #region Events
        public delegate void ClickedDelegate();
        public event ClickedDelegate Clicked;

        internal void RaiseClickedEvent() {
            // Inform caller
            if (this.Clicked != null)
                this.Clicked ();
        }
        #endregion
    }
}

No Gerenciador de Soluções, clique com o botão direito do mouse no Projeto e selecione Adicionar>Novo Arquivo... Selecione Geral>Classe Vazia, digite SourceListDataSource para o Nome e clique no botão Novo. Faça com que o SourceListDataSource.cs arquivo tenha a seguinte aparência:

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

namespace MacOutlines
{
    public class SourceListDataSource : NSOutlineViewDataSource
    {
        #region Private Variables
        private SourceListView _controller;
        #endregion

        #region Public Variables
        public List<SourceListItem> Items = new List<SourceListItem>();
        #endregion

        #region Constructors
        public SourceListDataSource (SourceListView controller)
        {
            // Initialize
            this._controller = controller;
        }
        #endregion

        #region Override Properties
        public override nint GetChildrenCount (NSOutlineView outlineView, Foundation.NSObject item)
        {
            if (item == null) {
                return Items.Count;
            } else {
                return ((SourceListItem)item).Count;
            }
        }

        public override bool ItemExpandable (NSOutlineView outlineView, Foundation.NSObject item)
        {
            return ((SourceListItem)item).HasChildren;
        }

        public override NSObject GetChild (NSOutlineView outlineView, nint childIndex, Foundation.NSObject item)
        {
            if (item == null) {
                return Items [(int)childIndex];
            } else {
                return ((SourceListItem)item) [(int)childIndex];
            }
        }

        public override NSObject GetObjectValue (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
        {
            return new NSString (((SourceListItem)item).Title);
        }
        #endregion

        #region Internal Methods
        internal SourceListItem ItemForRow(int row) {
            int index = 0;

            // Look at each group
            foreach (SourceListItem item in Items) {
                // Is the row inside this group?
                if (row >= index && row <= (index + item.Count)) {
                    return item [row - index - 1];
                }

                // Move index
                index += item.Count + 1;
            }

            // Not found
            return null;
        }
        #endregion
    }
}

Isso fornecerá os dados para nossa Lista de Fontes.

No Gerenciador de Soluções, clique com o botão direito do mouse no Projeto e selecione Adicionar>Novo Arquivo... Selecione Geral>Classe Vazia, digite SourceListDelegate para o Nome e clique no botão Novo. Faça com que o SourceListDelegate.cs arquivo tenha a seguinte aparência:

using System;
using AppKit;
using Foundation;

namespace MacOutlines
{
    public class SourceListDelegate : NSOutlineViewDelegate
    {
        #region Private variables
        private SourceListView _controller;
        #endregion

        #region Constructors
        public SourceListDelegate (SourceListView controller)
        {
            // Initialize
            this._controller = controller;
        }
        #endregion

        #region Override Methods
        public override bool ShouldEditTableColumn (NSOutlineView outlineView, NSTableColumn tableColumn, Foundation.NSObject item)
        {
            return false;
        }

        public override NSCell GetCell (NSOutlineView outlineView, NSTableColumn tableColumn, Foundation.NSObject item)
        {
            nint row = outlineView.RowForItem (item);
            return tableColumn.DataCellForRow (row);
        }

        public override bool IsGroupItem (NSOutlineView outlineView, Foundation.NSObject item)
        {
            return ((SourceListItem)item).HasChildren;
        }

        public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
        {
            NSTableCellView view = null;

            // Is this a group item?
            if (((SourceListItem)item).HasChildren) {
                view = (NSTableCellView)outlineView.MakeView ("HeaderCell", this);
            } else {
                view = (NSTableCellView)outlineView.MakeView ("DataCell", this);
                view.ImageView.Image = ((SourceListItem)item).Icon;
            }

            // Initialize view
            view.TextField.StringValue = ((SourceListItem)item).Title;

            // Return new view
            return view;
        }

        public override bool ShouldSelectItem (NSOutlineView outlineView, Foundation.NSObject item)
        {
            return (outlineView.GetParent (item) != null);
        }

        public override void SelectionDidChange (NSNotification notification)
        {
            NSIndexSet selectedIndexes = _controller.SelectedRows;

            // More than one item selected?
            if (selectedIndexes.Count > 1) {
                // Not handling this case
            } else {
                // Grab the item
                var item = _controller.Data.ItemForRow ((int)selectedIndexes.FirstIndex);

                // Was an item found?
                if (item != null) {
                    // Fire the clicked event for the item
                    item.RaiseClickedEvent ();

                    // Inform caller of selection
                    _controller.RaiseItemSelected (item);
                }
            }
        }
        #endregion
    }
}

Isso fornecerá o comportamento de nossa Lista de Fontes.

Por fim, no Gerenciador de Soluções, clique com o botão direito do mouse no Projeto e selecione Adicionar>Novo Arquivo... Selecione Geral>Classe Vazia, digite SourceListView para o Nome e clique no botão Novo. Faça com que o SourceListView.cs arquivo tenha a seguinte aparência:

using System;
using AppKit;
using Foundation;

namespace MacOutlines
{
    [Register("SourceListView")]
    public class SourceListView : NSOutlineView
    {
        #region Computed Properties
        public SourceListDataSource Data {
            get {return (SourceListDataSource)this.DataSource; }
        }
        #endregion

        #region Constructors
        public SourceListView ()
        {

        }

        public SourceListView (IntPtr handle) : base(handle)
        {

        }

        public SourceListView (NSCoder coder) : base(coder)
        {

        }

        public SourceListView (NSObjectFlag t) : base(t)
        {

        }
        #endregion

        #region Override Methods
        public override void AwakeFromNib ()
        {
            base.AwakeFromNib ();
        }
        #endregion

        #region Public Methods
        public void Initialize() {

            // Initialize this instance
            this.DataSource = new SourceListDataSource (this);
            this.Delegate = new SourceListDelegate (this);

        }

        public void AddItem(SourceListItem item) {
            if (Data != null) {
                Data.Items.Add (item);
            }
        }
        #endregion

        #region Events
        public delegate void ItemSelectedDelegate(SourceListItem item);
        public event ItemSelectedDelegate ItemSelected;

        internal void RaiseItemSelected(SourceListItem item) {
            // Inform caller
            if (this.ItemSelected != null) {
                this.ItemSelected (item);
            }
        }
        #endregion
    }
}

Isso cria uma subclasse personalizada e reutilizável de (SourceListView) que podemos usar para conduzir a Lista de NSOutlineView Origem em qualquer aplicativo Xamarin.Mac que criarmos.

Criando e mantendo listas de origem no Xcode

Agora, vamos projetar nossa Lista de Origem no Construtor de Interfaces. Clique duas vezes no Main.storyboard arquivo para abri-lo para edição no Construtor de Interfaces e arraste uma Exibição dividida do Inspetor de biblioteca, adicione-a ao Controlador de exibição e configure-a para redimensionar com a exibição no Editor de restrições:

Editando restrições no Construtor de Interfaces.

Em seguida, arraste uma Lista de Código-Fonte do Inspetor de Biblioteca, adicione-a ao lado esquerdo da Exibição dividida e configure-a para redimensionar com a Exibição no Editor de restrições:

Editando restrições arrastando uma Lista de Origem para a Exibição de Divisão.

Em seguida, alterne para o Modo de Exibição de Identidade, selecione a Lista de Origem e altere sua Classe para SourceListView:

Definindo o nome da classe

Por fim, crie uma saída para nossa lista de fontes chamada SourceList no ViewController.h arquivo:

Configurando uma tomada

Salve suas alterações e retorne ao Visual Studio para Mac para sincronizar com o Xcode.

Preenchendo a lista de origem

Vamos editar o RotationWindow.cs arquivo no Visual Studio para Mac e fazer com que seu AwakeFromNib método seja semelhante ao seguinte:

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

    // Populate source list
    SourceList.Initialize ();

    var library = new SourceListItem ("Library");
    library.AddItem ("Venues", "house.png", () => {
        Console.WriteLine("Venue Selected");
    });
    library.AddItem ("Singers", "group.png");
    library.AddItem ("Genre", "cards.png");
    library.AddItem ("Publishers", "box.png");
    library.AddItem ("Artist", "person.png");
    library.AddItem ("Music", "album.png");
    SourceList.AddItem (library);

    // Add Rotation
    var rotation = new SourceListItem ("Rotation");
    rotation.AddItem ("View Rotation", "redo.png");
    SourceList.AddItem (rotation);

    // Add Kiosks
    var kiosks = new SourceListItem ("Kiosks");
    kiosks.AddItem ("Sign-in Station 1", "imac");
    kiosks.AddItem ("Sign-in Station 2", "ipad");
    SourceList.AddItem (kiosks);

    // Display side list
    SourceList.ReloadData ();
    SourceList.ExpandItem (null, true);

}

O Initialize () método precisa ser chamado em relação ao Outlet da nossa Lista de Origem antes que qualquer item seja adicionado a ele. Para cada grupo de itens, criamos um item pai e, em seguida, adicionamos os subitens a esse item de grupo. Cada grupo é então adicionado à coleção SourceList.AddItem (...)da Lista de Origem. As duas últimas linhas carregam os dados para a Lista de Origem e expandem todos os grupos:

// Display side list
SourceList.ReloadData ();
SourceList.ExpandItem (null, true);

Por fim, edite o AppDelegate.cs arquivo e faça com que o DidFinishLaunching método fique parecido com o seguinte:

public override void DidFinishLaunching (NSNotification notification)
{
    mainWindowController = new MainWindowController ();
    mainWindowController.Window.MakeKeyAndOrderFront (this);

    var rotation = new RotationWindowController ();
    rotation.Window.MakeKeyAndOrderFront (this);
}

Se executarmos nosso aplicativo, o seguinte será exibido:

Um exemplo de execução de aplicativo

Resumo

Este artigo examinou detalhadamente como trabalhar com Listas de Origem em um aplicativo Xamarin.Mac. Vimos como criar e manter Listas de Origem no Construtor de Interfaces do Xcode e como trabalhar com Listas de Origem no código C#.