Compartir a través de


Vistas de tabla en Xamarin.Mac

En este artículo, se explica cómo trabajar con vistas de tabla en una aplicación de Xamarin.Mac. Describe cómo crear vistas de tabla en Xcode e Interface Builder e interactuar con ellas en el código.

Cuando se trabaja con C# y .NET en una aplicación de Xamarin.Mac, se tiene acceso a las mismas vistas de tabla que un desarrollador que trabaje en Objective-C y Xcode. Dado que Xamarin.Mac se integra directamente con Xcode, puede usar Interface Builder de Xcode para crear y mantener las vistas de tabla (u opcionalmente crearlas directamente en código C#).

Una vista de tabla muestra los datos en un formato tabular que contiene una o más columnas de información en varias filas. Según el tipo de vista de tabla que se va a crear, el usuario puede ordenar por columna, reorganizar columnas, agregar columnas, quitar columnas o editar los datos contenidos en la tabla.

Una tabla de ejemplo

En este artículo, trataremos los conceptos básicos del trabajo con vistas de tabla en una aplicación de Xamarin.Mac. Es muy recomendable leer primero el artículo Hello, Mac, en concreto las secciones Introducción a Xcode e Interface Builder y Salidas y acciones, ya que tratan los conceptos clave y las técnicas que se usan en este artículo.

También puede echar un vistazo a la sección Exponer clases o métodos de C# a Objective-C del documento sobre el funcionamiento interno de Xamarin.Mac, ya que en ella se explican los atributos Register y Export que se usan para conectar las clases de C# a objetos Objective-C y elementos de la interfaz de usuario.

Introducción a las vistas de tabla

Una vista de tabla muestra los datos en un formato tabular que contiene una o más columnas de información en varias filas. Las vistas de tabla se muestran dentro de las vistas de desplazamiento (NSScrollView) y, a partir de macOS 10.7, se puede usar cualquier NSView en lugar de celdas (NSCell) para mostrar las filas y columnas. Dicho esto, se puede seguir usando NSCell, pero lo habitual es subclasificar NSTableCellView y crear filas y columnas personalizadas.

Una vista de tabla no almacena sus propios datos, sino que se basa en un origen de datos (NSTableViewDataSource) para proporcionar las filas y columnas pertinentes según sea necesario.

El comportamiento de una vista de tabla se puede personalizar proporcionando una subclase del delegado de vista de tabla (NSTableViewDelegate) para admitir la administración de columnas de tabla, la funcionalidad de tipo para seleccionar, la selección y edición de filas, el seguimiento personalizado y las vistas personalizadas para columnas y filas individuales.

Para crear vistas de tabla, Apple sugiere lo siguiente:

  • Permitir al usuario ordenar la tabla haciendo clic en los encabezados de columna.
  • Crear encabezados de columna que sean nombres o frases sustantivas cortas que describan los datos que se muestran en esa columna.

Para obtener más información, consulte la sección Vistas de contenido de las directrices de interfaz humana de OS X de Apple.

Creación y mantenimiento de vistas de tabla en Xcode

Al crear una nueva aplicación de Cocoa de Xamarin.Mac, obtendrá una ventana estándar en blanco de forma predeterminada. Esta ventana se define en un archivo .storyboard incluido automáticamente en el proyecto. Para editar el diseño de la ventana, vaya a Explorador de soluciones y haga doble clic en el archivo Main.storyboard:

Selección del guión gráfico principal

Se abrirá el diseño de la ventana en Interface Builder de Xcode:

Edición de la UI en Xcode

Escriba table en el cuadro de búsqueda del Inspector de biblioteca para facilitar la búsqueda de los controles de vista de tabla:

Selección de una vista de tabla en la biblioteca

Arrastre una vista de tabla al controlador de vista del editor de interfaz, haga que rellene el área de contenido del controlador de vista y configúrela para que se reduzca y se agrande con la ventana en el editor de restricciones:

Restricciones de edición

Seleccione la vista de tabla en la jerarquía de la interfaz: las siguientes propiedades estarán disponibles en el Inspector de atributos:

La captura de pantalla muestra las propiedades disponibles en el Inspector de atributos.

  • Modo de contenido: permite usar vistas (NSView) o celdas (NSCell) para mostrar los datos en las filas y columnas. A partir de macOS 10.7, debe usar vistas.
  • Hacer flotantes las celdas agrupadas: si es true, la vista de tabla dibujará las celdas agrupadas como si fueran flotantes.
  • Columnas: define el número de columnas mostradas.
  • Encabezados: si es true, las columnas tendrán encabezados.
  • Reordenación: si es true, el usuario podrá reordenar las columnas de la tabla arrastrándolas.
  • Cambio de tamaño: si es true, el usuario podrá arrastrar los encabezados de columna para cambiar el tamaño de las columnas.
  • Tamaño de columna: controla la manera en que la tabla dimensionará automáticamente las columnas.
  • Resaltar: controla el tipo de resaltado que se usa en la tabla cuando se selecciona una celda.
  • Alternar filas: si es true, se aplicará un color de fondo diferente a las filas alternas.
  • Cuadrícula horizontal: selecciona el tipo de borde dibujado horizontalmente entre las celdas.
  • Cuadrícula vertical: selecciona el tipo de borde dibujado verticalmente entre las celdas.
  • Color de cuadrícula: establece el color del borde de celda.
  • Fondo: establece el color de fondo de la celda.
  • Selección: permite controlar cómo puede el usuario seleccionar celdas en la tabla de la siguiente forma:
    • Múltiple: si es true, el usuario puede seleccionar varias filas y columnas.
    • Columna: si es true, el usuario puede seleccionar columnas.
    • Escribir para seleccionar, si es true, el usuario puede escribir un carácter para seleccionar una fila.
    • Vacío: si es true, no es necesario que el usuario seleccione una fila o columna, la tabla no permite ninguna selección.
  • Guardar automáticamente: el nombre con el que se guarda automáticamente el formato de las tablas.
  • Información de columna: si es true, el orden y el ancho de las columnas se guardarán automáticamente.
  • Saltos de línea: seleccione cómo controla la celda los saltos de línea.
  • Trunca la última línea visible: si es true, la celda se truncará si los datos no caben dentro de sus límites.

Importante

A menos que esté manteniendo una aplicación heredada de Xamarin.Mac, se deben usar vistas de tabla basadas en NSView en lugar de vistas de tabla basadas en NSCell. NSCell se considera heredado y es posible que no se admita en el futuro.

Seleccione una columna de tabla en la jerarquía de la interfaz: las siguientes propiedades estarán disponibles en el Inspector de atributos:

La captura de pantalla muestra las propiedades disponibles para una Columna de Tabla en el Inspector de atributos.

  • Título: establece el título de la columna.
  • Alineación: establece la alineación del texto dentro de las celdas.
  • Fuente de título: selecciona la fuente del texto de encabezado de la celda.
  • Clave de ordenación: la clave que se usa para ordenar los datos de la columna. Dejar en blanco si el usuario no puede ordenar esta columna.
  • Selector: la acción que se usa para realizar la ordenación. Dejar en blanco si el usuario no puede ordenar esta columna.
  • Orden: el criterio de ordenación de los datos de columnas.
  • Cambio de tamaño: selecciona el tipo de cambio de tamaño de la columna.
  • Editable: si es true, el usuario puede editar las celdas en una tabla basada en celdas.
  • Oculta: si es true, la columna está oculta.

También puede cambiar el tamaño de la columna arrastrando el control (centrado verticalmente en el lado derecho de la columna) hacia la izquierda o la derecha.

Vamos a seleccionar cada columna de la vista de tabla y asignar a la primera columna el títuloProduct y a la segunda Details.

Seleccione una vista de celda de tabla (NSTableViewCell) en la jerarquía de la interfaz: las siguientes propiedades estarán disponibles en el Inspector de atributos:

La captura de pantalla muestra las propiedades disponibles para una vista de celda de tabla en el inspector de atributos.

Estas son todas las propiedades de una vista estándar. Aquí también tiene la opción de cambiar el tamaño de las filas de esta columna.

Seleccione una vista de celda de tabla (de forma predeterminadas, es un NSTextField) en la jerarquía de la interfaz: las siguientes propiedades estarán disponibles en el Inspector de atributos:

La captura de pantalla muestra las propiedades disponibles para una celda de la vista tabla en el Inspector de atributos.

Aquí podrá establecer todas las propiedades de un campo de texto estándar. De forma predeterminada, se usa un campo de texto estándar para mostrar los datos de una celda de una columna.

Seleccione una vista de celda de tabla (NSTableFieldCell) en la jerarquía de la interfaz: las siguientes propiedades estarán disponibles en el Inspector de atributos:

La captura de pantalla muestra las propiedades disponibles para una celda de vista de tabla diferente en el inspector de atributos.

Las opciones más importantes son:

  • Diseño: seleccione el diseño que tendrán las celdas de esta columna.
  • Usa el modo de línea única: si es true, la celda se limita a una sola línea.
  • Ancho del diseño en la primera ejecución: si es true, la celda se ajusta al ancho establecido (ya sea manual o automáticamente) la primera vez que se ejecuta la aplicación.
  • Acción: controla cuando se envía la acción de edición de la celda.
  • Comportamiento: define si una celda es seleccionable o editable.
  • Texto enriquecido: si es true, la celda puede mostrar texto con formato y estilo.
  • Deshacer: si es true, la celda asume la responsabilidad del comportamiento de deshacer.

Seleccione la vista de celda de tabla (NSTableFieldCell) en la parte inferior de una columna de tabla en la jerarquía de la interfaz:

Selección de la vista celda de tabla

Esto le permite editar la vista celda de tabla usada como patrón base para todas las celdas creadas para la columna especificada.

Adición de acciones y salidas

Al igual que cualquier otro control de la interfaz de usuario de Cocoa, es necesario exponer la vista de tabla y sus columnas y celdas al código de C# mediante acciones y salidas (según la funcionalidad necesaria).

El proceso es el mismo para cualquier elemento de vista de tabla que deseemos exponer:

  1. Cambie al Editor del Asistente y asegúrese de que el archivo ViewController.h esté seleccionado:

    Editor del asistente

  2. Seleccione la vista de tabla en la jerarquía de la interfaz, haga clic manteniendo pulsada la tecla Control y arrástrela al archivo ViewController.h.

  3. Cree una salida para la vista de tabla denominada ProductTable:

    La captura de pantalla muestra una conexión de salida creada para la vista de tabla denominada ProductTable.

  4. Cree también salidas para las columnas de tabla y denomínelas ProductColumn y DetailsColumn:

    Captura de pantalla que muestra las conexiones de salida creadas para otras vistas de tabla.

  5. Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

A continuación, escribiremos el código para mostrar algunos datos de la tabla cuando se ejecute la aplicación.

Rellenar la vista de tabla

Con la vista de tabla diseñada en Interface Builder y expuesta a través de una salida, tenemos que crear el código de C# para rellenarla.

En primer lugar, vamos a crear una nueva clase Product para almacenar la información de las filas individuales. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar>Nuevo archivo.... Seleccione General>Clase vacía, escriba Product para el Nombre y haga clic en el botón Nuevo:

Creación de una clase vacía

Edite el archivo Product.cs para que quede de la siguiente manera:

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

A continuación, es necesario crear una subclase de NSTableDataSource para proporcionar los datos de la tabla tal como se solicita. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar>Nuevo archivo.... Seleccione General>Clase vacía, escriba ProductTableDataSource para el Nombre y haga clic en el botón Nuevo.

Edite el archivo ProductTableDataSource.cs para que quede de la siguiente manera:

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

Esta clase tiene almacenamiento para los elementos de la vista de tabla e invalida GetRowCount para devolver el número de filas de la tabla.

Por último, es necesario crear una subclase de NSTableDelegate para proporcionar el comportamiento de la tabla. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Agregar>Nuevo archivo.... Seleccione General>Clase vacía, escriba ProductTableDelegate para el Nombre y haga clic en el botón Nuevo.

Edite el archivo ProductTableDelegate.cs para que quede de la siguiente manera:

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

Cuando creamos una instancia de ProductTableDelegate, también pasamos una instancia de ProductTableDataSource que proporciona los datos de la tabla. El método GetViewForItem es responsable de devolver una vista (datos) para mostrar la celda de una columna y una fila determinadas. Si es posible, se reutilizará una vista existente para mostrar la celda. De lo contrario, se debe crear una nueva vista.

Para rellenar la tabla, vamos a editar el archivo ViewController.cs y modificar el AwakeFromNib para que quede de la siguiente manera:

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

Si ejecutamos la aplicación, se muestra lo siguiente:

Captura de pantalla que muestra una ventana denominada Tabla de productos con tres entradas.

Ordenación por columna

Vamos a permitir que el usuario ordene los datos de la tabla haciendo clic en un encabezado de columna. En primer lugar, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Seleccione la columna Product, escriba Title para la Clave de ordenación y compare: para el Selector y seleccione Ascending para el Orden:

La captura de pantalla muestra el Interface Builder donde puede establecer la clave de ordenación para la columna Producto.

Seleccione la columna Details, escriba Description para la Clave de ordenación y compare: para el Selector y seleccione Ascending para el Orden:

La captura de pantalla muestra el Interface Builder donde puede establecer la clave de ordenación para la columna Detalles.

Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

Ahora vamos a editar el archivo ProductTableDataSource.cs y agregar los métodos siguientes:

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

El método Sort nos permite ordenar los datos en el origen de datos en función de un campo de clase de Product determinado en orden ascendente o descendente. Se llamará al método SortDescriptorsChanged invalidado cada vez que el usuario haga clic en un encabezado de columna. Se pasará el valor de Clave establecido en Interface Builder y el criterio de ordenación de esa columna.

Si ejecutamos la aplicación y hacemos clic en un encabezado de columna, las filas se ordenarán por esa columna:

Una ejecución de aplicación de ejemplo

Selección de fila

Si quiere permitir que el usuario seleccione una sola fila, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Seleccione la vista de tabla en la jerarquía de la interfaz y desactive la casilla Múltiple en el Inspector de atributos:

La captura de pantalla muestra el Interface Builder donde puede seleccionar Varios en el Inspector de atributos.

Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

A continuación, edite el archivo ProductTableDelegate.cs y agregue el método siguiente:

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

Esto permitirá al usuario seleccionar cualquier fila individual en la vista de tabla. Vuelva false para cualquier ShouldSelectRow fila que no quiera que el usuario pueda seleccionar o false para cada fila si no desea que el usuario pueda seleccionar ninguna fila.

La vista de tabla (NSTableView) contiene los métodos siguientes para trabajar con la selección de filas:

  • DeselectRow(nint): anula la selección de una fila dada en la tabla.
  • SelectRow(nint,bool): selecciona una fila dada. Pase false para el segundo parámetro para seleccionar solo una fila cada vez.
  • SelectedRow: devuelve la fila actual seleccionada en la tabla.
  • IsRowSelected(nint): devuelve true si se selecciona la fila dada.

Selección de varias filas

Si quiere permitir que el usuario seleccione varias filas, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Seleccione la vista de tabla en la jerarquía de la interfaz y active la casilla Múltiple en el Inspector de atributos:

La captura de pantalla muestra el Interface Builder donde puede seleccionar Varios para permitir la selección de múltiples filas.

Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

A continuación, edite el archivo ProductTableDelegate.cs y agregue el método siguiente:

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

Esto permitirá al usuario seleccionar cualquier fila individual en la vista de tabla. Vuelva false para cualquier ShouldSelectRow fila que no quiera que el usuario pueda seleccionar o false para cada fila si no desea que el usuario pueda seleccionar ninguna fila.

La vista de tabla (NSTableView) contiene los métodos siguientes para trabajar con la selección de filas:

  • DeselectAll(NSObject): anula la selección de todas las filas de la tabla. Use this como primer parámetro para enviar el objeto que realiza la selección.
  • DeselectRow(nint): anula la selección de una fila dada en la tabla.
  • SelectAll(NSobject): selecciona todas las filas de la tabla. Use this como primer parámetro para enviar el objeto que realiza la selección.
  • SelectRow(nint,bool): selecciona una fila dada. Si se pasa false para el segundo parámetro se borra la selección y se selecciona solo una fila, si se pasa true se amplía la selección y se incluye esta fila.
  • SelectRows(NSIndexSet,bool): selecciona el conjunto de filas dado. Si se pasa false para el segundo parámetro se borra la selección y se seleccionan solo estas filas, si se pasa true se amplía la selección y se incluyen estas filas.
  • SelectedRow: devuelve la fila actual seleccionada en la tabla.
  • SelectedRows: devuelve un NSIndexSet que contiene los índices de las filas seleccionadas.
  • SelectedRowCount: devuelve el número de filas seleccionadas.
  • IsRowSelected(nint): devuelve true si se selecciona la fila dada.

Escribir para seleccionar fila

Si quiere que el usuario pueda escribir un carácter con la vista de tabla seleccionada y seleccionar la primera fila que contenga ese carácter, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Seleccione la vista de tabla en la jerarquía de la interfaz y active la casilla Escribir para seleccionar en el Inspector de atributos:

Establecimiento del tipo de selección

Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

Ahora vamos a editar el archivo ProductTableDelegate.cs y agregar el método siguiente:

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

El método GetNextTypeSelectMatch toma la searchString dada y devuelve la fila del primer Product que tenga esa cadena en su Title.

Si ejecutamos la aplicación y escribimos un carácter, se selecciona una fila:

Captura de pantalla que muestra el resultado de ejecutar la aplicación.

Reordenación de columnas

Si quiere permitir que el usuario arrastre las columnas para reordenarlas en la vista de tabla, haga doble clic en el archivo Main.storyboard para abrirlo y editarlo en Interface Builder. Seleccione la vista de tabla en la jerarquía de la interfaz y active la casilla Reordenar en el Inspector de atributos:

La captura de pantalla muestra el Interface Builder donde puede seleccionar Reordenar en el Inspector de atributos.

Si se proporciona un valor para la propiedad Guardar automáticamente y se activa el campo Información de columna, los cambios realizados en el diseño de la tabla se guardarán automáticamente y se restaurarán la próxima vez que se ejecute la aplicación.

Guarde los cambios y vuelva a Visual Studio para Mac para sincronizarlo con Xcode.

Ahora vamos a editar el archivo ProductTableDelegate.cs y agregar el método siguiente:

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

El método ShouldReorder debe devolver true para cualquier columna que se pueda arrastrar para reordenar en newColumnIndex, en caso contrario devolverá false.

Si ejecutamos la aplicación, podemos arrastrar los encabezados de columna para reordenar las columnas:

Ejemplo de las columnas reordenadas

Edición de celdas

Si quiere permitir que el usuario edite los valores de una celda determinada, edite el archivo ProductTableDelegate.cs y cambie el método GetViewForItem de la siguiente manera:

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

Ahora, si ejecutamos la aplicación, el usuario puede editar las celdas en la vista de tabla:

Ejemplo de edición de una celda

Uso de imágenes en vistas de tabla

Para incluir una imagen como parte de la celda en una NSTableView, tendrá que cambiar la forma en que el método de NSTableViewDelegate'sGetViewForItem de la vista de tabla devuelve los datos para usar NSTableCellView en lugar del habitual NSTextField. Por ejemplo:

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

Para obtener más información, consulte la sección Uso de imágenes con vistas de tabla de la documentación de trabajo con imágenes.

Adición de un botón Eliminar a una fila

Según los requisitos de la aplicación, puede haber ocasiones en las que necesite proporcionar un botón de acción para cada fila de la tabla. Como ejemplo, vamos a ampliar el ejemplo de vista de tabla creado anteriormente para incluir un botón Eliminar en cada fila.

En primer lugar, edite Main.storyboard en Interface Builder de Xcode, seleccione la vista de tabla y aumente el número de columnas a tres (3). A continuación, cambie el Título de la nueva columna a Action:

Editar el nombre de columna

Guarde los cambios en el guion gráfico y vuelva a Visual Studio para Mac para sincronizar los cambios.

A continuación, edite el archivo ViewController.cs y agregue el siguiente método público:

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

En el mismo archivo, modifique la creación del nuevo delegado de vista de tabla dentro del método ViewDidLoad de la siguiente manera:

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

Ahora, edite el archivo ProductTableDelegate.cs para incluir una conexión privada al controlador de vista y tomar el controlador como parámetro al crear una nueva instancia del delegado:

#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

A continuación, agregue el siguiente nuevo método privado a la clase:

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

Esto toma todas las configuraciones de vista de texto que se estaban realizando anteriormente en el método GetViewForItem y las coloca en una sola ubicación invocable (ya que la última columna de la tabla no incluye una vista de texto, sino un botón).

Por último, edite el método GetViewForItem para que quede de la siguiente manera:

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

Veamos con más detalle varias secciones de este código. En primer lugar, si se crea una nueva NSTableViewCell se realiza una acción en función del nombre de la columna. Para las dos primeras columnas (Product y Details), se llama al nuevo método ConfigureTextField.

Para la columna Action, se crea un nuevo NSButton y se agrega a la celda como una vista secundaria:

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

La propiedad Tag del botón se usa para guardar el número de la fila que se está procesando actualmente. Este número se usará más adelante cuando el usuario solicite que se elimine una fila en el evento Activated del botón:

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

Al inicio del controlador de eventos, obtenemos el botón y el producto que se encuentra en la fila de tabla dada. A continuación, se presenta una alerta al usuario que confirma la eliminación de la fila. Si el usuario elige eliminar la fila, se quita la fila dada del origen de datos y se vuelve a cargar la tabla:

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

Por último, si se vuelve a usar la celda de vista de tabla en lugar de crear una nueva, el código siguiente lo configura en función de la columna que se está procesando:

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

Para la columna Action, se examinan todas las vistas secundarias hasta encontrar NSButton y, luego, se actualiza la propiedad Tag para que apunte a la fila actual.

Con estos cambios implementados, cada fila tendrá un botón Eliminar cuando se ejecute la aplicación:

Vista de tabla con botones de eliminación

Cuando el usuario haga clic en un botón Eliminar, se mostrará una alerta en la que se le preguntará si desea eliminar la fila en cuestión:

Una alerta de eliminación de filas

Si el usuario elige eliminarla, se quitará la fila y se volverá a dibujar la tabla:

La tabla después de eliminar la fila

Vistas de tabla de enlace de datos

Mediante el uso de técnicas de codificación de clave-valor y enlace de datos en la aplicación de Xamarin.Mac, puede reducir considerablemente la cantidad de código que tiene que escribir y mantener para rellenar y trabajar con elementos de la interfaz de usuario. También tiene la ventaja de poder desacoplar aún más los datos de respaldo (Modelo de datos) de la interfaz de usuario de front-end (Modelo-Vista-Controlador), lo que facilita el mantenimiento y hace que el diseño de las aplicaciones sea más flexible.

La codificación de clave-valor (KVC) es un mecanismo para acceder indirectamente a las propiedades de un objeto mediante claves (cadenas con formato especial) para identificar propiedades en lugar de acceder a ellas a través de variables de instancia o métodos de descriptor de acceso (get/set). Al implementar descriptores de acceso compatibles con la codificación de clave-valor en la aplicación de Xamarin.Mac, obtendrá acceso a otras características de macOS, como la observación de clave-valor (KVO), el enlace de datos, Core Data, enlaces de Cocoa y la capacidad de ejecución mediante scripts.

Para obtener más información, consulte la sección Enlace de datos de vista de tabla de nuestra documentación sobre el enlace de datos y la codificación de clave-valor.

Resumen

En este artículo se trata en detalle cómo trabajar con vistas de tabla en una aplicación de Xamarin.Mac. Se explican los distintos tipos y usos de las vistas de tabla, cómo crear y mantener vistas de tabla en Interface Builder de Xcode y cómo trabajar con vistas de tabla en código de C#.