Teknisk artikel

HotXLS: protection, page setup, and printing in Delphi

Tre grupper av kalkylbladsinställningar har ingenting att göra med cellvärdena och allt att göra med hur filen beter sig när den lämnar din kod. Bladskydd avgör vilka celler en användare kan redigera efter att du har lämnat över arbetsboken. Sidinställningar fastställer orientering, pappersstorlek och marginaler. Utskriftsinställningarna (upprepade rubrikrader, skalning och manuella sidbrytningar) styr hur ett rutnät av godtycklig längd landar på papper. Ingen av de tre syns när du ögnar igenom datan i en visare, och alla tre går sönder tyst ute i fält när de är fel. HotXLS, ett nativt kalkylbibliotek för Delphi och C++Builder, exponerar hela ytan för .xls och .xlsx, vilket innebär att det också återger varje kontraintuitiv Excel-regel inbakad i den ytan

Den första av dessa regler snubblar nästan alla på första gången de skyddar ett genererat blad. Anropa Protect och plötsligt kan ingen skriva i någon cell alls, inklusive de indatakolumner som du byggde arbetsboken kring. Inget i din kod rörde de kolumnerna, och det är precis därför det händer

Varje cell föds låst

ECMA-376 definierar locked som en del av en cells formateringspost, inte som en egenskap hos skyddet i sig, och den är som standard sann. Bladskydd är bara omkopplaren som gör flaggan verkställbar. Så hela rutnätet bär en låsflagga från det ögonblick det finns, vilande, och anropet till Protect aktiverar dem alla på en gång. Lösningen är att sätta ordningen medvetet: bygg layouten, lås upp explicit de områden användarna måste redigera, och skydda sist

Diagram över HotXLS skyddsordningen i Delphi där varje cell föds med locked true, indataintervall låses upp med SetLocked först, och Sheet.Protect anropad sist håller de upplåsta cellerna redigerbara
Celler anländer låsta som standard, så lås upp inmatningsintervallen först och anropa Protect sist för att hålla dem redigerbara
Book := TXLSXWorkbook.Create;
try
  Sheet := Book.Sheets.Add('Timesheet');
  // ... rubrikrad, namnkolumn och avgiftsformler skrivs här ...
  Sheet.Range['B2:B50'].SetLocked(False);         // personal skriver timmar här
  Sheet.Range['F2:F50'].SetFormulaHidden(True);   // håll avgiftsberäkningen privat
  Sheet.Protect('review-2026');                   // nu biter låsflaggorna
  Book.SaveAs('timesheet.xlsx');
finally
  Book.Free;
end;

SetFormulaHidden gör något separat och lätt att missa: medan skyddet är aktivt visar cellen fortfarande sitt beräknade värde, men formelfältet visar ingenting. Det spelar roll när en formel bäddar in faktureringsräntor, marginaler eller poängviktningar som du hellre inte vill lämna ut till varje mottagare som klickar på en summa. På XLS-fasaden uttrycks samma avsikt per område via IXLSRange.Locked och FormulaHidden. Kalkylbladet där bär också femton Allow*-flaggor (AllowSort, AllowAutoFilter, AllowFormatCells, och resten), så att ett skyddat blad ändå kan sorteras och filtreras i stället för att frysas till ett förseglat utställningsobjekt

Vad skyddslösenordet faktiskt skyddar

Båda formaten lagrar blad- och arbetsboksskyddslösenordet som en äldre hash på 4 hexsiffror. Sexton bitar innebär att otaliga strängar kolliderar med vilket givet lösenord som helst, och borttagningsverktyg är en sökning bort. Behandla skydd som ett säkerhetsbälte mot oavsiktliga redigeringar, inte som åtkomstkontroll. Det är rätt verktyg för att hindra granskare från att skriva över formelkolumnen och fel verktyg för allt som involverar ordet konfidentiellt

Ett steg upp låser ProtectWorkbook på XLSX-fasaden arbetsbokens struktur, vilket förhindrar att lägga till, byta namn på, ta bort eller ändra ordning på blad. Sätt den när bladlistan i sig är ett kontrakt med en nedströmsparser som indexerar blad efter namn eller position. Ett omdöpt blad förstör importen på andra sidan precis lika säkert som en borttagen kolumn gör. XLS-fasaden speglar lagerindelningen med TXLSWorkbook.Protect på arbetsboksnivå och per-blad-anrop av Protect, plus en egenskap isProtected för kod som behöver inspektera en ärvd fil innan den ändrar något

När kravet är verklig konfidentialitet ändras mekanismen helt. SaveAsEncrypted producerar ett AES-krypterat paket enligt ECMA-376:s Standard Encryption-schema, som behandlas ingående i guiden om AES-skyddad XLSX-utdata, och den äldre XLS-fasaden skriver och läser RC4-krypterade .xls-filer via EncryptionPassword och lösenordsöverlagringen av Open. Skillnaden är inte akademisk. Ett skyddat blad färdas i klartext, så vilket zip-verktyg som helst kan läsa dess cellvärden, medan ett krypterat paket är oläsbart utan lösenordet. En revisionsrad som säger "lönefilen måste skyddas" betyder nästan alltid kryptering, oavsett vilket ordval den råkar använda

Diagram som kontrasterar HotXLS bladskydd, som lagrar en 16-bitars äldre hash och lämnar cellvärden i klartext läsbara av vilket zip-verktyg som helst, med SaveAsEncrypted AES-utdata som förblir oläsbar utan lösenordet
Bladskydd är ett säkerhetsbälte mot oavsiktliga redigeringar medan klartextvärden förblir läsbara, och endast AES-kryptering döljer innehållet

Sidinställningar är en del av dokumentkontraktet

Utskriftsbeteende är osynligt på skärmen, vilket är varför det så ofta levereras trasigt. I samma stund en kund skriver ut arbetsboken, eller exporterar den till PDF åt en revisor, förvandlas marginaler, skalning och upprepade rubriker till funktionella krav som ingen testade. På XLSX-fasaden hänger dessa inställningar direkt på kalkylbladet:

Sheet.PageLandscape := True;
Sheet.PaperSize := xlsxPaperA4;
Sheet.SetPageMargins(0.5, 0.5, 0.75, 0.75, 0.3, 0.3);
Sheet.CenterHeader := 'Monthly Timesheet';
Sheet.RightFooter := 'Page &P of &N';
Sheet.PrintArea := '$A$1:$F$60';     // ren referens: inget bladnamn här
Sheet.PrintTitleRows := '$1:$1';     // rubrikraden upprepas på varje sida
Sheet.FitToWidth := 1;
Sheet.FitToHeight := 0;              // växer nedåt allteftersom datan växer
Sheet.PrintGridlines := False;

Två av de raderna döljer fällor. Rubrik- och sidfotssträngarna använder Excels formateringskoder: &P för aktuell sida, &N för totalantalet, med &L, &C och &R för att adressera de tre sektionerna explicit. Den andra fällan är PrintArea, som avsiktligt tar en ren cellreferens. HotXLS lagrar den okvalificerad och lägger till bladnamnet som prefix när den skriver filen, så om du själv skickar in 'Timesheet!$A$1:$F$60' uppstår en dubbelt kvalificerad, felformad referens. Samma försiktighet gäller ett lager djupare: utskriftsområden och utskriftsrubriker sparas som de inbyggda definierade namnen _xlnm.Print_Area och _xlnm.Print_Titles, så lägg aldrig till _xlnm.*-poster via DefinedNames för hand, annars kommer de två mekanismerna att slåss om samma plats

Skalning som överlever produktionens datavolymer

Kombinationen FitToWidth := 1 med FitToHeight := 0 läses som "passa alltid kolumnerna över en sida, ta sedan så många sidor nedåt som datan behöver," och det är standardvalet för alla rapporter vars radantal varierar. Fällan är att finjustera en fast procentsats eller ett fit-to-page-par mot en testfil med trettio rader: mata samma inställningar med sexhundra produktionsrader och utdatan exploderar antingen i dussintals beskurna sidor eller krymper under läsbarhetsgränsen. Skala bredden, låt längden växa, och upprepa rubrikraden via PrintTitleRows så att sidan sjutton fortfarande är läsbar på egen hand

Diagram över HotXLS utskriftsskalning i Delphi med FitToWidth satt till 1 så att varje sida förblir ett blad bred, FitToHeight satt till 0 så att sidor växer nedåt, PrintTitleRows som upprepar rubrikbandet, och sidbrytningar som regenereras efter ClearAllPageBreaks
FitToWidth 1 med FitToHeight 0 håller varje sida ett ark bred medan upprepade rubrikrader och omgenererade sidbrytningar bevarar läsbarheten

Manuella brytningar följer samma regenereringsdisciplin som allt annat i en genererad arbetsbok. AddRowBreak(BeforeRow) startar en ny sida före en sektionsgräns, men när generatorn körs igen och raderna förskjuts hamnar en föråldrad brytning mitt i tabellen. Anropa ClearAllPageBreaks först, lägg sedan till brytningar beräknade från generatorns egna radräknare i stället för att lappa gamla positioner. På XLS-fasaden bor de motsvarande kontrollerna på Sheet.PageSetup (orientering, pappersstorlek, marginaler, rubrik- och sidfotssträngar, fit-to-pages), med RepeatRows och RepeatColumns som täcker utskriftsrubrikerna

Kontrollera resultatet innan en kund gör det

Skydds- och utskriftsbuggar delar en egenskap: de är triviala att verifiera för hand och verifieras nästan aldrig. Öppna den genererade filen i Excel och lägg nittio sekunder på den. Skriv i en indatacell och bekräfta att den accepterar knapptryckningen; skriv i en låst cell och bekräfta att skyddsprompten visas; kontrollera att en dold formel lämnar formelfältet tomt. Kör sedan Förhandsgranskning mot en produktionsstor datamängd, inte ett trettioradsurval, och läs av sidantalet, den upprepande rubrikraden och sidfotens numrering. Förhandsgranskningen är steget som betalar sig självt, eftersom utskriftsgeometrin beror på inställningar utan skärmrendering, och förutom en fysisk skrivare är det den enda platsen där ett skalningsfel någonsin blir synligt

En sista inställning avrundar granskningen. FreezePane(ACol, ARow) håller rubrikblocket synligt medan en granskare rullar. Det är skärmbeteende snarare än utskriftsbeteende, men en granskare bedömer hela leveransen på en gång. Och en arbetsbok som börjar sitt liv som en designerunderhållen layout får det mesta av detta gratis: arbetsflödet för mallbaserad rapportgenerering håller sidinställningarna i mallen, där en människa har finjusterat dem mot en verklig skrivare, och lämnar åt koden att fylla i datan och återapplicera skyddet när layouten har satt sig

HotXLS är ett nativt Object Pascal-kalkylbibliotek för Delphi och C++Builder; den fullständiga API-referensen för skydd och sidinställningar finns på produktsidan för HotXLS Delphi Component