Представления таблиц в Xamarin.Mac

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

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

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

An example table

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

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

Общие сведения о представлениях таблиц

Представление таблицы отображает данные в табличном формате с одним или несколькими столбцами информации в нескольких строках. Представления таблиц отображаются внутри представлений прокрутки (NSScrollView) и начинаются с macOS 10.7, а NSView не ячейки (NSCell) для отображения как строк, так и столбцов. Тем не менее, вы по-прежнему можете использовать NSCell подкласс NSTableCellView и создавать пользовательские строки и столбцы.

Представление таблицы не хранит собственные данные, а использует источник данных (NSTableViewDataSource) для предоставления строк и столбцов, необходимых по мере необходимости.

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

При создании представлений таблиц Apple предлагает следующее:

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

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

Создание и обслуживание представлений таблиц в Xcode

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

Selecting the main storyboard

Откроется конструктор окон в построителе интерфейсов Xcode:

Editing the UI in Xcode

Введите table в поле поиска инспектора библиотеки, чтобы упростить поиск элементов управления представлением таблицы:

Selecting a Table View from the Library

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

Editing constraints

Выберите представление таблицы в иерархии интерфейса, а следующие свойства доступны в инспекторе атрибутов:

Screenshot shows the properties available in the Attribute Inspector.

  • Режим содержимого— позволяет использовать представления (NSView) или ячейки (NSCell) для отображения данных в строках и столбцах. Начиная с macOS 10.7, следует использовать представления.
  • Строки группы с плавающей запятой — если trueпредставление таблицы нарисует сгруппированные ячейки, как если бы они плавали.
  • Столбцы — определяет количество отображаемых столбцов.
  • Заголовки — если trueстолбцы будут иметь заголовки.
  • Переупорядочение — если trueпользователь сможет перетаскивать столбцы в таблице.
  • Изменение размера — если trueпользователь сможет перетащить заголовки столбцов для изменения размера столбцов.
  • Размер столбцов — определяет, как таблица будет автоматически размерить столбцы.
  • Выделение — управляет типом выделения таблицы, используемой при выборе ячейки.
  • Альтернативные строки — если true, когда-либо другая строка будет иметь другой цвет фона.
  • Горизонтальная сетка — выбирает тип границы, рисуемой между ячейками по горизонтали.
  • Вертикальная сетка — выбирает тип границы, рисуемой между ячейками по вертикали.
  • Цвет сетки — задает цвет границы ячейки.
  • Фон — задает цвет фона ячейки.
  • Выбор . Позволяет управлять тем, как пользователь может выбирать ячейки в таблице следующим образом:
    • Несколько — если trueпользователь может выбрать несколько строк и столбцов.
    • Столбец — если trueпользователь может выбрать столбцы.
    • Выбор типа — если trueпользователь может ввести символ, чтобы выбрать строку.
    • Пустое — если trueпользователю не требуется выбрать строку или столбец, таблица не позволяет выбирать ни в коем случае.
  • Автосохранение — имя, в которое формат таблиц автоматически сохраняется.
  • Сведения о столбцах — если trueпорядок и ширина столбцов будут автоматически сохранены.
  • Разрывы строк— выберите способ обработки разрывов строк ячейки.
  • Усечение последней видимой строки . Если trueячейка будет усечена в данных не может помещаться внутри границ.

Внимание

Если вы не поддерживаете устаревшее приложение Xamarin.Mac, NSView представления таблиц на основе таблицы должны использоваться в NSCell представлениях таблиц на основе. NSCell считается устаревшим и может не поддерживаться в будущем.

Выберите столбец таблицы в иерархии интерфейса, а следующие свойства доступны в инспекторе атрибутов:

Screenshot shows the properties available for a Table Column in the Attribute Inspector.

  • Заголовок — задает заголовок столбца.
  • Выравнивание — установка выравнивания текста в ячейках.
  • Заголовок шрифта — выбирает шрифт для текста заголовка ячейки.
  • Ключ сортировки — это ключ , используемый для сортировки данных в столбце. Оставьте пустым, если пользователь не может сортировать этот столбец.
  • Селектор — это действие , используемое для выполнения сортировки. Оставьте пустым, если пользователь не может сортировать этот столбец.
  • Порядок — это порядок сортировки данных столбцов.
  • Изменение размера — выбирает тип изменения размера столбца.
  • Редактируемый — если trueпользователь может изменять ячейки в таблице на основе ячеек.
  • Скрытый — если trueстолбец скрыт.

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

Давайте выделите каждый столбец в представлении таблицы и присвойте первому столбцу заголовокProduct и второй Details.

Выберите представление ячейки таблицы (NSTableViewCell) в иерархии интерфейса и в инспекторе атрибутов доступны следующие свойства:

Screenshot shows the properties available for a Table Cell View in the Attribute Inspector.

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

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

Screenshot shows the properties available for a Table View Cell in the Attribute Inspector.

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

Выберите представление ячейки таблицы (NSTableFieldCell) в иерархии интерфейса и в инспекторе атрибутов доступны следующие свойства:

Screenshot shows the properties available for a different Table View Cell in the Attribute Inspector.

Ниже приведены наиболее важные параметры.

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

Выберите представление ячейки таблицы (NSTableFieldCell) в нижней части столбца таблицы в иерархии интерфейса:

Selecting the Table Cell View

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

Добавление действий и точек

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

Процесс совпадает с любым элементом Table View, который мы хотим предоставить:

  1. Перейдите в редактор помощника и убедитесь, что ViewController.h выбран файл:

    The Assistant Editor

  2. Выберите представление таблицы из иерархии интерфейса, щелкните элемент управления и перетащите его в ViewController.h файл.

  3. Создайте выход для представления таблицы:ProductTable

    Screenshot shows an Outlet connection created for the Table View named ProductTable.

  4. Создайте точки для столбцов таблиц, которые также называются ProductColumn и DetailsColumn:

    Screenshot shows an Outlet connections created for other Table Views.

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

Затем мы напишем код, отображая некоторые данные для таблицы при запуске приложения.

Заполнение представления таблицы

С помощью представления таблицы, разработанного в построителе интерфейсов и предоставляемых через выход, необходимо создать код C#, чтобы заполнить его.

Сначала создадим новый Product класс для хранения сведений для отдельных строк. В Обозреватель решений щелкните проект правой кнопкой мыши и выберите "Добавить>новый файл" ... Выберите общий>пустой класс, введите Productимя и нажмите кнопку "Создать":

Creating an empty class

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

using System;

namespace MacTables
{
  public class Product
  {
    #region Computed Properties
    public string Title { get; set;} = "";
    public string Description { get; set;} = "";
    #endregion

    #region Constructors
    public Product ()
    {
    }

    public Product (string title, string description)
    {
      this.Title = title;
      this.Description = description;
    }
    #endregion
  }
}

Затем необходимо создать подкласс NSTableDataSource для предоставления данных для таблицы по мере его запроса. В Обозреватель решений щелкните проект правой кнопкой мыши и выберите "Добавить>новый файл" ... Выберите общий>пустой класс, введите ProductTableDataSourceимя и нажмите кнопку "Создать".

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

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

namespace MacTables
{
  public class ProductTableDataSource : NSTableViewDataSource
  {
    #region Public Variables
    public List<Product> Products = new List<Product>();
    #endregion

    #region Constructors
    public ProductTableDataSource ()
    {
    }
    #endregion

    #region Override Methods
    public override nint GetRowCount (NSTableView tableView)
    {
      return Products.Count;
    }
    #endregion
  }
}

Этот класс содержит хранилище для элементов представления таблиц и переопределяет GetRowCount количество строк в таблице.

Наконец, необходимо создать подкласс NSTableDelegate , чтобы обеспечить поведение для нашей таблицы. В Обозреватель решений щелкните проект правой кнопкой мыши и выберите "Добавить>новый файл" ... Выберите общий>пустой класс, введите ProductTableDelegateимя и нажмите кнопку "Создать".

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

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

namespace MacTables
{
  public class ProductTableDelegate: NSTableViewDelegate
  {
    #region Constants 
    private const string CellIdentifier = "ProdCell";
    #endregion

    #region Private Variables
    private ProductTableDataSource DataSource;
    #endregion

    #region Constructors
    public ProductTableDelegate (ProductTableDataSource datasource)
    {
      this.DataSource = datasource;
    }
    #endregion

    #region Override Methods
    public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
    {
      // This pattern allows you reuse existing views when they are no-longer in use.
      // If the returned view is null, you instance up a new view
      // If a non-null view is returned, you modify it enough to reflect the new data
      NSTextField view = (NSTextField)tableView.MakeView (CellIdentifier, this);
      if (view == null) {
        view = new NSTextField ();
        view.Identifier = CellIdentifier;
        view.BackgroundColor = NSColor.Clear;
        view.Bordered = false;
        view.Selectable = false;
        view.Editable = false;
      }

      // Setup view based on the column selected
      switch (tableColumn.Title) {
      case "Product":
        view.StringValue = DataSource.Products [(int)row].Title;
        break;
      case "Details":
        view.StringValue = DataSource.Products [(int)row].Description;
        break;
      }

      return view;
    }
    #endregion
  }
}

При создании экземпляра ProductTableDelegateтакже передаются экземпляры ProductTableDataSource , предоставляющие данные для таблицы. Метод GetViewForItem отвечает за возврат представления (данных) для отображения ячейки для заданного столбца и строки. Если это возможно, существующее представление будет повторно использоваться для отображения ячейки, если она не должна быть создана.

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

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

  // Create the Product Table Data Source and populate it
  var DataSource = new ProductTableDataSource ();
  DataSource.Products.Add (new Product ("Xamarin.iOS", "Allows you to develop native iOS Applications in C#"));
  DataSource.Products.Add (new Product ("Xamarin.Android", "Allows you to develop native Android Applications in C#"));
  DataSource.Products.Add (new Product ("Xamarin.Mac", "Allows you to develop Mac native Applications in C#"));

  // Populate the Product Table
  ProductTable.DataSource = DataSource;
  ProductTable.Delegate = new ProductTableDelegate (DataSource);
}

Если запустить приложение, отобразится следующее:

Screenshot shows a window named Product Table with three entries.

Сортировка по столбцу

Давайте разрешим пользователю отсортировать данные в таблице, щелкнув заголовок столбца. Сначала дважды щелкните Main.storyboard файл, чтобы открыть его для редактирования в Конструкторе интерфейсов. Product Выберите столбец, введите Title ключ compare:сортировки для селектораи выберите Ascending для заказа:

Screenshot shows the Interface Builder where you can set the sort key for the Product column.

Details Выберите столбец, введите Description ключ compare:сортировки для селектораи выберите Ascending для заказа:

Screenshot shows the Interface Builder where you can set the sort key for the Details column.

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

Теперь давайте отредактируем ProductTableDataSource.cs файл и добавьте следующие методы:

public void Sort(string key, bool ascending) {

  // Take action based on key
  switch (key) {
  case "Title":
    if (ascending) {
      Products.Sort ((x, y) => x.Title.CompareTo (y.Title));
    } else {
      Products.Sort ((x, y) => -1 * x.Title.CompareTo (y.Title));
    }
    break;
  case "Description":
    if (ascending) {
      Products.Sort ((x, y) => x.Description.CompareTo (y.Description));
    } else {
      Products.Sort ((x, y) => -1 * x.Description.CompareTo (y.Description));
    }
    break;
  }

}

public override void SortDescriptorsChanged (NSTableView tableView, NSSortDescriptor[] oldDescriptors)
{
  // Sort the data
  if (oldDescriptors.Length > 0) {
    // Update sort
    Sort (oldDescriptors [0].Key, oldDescriptors [0].Ascending);
  } else {
    // Grab current descriptors and update sort
    NSSortDescriptor[] tbSort = tableView.SortDescriptors; 
    Sort (tbSort[0].Key, tbSort[0].Ascending); 
  }
      
  // Refresh table
  tableView.ReloadData ();
}

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

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

An example app run

Выбор строки

Если вы хотите разрешить пользователю выбрать одну строку, дважды щелкните Main.storyboard файл, чтобы открыть его для редактирования в Конструкторе интерфейсов. Выберите представление таблицы в иерархии интерфейса и un проверка поле "Несколько проверка" в инспекторе атрибутов:

Screenshot shows the Interface Builder where you can select Multiple in the Attribute Inspector.

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

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

public override bool ShouldSelectRow (NSTableView tableView, nint row)
{
  return true;
}

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

Представление таблицы (NSTableView) содержит следующие методы для работы с выбором строк:

  • DeselectRow(nint) — отменяет выбор заданной строки в таблице.
  • SelectRow(nint,bool) — выбирает указанную строку. Передайте false второй параметр, чтобы выбрать только одну строку за раз.
  • SelectedRow — возвращает текущую строку, выбранную в таблице.
  • IsRowSelected(nint) — возвращает значение true , если выбрана заданная строка.

Выбор нескольких строк

Если вы хотите разрешить пользователю выбрать несколько строк, дважды щелкните Main.storyboard файл, чтобы открыть его для редактирования в Конструкторе интерфейсов. Выберите представление таблицы в иерархии интерфейса и проверка поле "Несколько проверка" в инспекторе атрибутов:

Screenshot shows the Interface Builder where you can select Multiple to allow multiple row selection.

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

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

public override bool ShouldSelectRow (NSTableView tableView, nint row)
{
  return true;
}

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

Представление таблицы (NSTableView) содержит следующие методы для работы с выбором строк:

  • DeselectAll(NSObject) — отменяет выбор всех строк в таблице. Используйте this первый параметр для отправки в объекте, выполняющего выбор.
  • DeselectRow(nint) — отменяет выбор заданной строки в таблице.
  • SelectAll(NSobject) — выбирает все строки в таблице. Используйте this первый параметр для отправки в объекте, выполняющего выбор.
  • SelectRow(nint,bool) — выбирает указанную строку. Передайте второй параметр снимите выделение и выберите только одну строку, передайте falsetrue , чтобы расширить выделение и включить эту строку.
  • SelectRows(NSIndexSet,bool) — выбирает заданный набор строк. Передайте false второй параметр снимите выделение и выберите только эти строки, передайте true , чтобы расширить выделение и включить эти строки.
  • SelectedRow — возвращает текущую строку, выбранную в таблице.
  • SelectedRows — возвращает NSIndexSet индексы выбранных строк.
  • SelectedRowCount — возвращает количество выбранных строк.
  • IsRowSelected(nint) — возвращает значение true , если выбрана заданная строка.

Тип для выбора строки

Если вы хотите разрешить пользователю вводить символ с выбранным представлением таблицы и выбрать первую строку с таким символом, дважды щелкните Main.storyboard файл, чтобы открыть его для редактирования в Конструкторе интерфейсов. Выберите представление таблицы в иерархии интерфейса и проверка поле "Выбрать проверка" в инспекторе атрибутов:

Setting the selection type

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

Теперь давайте отредактируем ProductTableDelegate.cs файл и добавьте следующий метод:

public override nint GetNextTypeSelectMatch (NSTableView tableView, nint startRow, nint endRow, string searchString)
{
  nint row = 0;
  foreach(Product product in DataSource.Products) {
    if (product.Title.Contains(searchString)) return row;

    // Increment row counter
    ++row;
  }

  // If not found select the first row
  return 0;
}

Метод GetNextTypeSelectMatch принимает заданный searchString и возвращает строку первой Product строки, которая содержит строку в ней Title.

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

Screenshot shows the result of running the application.

Изменение порядка столбцов

Если вы хотите разрешить пользователю перетаскивать столбцы переупорядочения в представлении таблицы, дважды щелкните Main.storyboard файл, чтобы открыть его для редактирования в Конструкторе интерфейсов. Выберите представление таблицы в иерархии интерфейса и проверка поле "Изменить порядок проверка" в инспекторе атрибутов:

Screenshot shows the Interface Builder where you can select Reodering in the Attribute Inspector.

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

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

Теперь давайте отредактируем ProductTableDelegate.cs файл и добавьте следующий метод:

public override bool ShouldReorder (NSTableView tableView, nint columnIndex, nint newColumnIndex)
{
  return true;
}

Метод ShouldReorder должен возвращать true любой столбец, который требуется разрешить переупорядочению в другой newColumnIndexвозвращаемый falseстолбец;

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

An example of the reordered columns

Редактирование ячеек

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

public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
  // This pattern allows you reuse existing views when they are no-longer in use.
  // If the returned view is null, you instance up a new view
  // If a non-null view is returned, you modify it enough to reflect the new data
  NSTextField view = (NSTextField)tableView.MakeView (tableColumn.Title, this);
  if (view == null) {
    view = new NSTextField ();
    view.Identifier = tableColumn.Title;
    view.BackgroundColor = NSColor.Clear;
    view.Bordered = false;
    view.Selectable = false;
    view.Editable = true;

    view.EditingEnded += (sender, e) => {
          
      // Take action based on type
      switch(view.Identifier) {
      case "Product":
        DataSource.Products [(int)view.Tag].Title = view.StringValue;
        break;
      case "Details":
        DataSource.Products [(int)view.Tag].Description = view.StringValue;
        break; 
      }
    };
  }

  // Tag view
  view.Tag = row;

  // Setup view based on the column selected
  switch (tableColumn.Title) {
  case "Product":
    view.StringValue = DataSource.Products [(int)row].Title;
    break;
  case "Details":
    view.StringValue = DataSource.Products [(int)row].Description;
    break;
  }

  return view;
}

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

An example of editing a cell

Использование изображений в представлениях таблиц

Чтобы включить изображение в ячейку в NSTableViewячейку, необходимо изменить способ возврата данных методом представления NSTableViewDelegate'sGetViewForItem таблицы, чтобы использовать NSTableCellView вместо типичных NSTextField. Например:

public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{

  // This pattern allows you reuse existing views when they are no-longer in use.
  // If the returned view is null, you instance up a new view
  // If a non-null view is returned, you modify it enough to reflect the new data
  NSTableCellView view = (NSTableCellView)tableView.MakeView (tableColumn.Title, this);
  if (view == null) {
    view = new NSTableCellView ();
    if (tableColumn.Title == "Product") {
      view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
      view.AddSubview (view.ImageView);
      view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
    } else {
      view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
    }
    view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
    view.AddSubview (view.TextField);
    view.Identifier = tableColumn.Title;
    view.TextField.BackgroundColor = NSColor.Clear;
    view.TextField.Bordered = false;
    view.TextField.Selectable = false;
    view.TextField.Editable = true;

    view.TextField.EditingEnded += (sender, e) => {

      // Take action based on type
      switch(view.Identifier) {
      case "Product":
        DataSource.Products [(int)view.TextField.Tag].Title = view.TextField.StringValue;
        break;
      case "Details":
        DataSource.Products [(int)view.TextField.Tag].Description = view.TextField.StringValue;
        break; 
      }
    };
  }

  // Tag view
  view.TextField.Tag = row;

  // Setup view based on the column selected
  switch (tableColumn.Title) {
  case "Product":
    view.ImageView.Image = NSImage.ImageNamed ("tags.png");
    view.TextField.StringValue = DataSource.Products [(int)row].Title;
    break;
  case "Details":
    view.TextField.StringValue = DataSource.Products [(int)row].Description;
    break;
  }

  return view;
}

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

Добавление кнопки удаления в строку

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

Сначала измените построитель Main.storyboard интерфейсов Xcode, выберите представление таблицы и увеличьте число столбцов до трех (3). Затем измените заголовок нового столбца Actionна :

Editing the column name

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

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

public void ReloadTable ()
{
  ProductTable.ReloadData ();
}

В том же файле измените создание нового делегата представления таблиц внутри ViewDidLoad метода следующим образом:

// Populate the Product Table
ProductTable.DataSource = DataSource;
ProductTable.Delegate = new ProductTableDelegate (this, DataSource);

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

#region Private Variables
private ProductTableDataSource DataSource;
private ViewController Controller;
#endregion

#region Constructors
public ProductTableDelegate (ViewController controller, ProductTableDataSource datasource)
{
  this.Controller = controller;
  this.DataSource = datasource;
}
#endregion

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

private void ConfigureTextField (NSTableCellView view, nint row)
{
  // Add to view
  view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
  view.AddSubview (view.TextField);

  // Configure
  view.TextField.BackgroundColor = NSColor.Clear;
  view.TextField.Bordered = false;
  view.TextField.Selectable = false;
  view.TextField.Editable = true;

  // Wireup events
  view.TextField.EditingEnded += (sender, e) => {

    // Take action based on type
    switch (view.Identifier) {
    case "Product":
      DataSource.Products [(int)view.TextField.Tag].Title = view.TextField.StringValue;
      break;
    case "Details":
      DataSource.Products [(int)view.TextField.Tag].Description = view.TextField.StringValue;
      break;
    }
  };

  // Tag view
  view.TextField.Tag = row;
}

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

Наконец, измените GetViewForItem метод и сделайте его следующим образом:

public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{

  // This pattern allows you reuse existing views when they are no-longer in use.
  // If the returned view is null, you instance up a new view
  // If a non-null view is returned, you modify it enough to reflect the new data
  NSTableCellView view = (NSTableCellView)tableView.MakeView (tableColumn.Title, this);
  if (view == null) {
    view = new NSTableCellView ();

    // Configure the view
    view.Identifier = tableColumn.Title;

    // Take action based on title
    switch (tableColumn.Title) {
    case "Product":
      view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
      view.AddSubview (view.ImageView);
      view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
      ConfigureTextField (view, row);
      break;
    case "Details":
      view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
      ConfigureTextField (view, row);
      break;
    case "Action":
      // Create new button
      var button = new NSButton (new CGRect (0, 0, 81, 16));
      button.SetButtonType (NSButtonType.MomentaryPushIn);
      button.Title = "Delete";
      button.Tag = row;

      // Wireup events
      button.Activated += (sender, e) => {
        // Get button and product
        var btn = sender as NSButton;
        var product = DataSource.Products [(int)btn.Tag];

        // Configure alert
        var alert = new NSAlert () {
          AlertStyle = NSAlertStyle.Informational,
          InformativeText = $"Are you sure you want to delete {product.Title}? This operation cannot be undone.",
          MessageText = $"Delete {product.Title}?",
        };
        alert.AddButton ("Cancel");
        alert.AddButton ("Delete");
        alert.BeginSheetForResponse (Controller.View.Window, (result) => {
          // Should we delete the requested row?
          if (result == 1001) {
            // Remove the given row from the dataset
            DataSource.Products.RemoveAt((int)btn.Tag);
            Controller.ReloadTable ();
          }
        });
      };

      // Add to view
      view.AddSubview (button);
      break;
    }

  }

  // Setup view based on the column selected
  switch (tableColumn.Title) {
  case "Product":
    view.ImageView.Image = NSImage.ImageNamed ("tag.png");
    view.TextField.StringValue = DataSource.Products [(int)row].Title;
    view.TextField.Tag = row;
    break;
  case "Details":
    view.TextField.StringValue = DataSource.Products [(int)row].Description;
    view.TextField.Tag = row;
    break;
  case "Action":
    foreach (NSView subview in view.Subviews) {
      var btn = subview as NSButton;
      if (btn != null) {
        btn.Tag = row;
      }
    }
    break;
  }

  return view;
}

Давайте рассмотрим несколько разделов этого кода более подробно. Во-первых, если создается новое NSTableViewCell действие, выполняется на основе имени столбца. Для первых двух столбцов (Product и Details) вызывается новый ConfigureTextField метод.

Для столбца "Действие " NSButton создается и добавляется в ячейку в виде подчиненного представления:

// Create new button
var button = new NSButton (new CGRect (0, 0, 81, 16));
button.SetButtonType (NSButtonType.MomentaryPushIn);
button.Title = "Delete";
button.Tag = row;
...

// Add to view
view.AddSubview (button);

Свойство Button Tag используется для хранения числа обрабатываемых строк. Это число будет использоваться позже, когда пользователь запрашивает удаление строки в событии Кнопки Activated :

// Wireup events
button.Activated += (sender, e) => {
  // Get button and product
  var btn = sender as NSButton;
  var product = DataSource.Products [(int)btn.Tag];

  // Configure alert
  var alert = new NSAlert () {
    AlertStyle = NSAlertStyle.Informational,
    InformativeText = $"Are you sure you want to delete {product.Title}? This operation cannot be undone.",
    MessageText = $"Delete {product.Title}?",
  };
  alert.AddButton ("Cancel");
  alert.AddButton ("Delete");
  alert.BeginSheetForResponse (Controller.View.Window, (result) => {
    // Should we delete the requested row?
    if (result == 1001) {
      // Remove the given row from the dataset
      DataSource.Products.RemoveAt((int)btn.Tag);
      Controller.ReloadTable ();
    }
  });
};

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

// Remove the given row from the dataset
DataSource.Products.RemoveAt((int)btn.Tag);
Controller.ReloadTable ();

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

// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
  view.ImageView.Image = NSImage.ImageNamed ("tag.png");
  view.TextField.StringValue = DataSource.Products [(int)row].Title;
  view.TextField.Tag = row;
  break;
case "Details":
  view.TextField.StringValue = DataSource.Products [(int)row].Description;
  view.TextField.Tag = row;
  break;
case "Action":
  foreach (NSView subview in view.Subviews) {
    var btn = subview as NSButton;
    if (btn != null) {
      btn.Tag = row;
    }
  }
  break;
}

Для столбца Действия все вложенные представления сканируются до тех пор, пока не будет найден, NSButton то его Tag свойство обновляется до точки в текущей строке.

При наличии этих изменений при запуске приложения каждая строка будет иметь кнопку "Удалить ":

The table view with deletion buttons

Когда пользователь нажимает кнопку "Удалить ", появится оповещение с просьбой удалить указанную строку:

A delete row alert

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

The table after the row is deleted

Представления таблицы привязки данных

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

Кодирование ключей (KVC) — это механизм косвенного доступа к свойствам объекта с помощью ключей (специально отформатированных строк) для идентификации свойств вместо доступа к ним через переменные экземпляра или методы доступа (get/set). Реализуя соответствующие методы доступа к кодированию ключей в приложении Xamarin.Mac, вы получаете доступ к другим функциям macOS, таким как наблюдение за ключевым значением (KVO), привязка данных, основные данные, привязки Какао и скрипты.

Дополнительные сведения см. в разделе "Привязка данных представления данных" в документации по привязке данных и кодированию ключа.

Итоги

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