La regla estaba ahí. Excel la mostraba en Administrar reglas, el rango era correcto, la fórmula era correcta, y aun así no se resaltaba ni una sola celda. Este es el primer contacto más común con el formato condicional programático: una regla cellIs describe una condición, pero el resaltado viene de un registro de estilo diferencial separado, y una regla escrita sin ese registro evalúa felizmente a "true, no cambies nada". Al trabajar con HotXLS desde Delphi, donde ustedes ensamblan estas piezas en lugar de hacer clic por el cuadro de diálogo de Excel, esa separación entre condición y consecuencia es lo primero que hay que interiorizar.
HotXLS escribe formato condicional de forma nativa en archivos BIFF8 .xls y OOXML .xlsx, junto con runs de texto enriquecido y un modelo de estilos agrupados. Las tres funciones interactúan más de lo que sugiere la superficie de API, y este artículo mapea los puntos donde la salida se desvía de la intención.
Una condición necesita una consecuencia: el estilo dxf
En la hoja XLSX, las reglas de comparación se crean con AddConditionalFormat, que devuelve el índice de la nueva regla en la colección ConditionalFormats de la hoja. El objeto de regla devuelto lleva una propiedad Style: el formato diferencial (dxf en términos OOXML, según ECMA-376) que Excel aplica cuando se cumple la condición. Omitirlo produce la regla invisible del párrafo inicial.
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Idx: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('kpi.xlsx');
Sheet := Book.Sheets[0];
// Negative variance: light red fill
Idx := Sheet.AddConditionalFormat('D2:D200', xlsxCfOpLessThan, '0');
Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);
// Duplicate order IDs get flagged the same way
Idx := Sheet.AddCondFormatDuplicateValues('A2:A200');
Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFEB9C);
// Custom formula rule: highlight rows where actual misses 90% of target
Idx := Sheet.AddCondFormatExpression('B2:B200', '$C2<$B2*0.9');
Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);
Book.SaveAs('kpi-flagged.xlsx');
finally
Book.Free;
end;
end;
Los colores aquí son valores ARGB de 32 bits, así que $FFFFC7CE es el conocido "rojo claro" de Excel con un byte alfa completamente opaco al frente. La familia de reglas basadas en texto, como AddCondFormatContainsText, AddCondFormatBeginsWith, AddCondFormatEndsWith y similares, sigue el mismo patrón crear-y-luego-estilizar, igual que AddCondFormatTop10, AddCondFormatAboveAverage y los detectores de blancos y errores.
Las barras de datos, escalas de color y conjuntos de iconos se pintan solos
Los tipos de regla visuales son la imagen invertida: llevan su apariencia en la propia definición de la regla e ignoran por completo la propiedad Style. Asignar un relleno a una regla de barra de datos no hace nada, lo que parece un bug hasta que se conoce la taxonomía de reglas. AddCondFormatDataBar recibe directamente el color de la barra; las escalas de color de dos y tres puntos reciben sus colores de extremo; AddCondFormatIconSet elige uno de 26 tipos de conjuntos de iconos, como icsTrafficLights3.
Los parámetros interesantes en estas llamadas son los anclajes de valor, tipados como TXLSCfValueKind: un extremo de barra o escala puede ubicarse en el mínimo o máximo del rango, en un número literal, un porcentaje, un percentil o el resultado de una fórmula. Los anclajes predeterminados de mínimo y máximo del rango se ven bien en datos de demostración y luego engañan en datos de producción con valores atípicos: un valor extremo aplana todas las demás barras. Para tableros que se comparan entre periodos, fijen los extremos a números o percentiles estables para que "media barra" signifique lo mismo en marzo y abril.
El escritor heredado es un subconjunto, no un port
La fachada XLS puede crear exactamente cuatro formas de regla condicional: barras de datos, escalas de dos y tres colores y conjuntos de iconos, escritos como registros CF12 en el flujo BIFF8. No hay API de creación para reglas cellIs, de expresión o de texto de ese lado. Las reglas de esos tipos que ya existen en un archivo abierto se preservan y se escriben de vuelta sin cambios, así que hacer round-trip de un .xls de un cliente no daña su formato; pero un generador que necesita resaltado por umbrales en salida .xls debe simularlo aplicando rellenos ordinarios de celda desde código, o mover el entregable a .xlsx, donde está disponible toda la familia de reglas.
Vale la pena exponer esa asimetría temprano en un proyecto, porque cambia la decisión de formato de archivo para cualquier cosa con forma de tablero. Un equipo que se estandarizó en .xls por compatibilidad suele descubrir esta restricción en la tercera semana, después de construir la capa de datos.
Apilamiento de reglas, prioridad y rangos superpuestos
Los tableros reales rara vez tienen una sola regla por rango. Una columna de variación puede llevar una barra de datos para magnitud más una regla cellIs para el umbral duro, y una regla de expresión a nivel fila encima para escalaciones. Cada TXLSXConditionalFormat expone un valor Priority, y Excel evalúa reglas competidoras en orden de prioridad; por eso, cuando dos reglas quieren pintar la misma celda, el resultado queda definido por números que ustedes controlan, no por el orden en que un auditor llegue a leerlas en el cuadro de diálogo.
El enfoque mantenible es tratar las prioridades como z-order en un programa de dibujo: asígnenlas deliberadamente cuando dos reglas puedan tocar las mismas celdas, y dejen un espacio entre valores para poder insertar una regla más adelante sin renumerar todo. Donde las reglas realmente no interactúan, una barra de datos en la columna E, una regla de texto en la columna G, dejen que se mantenga el orden de creación y gasten el esfuerzo de revisión en los límites de rango. Los bugs costosos en esta área casi nunca son inversiones de prioridad; son rangos como B2:B200 en un reporte que ahora tiene 350 filas, donde la cola no cubierta se ve exactamente como una fila saludable. Deriven cada rango de regla de la variable de conteo final de filas, la misma disciplina que aplica a series de gráficos y rangos de validación en otras partes de la biblioteca.
Un hábito de verificación se paga solo: después de generar, abran el archivo en Excel, seleccionen el rango de regla y recorran Administrar reglas una vez por cada cambio de plantilla. El formato condicional es una de las pocas funciones donde el renderizador autoritativo es la aplicación consumidora, y una revisión visual de sesenta segundos detecta lo que ninguna prueba unitaria del XML verá.
Texto enriquecido: muchos formatos dentro de una celda
El texto enriquecido en el modelo XLSX es una lista de runs, cada uno con sus propios atributos de fuente, ensamblada aparte y luego adjuntada a una celda. La regla de propiedad es la parte que muerde: asignar a Cell.RichText transfiere la propiedad del objeto TXLSXRichText a la celda, que lo libera cuando la celda se destruye. Liberarlo ustedes después es una doble liberación que tal vez no falle hasta mucho más tarde, en código no relacionado.
var
Rich: TXLSXRichText;
Run: TXLSXRichTextRun;
begin
Rich := TXLSXRichText.Create;
Rich.AddRunText('Status: ');
Run := Rich.AddRunText('OVERDUE');
Run.Bold := True;
Run.Color := $FFC00000;
Run.ColorIsAuto := False;
Run := Rich.AddRunText(' — escalated to regional manager');
Run.Italic := True;
Sheet.Cells[2, 7].RichText := Rich; // ownership moves to the cell: do not Free
end;
Noten el ColorIsAuto := False explícito: una asignación de color de run solo tiene efecto después de desactivar la bandera de color automático, y olvidarlo produce un run en negrita pero obstinadamente negro. Los runs también admiten tachado, variantes de subrayado y alineación vertical para superíndice y subíndice, y PlainText aplana toda la lista a una cadena al exportar o comparar.
La fachada XLS no tiene API pública para escribir texto enriquecido de nivel celda: allí los runs están disponibles en comentarios y cuadros de texto mediante TextRuns, y las cadenas enriquecidas leídas desde un archivo existente sobreviven intactas a un round-trip. Igual que la brecha de formato condicional, esto empuja los reportes con formatos mixtos hacia el escritor XLSX.
El pool de estilos y el off-by-one que llega a producción
El estilo normal de celdas en el modelo XLSX pasa por colecciones agrupadas en el libro: Fonts.Add, Fills.AddSolid y Borders.Add devuelven cada uno un índice dentro de su pool. Esos índices devueltos son 0-based; las propiedades del lado celda, como FontIndex, tratan 0 como "predeterminado", así que el índice del pool debe incrementarse en uno al asignarlo:
HeaderFont := Book.Fonts.Add('Calibri', 11, True, False); // pool index, 0-based
for Col := 1 to 6 do
Sheet.Cells[1, Col].FontIndex := HeaderFont + 1; // cell index, 1-based
Olviden el + 1 y todos los encabezados se renderizarán en silencio con la fuente predeterminada: sin excepción, sin advertencia, solo un libro que parece no tener estilo. El error de segundo orden es llamar Fonts.Add dentro del bucle de filas: las definiciones de fuente idénticas se deduplican, pero crear entradas de pool por fila es trabajo desperdiciado, y algunos pools devuelven un objeto fresco en cada llamada. Construyan una vez el puñado de estilos antes del bucle y reutilicen los índices; en reportes de cientos de miles de filas, esta es una de las diferencias detalladas en la optimización de rendimiento para libros grandes en HotXLS. Para apariencias semánticas de una sola vez, ambas fachadas también ofrecen ApplyBuiltinStyle sobre rangos, mapeado a los estilos integrados de Excel Good, Bad, Neutral y accent sin tocar directamente los pools.
El formato condicional y el texto enriquecido suelen ser la última milla de un pipeline de reportes; el modelo de datos y las decisiones de diseño que los preceden se cubren en generación de reportes basada en plantillas con HotXLS. La referencia completa de reglas, runs y estilos está en la página de producto HotXLS Component.