Zdrojové seznamy v Xamarin.Mac

Tento článek popisuje práci se zdrojovými seznamy v aplikaci Xamarin.Mac. Popisuje vytváření a údržbu zdrojových seznamů v Xcode a Interface Builderu a interakci s nimi v kódu jazyka C#.

Při práci s C# a .NET v aplikaci Xamarin.Mac máte přístup ke stejným zdrojovým seznamům, ve Objective-C kterých vývojář pracuje a Xcode . Vzhledem k tomu, že se Xamarin.Mac integruje přímo s Xcode, můžete pomocí Tvůrce rozhraní Xcode vytvářet a udržovat zdrojové seznamy (nebo je volitelně vytvářet přímo v kódu jazyka C#).

Zdrojový seznam je speciální typ zobrazení osnovy, který slouží k zobrazení zdroje akce, jako je boční panel ve Finderu nebo iTunes.

An example source list

V tomto článku se podíváme na základy práce se zdrojovými seznamy v aplikaci Xamarin.Mac. Důrazně doporučujeme, abyste si nejprve prošli článek Hello, Mac , konkrétně úvod do Xcode a Tvůrce rozhraní a výstupy a akce , protože se zabývá klíčovými koncepty a technikami, které budeme používat v tomto článku.

Možná se také budete chtít podívat na oddíly v dokumentu Interní dokumenty Xamarin.Mac pro zveřejnění tříd a metodObjective-Cjazyka C#. Vysvětluje také, které Register příkazy a Export které se používají k připojení tříd jazyka C# k Objective-C objektům a prvkům uživatelského rozhraní.

Úvod do zdrojových seznamů

Jak je uvedeno výše, zdrojový seznam je speciální typ zobrazení osnovy, který slouží k zobrazení zdroje akce, jako je boční panel ve Finderu nebo iTunes. Zdrojový seznam je typ tabulky, která uživateli umožňuje rozbalit nebo sbalit řádky hierarchických dat. Na rozdíl od zobrazení tabulky nejsou položky ve zdrojovém seznamu v plochém seznamu uspořádané v hierarchii, jako jsou soubory a složky na pevném disku. Pokud položka ve zdrojovém seznamu obsahuje další položky, může ji uživatel rozbalit nebo sbalit.

Zdrojový seznam je speciálně stylované zobrazení osnovy (NSOutlineView), které je sama podtřídou zobrazení tabulky (NSTableView) a jako takové dědí většinu svého chování z nadřazené třídy. V důsledku toho je seznam zdrojů podporovaných mnoha operacemi podporovanými zobrazením osnovy. Aplikace Xamarin.Mac má kontrolu nad těmito funkcemi a může nakonfigurovat parametry zdrojového seznamu (buď v kódu, nebo v Tvůrci rozhraní), aby umožňovaly nebo nepovolily určité operace.

Zdrojový seznam neukládá vlastní data, místo toho spoléhá na zdroj dat (NSOutlineViewDataSource) k poskytnutí požadovaných řádků i sloupců podle potřeby.

Chování zdrojového seznamu lze přizpůsobit poskytnutím podtřídy delegáta zobrazení osnovy (NSOutlineViewDelegate) pro podporu typu osnovy pro výběr funkcí, výběru položek a úprav, vlastní sledování a vlastní zobrazení pro jednotlivé položky.

Vzhledem k tomu, že zdrojový seznam sdílí většinu jeho chování a funkcí se zobrazením tabulky a zobrazením osnovy, můžete si před pokračováním v tomto článku projít naši dokumentaci k zobrazením tabulek a zobrazení osnovy.

Práce se zdrojovými seznamy

Zdrojový seznam je speciální typ zobrazení osnovy, který slouží k zobrazení zdroje akce, jako je boční panel ve Finderu nebo iTunes. Na rozdíl od zobrazení osnovy vytvoříme před definováním zdrojového seznamu v Tvůrci rozhraní třídy backing v Xamarin.Mac.

Nejprve vytvoříme novou SourceListItem třídu, která bude uchovávat data pro náš seznam zdrojů. V Průzkumník řešení klikněte pravým tlačítkem na Projekt a vyberte Přidat>nový soubor... Vyberte položku Obecné>prázdné třídy, zadejte SourceListItem název a klikněte na tlačítko Nový:

Adding an empty class

Udělejte soubor SourceListItem.cs takto:

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

V Průzkumník řešení klikněte pravým tlačítkem na Projekt a vyberte Přidat>nový soubor... Vyberte Obecné>prázdné třídy, zadejte SourceListDataSource název a klikněte na tlačítko Nový. Udělejte soubor SourceListDataSource.cs takto:

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

Tím poskytnete data pro náš seznam zdrojů.

V Průzkumník řešení klikněte pravým tlačítkem na Projekt a vyberte Přidat>nový soubor... Vyberte Obecné>prázdné třídy, zadejte SourceListDelegate název a klikněte na tlačítko Nový. Udělejte soubor SourceListDelegate.cs takto:

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

Tím zajistíte chování našeho zdrojového seznamu.

Nakonec v Průzkumník řešení klikněte pravým tlačítkem na Projekt a vyberte Přidat>nový soubor... Vyberte Obecné>prázdné třídy, zadejte SourceListView název a klikněte na tlačítko Nový. Udělejte soubor SourceListView.cs takto:

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

Tím se vytvoří vlastní opakovaně použitelná podtřída NSOutlineView (SourceListView), kterou můžeme použít k řízení zdrojového seznamu v libovolné aplikaci Xamarin.Mac, kterou vytvoříme.

Vytváření a údržba zdrojových seznamů v Xcode

Teď v Tvůrci rozhraní navrhneme seznam zdrojů. Poklikáním otevřete Main.storyboard soubor pro úpravy v Tvůrci rozhraní a přetáhněte rozdělené zobrazení z inspektoru knihovny, přidejte ho do kontroleru zobrazení a nastavte jeho velikost pomocí zobrazení v Editoru omezení:

Editing constraints in the Interface Builder.

Potom přetáhněte zdrojový seznam z nástroje Library Inspector, přidejte ho na levou stranu rozděleného zobrazení a nastavte jeho velikost pomocí zobrazení v Editoru omezení:

Editing constraints by dragging a Source List to the Split View.

Pak přepněte do zobrazení identity, vyberte zdrojový seznam a změňte třídu naSourceListView:

Setting the class name

Nakonec vytvořte výstup pro náš zdrojový seznam volaný SourceList v ViewController.h souboru:

Configuring an Outlet

Uložte změny a vraťte se do Visual Studio pro Mac pro synchronizaci s Xcode.

Naplnění zdrojového seznamu

Pojďme soubor upravit RotationWindow.cs v Visual Studio pro Mac a nastavit, AwakeFromNib aby metoda vypadala takto:

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);

}

Metodu Initialize () je potřeba volat proti výstupu seznamu zdrojů, než se do ní přidají všechny položky. Pro každou skupinu položek vytvoříme nadřazenou položku a pak do této položky skupiny přidáme dílčí položky. Každá skupina se pak přidá do kolekce SourceList.AddItem (...)zdrojového seznamu . Poslední dva řádky načtou data pro zdrojový seznam a rozbalí všechny skupiny:

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

Nakonec upravte AppDelegate.cs soubor a udělejte metodu DidFinishLaunching takto:

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

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

Pokud spustíme naši aplikaci, zobrazí se následující:

An example app run

Shrnutí

Tento článek se podrobně podíval na práci se zdrojovými seznamy v aplikaci Xamarin.Mac. Viděli jsme, jak vytvářet a udržovat zdrojové seznamy v Tvůrci rozhraní Xcode a jak pracovat se zdrojovými seznamy v kódu jazyka C#.