Artículo técnico

Duplica una hoja de cálculo XLSX en Delphi con HotXLS

Ya construiste una hoja exactamente como debía quedar. La banda de encabezado está combinada, los anchos de columna encajan con los datos, las dos primeras filas están congeladas, el área de impresión y los márgenes están listos para una exportación A4 limpia, y la pestaña tiene color para que finanzas la encuentre. Ahora el informe necesita doce de estas, una por región, cada una partiendo del mismo diseño. Volver a construir esa hoja doce veces en código es la forma en que entra una deriva sutil: la región 7 queda con una columna un punto más angosta, la región 11 pierde el congelamiento y nadie lo nota hasta que el PDF llega al escritorio de un gerente. Lo que en realidad quieres es la versión programática del clic derecho de Excel, Mover o copiar, Crear una copia: tomar la hoja terminada y sacar duplicados independientes

El motor XLSX de HotXLS, una biblioteca nativa de Delphi y C++Builder que lee y escribe archivos de Excel sin automatizar Excel mismo, ya podía mover hojas, borrar hojas y copiar rangos de celdas entre hojas. Lo que no podía hacer hasta v2.91.0 era clonar una hoja completa en una sola llamada. Esa versión agrega dos puntos de entrada: TXLSXWorksheet.CopyFrom, que copia el estado de nivel de hoja de una hoja de trabajo a otra, y TXLSXSheets.Duplicate, que agrega una hoja nueva y ejecuta CopyFrom por ti. Lo interesante no es que copie cosas. Es la línea deliberada entre lo que se copia en profundidad y lo que no, y por qué esa línea está donde está

Una sola llamada para clonar una hoja terminada

La operación de alto nivel es Duplicate. Pásale el índice basado en 1 de la hoja origen y devuelve una hoja de cálculo completamente nueva que refleja el diseño y los datos del original. La convención de índices coincide con Items[] del lado XLSX, así que la hoja uno usa el índice 1, no 0; si pasas un índice fuera de rango obtienes nil en lugar de una excepción, el mismo contrato de error que usa el resto de la colección de hojas XLSX

var
  Book: TXLSXWorkbook;
  Template, Copy: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Template := Book.Sheets.Add('Template');
    Template.Cells[1, 1].Value := 'Quarterly Statement';
    Template.Range['A1:C1'].Merge;
    Template.ColWidth[1] := 18;
    Template.FreezePanes(2, 1);          // freeze top row + first column
    Template.TabColorIsAuto := False;
    Template.TabColor := $FF1F4E79;

    // Clone with an explicit name...
    Copy := Book.Sheets.Duplicate(1, 'Region-North');
    // ...or let it pick the Excel-style default name.
    Copy := Book.Sheets.Duplicate(1);    // -> "Template (2)"

    Book.SaveAs('regions.xlsx');
  finally
    Book.Free;
  end;
end;

Dos cosas de ese fragmento merecen atención. Primero, FreezePanes toma sus argumentos fila primero, FreezePanes(ARow, ACol), así que coincide con el indexado de Cells[Row, Col]; la copia hereda exactamente la misma división de congelamiento. Segundo, el método se llama Duplicate y no el más obvio Copy, y eso no es una preferencia de estilo. Copy es una rutina estándar de la unidad System, usada constantemente para cadenas y arreglos dinámicos. Un método llamado Copy en una clase lo ocultaría dentro de los cuerpos de método y crearía exactamente el tipo de ambigüedad de resolución que muerde seis meses después. Duplicate evita por completo ese problema y se lee bien en el punto de llamada

El nombre predeterminado sigue la regla de Excel

Cuando llamas a la sobrecarga de un argumento, o pasas una cadena de nombre vacía, la nueva hoja se nombra a partir de la original con el sufijo (2), y el sufijo aumenta hasta que el nombre es único. Duplica la hoja Template una vez y obtienes Template (2); vuelve a duplicarla y obtienes Template (3), porque Template (2) ya está ocupado. Esto refleja los nombres que Excel genera con su propio comando Crear una copia, así que un libro que tu código produce se ve como el usuario esperaría que se viera uno duplicado a mano. La comprobación de unicidad se hace contra la colección de hojas en vivo, lo que significa que también salta nombres creados manualmente, no solo los de duplicaciones anteriores

Si vas a generar una hoja por región o por mes, mejor usa la sobrecarga con nombre explícito. Un esquema predecible Region-North, Region-South es más fácil de direccionar después que una serie de sufijos (2), (3), y mantiene legibles tus nombres definidos y fórmulas entre hojas

Qué copia en profundidad CopyFrom

Por debajo, Duplicate agrega la hoja y luego llama a CopyFrom(ASource), que también puedes llamar directamente cuando quieres clonar sobre una hoja que ya creaste. CopyFrom protege de entrada contra los dos casos degenerados: copiar desde nil o copiar una hoja sobre sí misma, ambos regresan de inmediato y no hacen nada. Todo lo demás es la copia en sí, y es deliberadamente amplia

Primero van los datos de celdas. CopyFrom le pide a la fuente su UsedRange, el cuadro delimitador ajustado de celdas pobladas y regiones combinadas, y reutiliza la maquinaria existente de CopyRangeTo para llevar cada valor, fórmula e índice de estilo por celda al destino empezando en A1. Encima de las celdas, reproduce toda la capa de estado de nivel de hoja que hace que una plantilla se vea terminada:

  • Rangos combinados, recreados por coordenadas para que el banner abarque el mismo rectángulo
  • Anchos de columna y altos de fila, más las listas ocultas, contraídas y de nivel de esquema, copiados tal cual para que filas y columnas no predeterminadas queden alineadas exactamente
  • Panes congelados y el estado de vista: nivel de zoom, muestra de líneas de cuadrícula y de valores cero, dirección de derecha a izquierda y tipo de vista
  • Estado de protección con sus opciones de permiso por acción, para que una plantilla bloqueada siga bloqueada del mismo modo
  • Todo el bloque de configuración de página: márgenes, orientación, tamaño de papel, escala y ajuste a la página, área de impresión, títulos de impresión, encabezados y pies de página, y los indicadores de líneas de cuadrícula y encabezados impresos
  • El rango de AutoFilter, el color de la pestaña y la visibilidad de la hoja

El resultado es una hoja que imprime, filtra y se presenta idéntica a su origen. Y como las celdas, los combinados y las listas de dimensiones se vuelven a crear físicamente en la nueva hoja en vez de quedar aliasadas, la copia es totalmente independiente. Escribe 999 en una celda de la copia y la fuente conserva su valor original; esa independencia es la propiedad más importante de un clon pensado para reportes regionales en paralelo, y la demo SheetCopy incluida lo comprueba explícitamente

Qué deja en copia superficial y por qué

Ahora la parte honesta. Los gráficos, las imágenes incrustadas, las tablas XLSX, las validaciones de datos y las reglas de formato condicional no se copian. Este es un límite documentado y deliberado, no una omisión, y vale la pena entender el motivo para que puedas planear alrededor de él en lugar de sorprenderte

Cada una de esas colecciones carga identidad y referencias que no sobreviven a una copia de campo ingenua. Un gráfico apunta a un rango de datos fuente y posee una relación de dibujo en el paquete OOXML; clonar el objeto sin reasignar la relación y las referencias de serie produce un gráfico que renderiza sobre los datos equivocados, o un paquete que Excel marca como necesitado de reparación. Una tabla tiene un nombre que debe ser único dentro del libro, una fila de encabezado ligada a columnas específicas y su propia relación generada automáticamente. Los formatos condicionales y las validaciones de datos se adjuntan a rangos de coordenadas y, en el caso de la validación, pueden referenciar otros rangos mediante fórmulas. Copiar en profundidad cualquiera de estas cosas correctamente implica reescribir referencias y generar identidades nuevas, lo cual es trabajo real con modos de falla reales. Hacerlo a medias, copiando el objeto pero no sus referencias, es peor que no copiar nada: genera un archivo que se abre con un aviso de reparación y descarta contenido sin decirlo. Así que el motor copia lo que puede copiar limpiamente y deja las colecciones con referencias al llamador, que sabe a qué debe apuntar el destino

En la práctica, eso significa que el flujo para una plantilla más rica es: duplica la hoja para obtener las celdas, el diseño y la configuración de impresión, y luego reconstruye el gráfico, la tabla, las validaciones o los formatos condicionales en la copia con la misma API que usaste para crearlos la primera vez. Como los recreas contra los rangos propios de la copia, las referencias quedan correctas por construcción. Para un gráfico que lee A1:C10, agrega un nuevo gráfico en la copia apuntando al A1:C10 de la copia; para un AutoFilter que quieras mantener vivo, nota que el rango sí se conserva, así que solo vuelves a aplicar los criterios de columna. Las reglas de formato condicional y validación de datos que volverías a agregar con las mismas llamadas descritas en el artículo sobre celdas combinadas y diseño de plantillas de informe, que recorre la tabla de combinaciones y el modelo de rangos que hereda la copia

Dónde encaja la duplicación en un flujo de reportes

La duplicación de hojas es el compañero natural de la generación basada en marcadores de posición. El enfoque anclado en tokens de la guía de generación de reportes basada en plantillas en Delphi resuelve el problema de escribir datos en un diseño que otras personas editan; la duplicación resuelve el problema de necesitar ese diseño muchas veces en un mismo libro. Combina ambos y el patrón queda limpio: conserva una sola hoja Template impecable con sus tokens, combinados y configuración de impresión, luego para cada región o periodo llama a Duplicate, llena los tokens del clon con ese bloque de datos y sigue adelante. La plantilla impecable nunca se modifica, así que sigue siendo una fuente confiable para el siguiente clon, y cada hoja de salida empieza con un diseño idéntico byte por byte

Una nota de secuencia evita una clase de confusión. Duplica la hoja antes de volcar datos en ella, no después. Una plantilla debe contener estructura y formato, no los números del trimestre pasado, y clonar una hoja estilizada vacía significa que cada duplicado empieza limpio. Si duplicas una hoja que ya trae datos, esos datos viajan con ella, porque CopyFrom copia fielmente el rango usado; a veces eso es lo que quieres, pero para un informe distribuido normalmente no lo es

Un hábito rápido de verificación

Como la división entre copia profunda y copia superficial es invisible hasta que la buscas, conviene incorporar una comprobación de cinco líneas al trabajo en lugar de confiar en que todo se copió. Después de duplicar, vuelve a leer las señales estructurales que la copia debe heredar y afirma que coinciden con la fuente

Copy := Book.Sheets.Duplicate(1, 'Region-North');
WriteLn(Format('merged=%d  colA=%.1f  freezeRow=%d  tabAuto=%d',
  [Copy.MergedCells.Count, Copy.ColWidth[1],
   Copy.FreezeRow, Integer(Copy.TabColorIsAuto)]));
// Prove independence: mutate the copy, confirm the source is untouched.
Copy.Cells[2, 2].Value := 999;
// Template.Cells[2, 2].Value is still whatever it was.

El conteo de combinados, el ancho de una columna, la fila congelada y la marca de color de pestaña te dicen que la capa que se copia llegó bien. Por separado, en cualquier hoja que traía un gráfico, una tabla, validaciones o formato condicional, trátalas como una lista por reconstruir en la copia: su ausencia es intencional y la corrección son unas cuantas llamadas, no un reporte de error. Ese modelo mental, profundo donde es seguro y superficial donde las referencias se romperían, es la historia completa de cómo usar bien esta función

La duplicación de hojas y la copia de estado de hoja CopyFrom descritas aquí se incluyen en la v2.91.0 del componente nativo de hojas de cálculo HotXLS Delphi spreadsheet component, junto con una muestra ejecutable SheetCopy que ejercita el ciclo de clonar y modificar de principio a fin