בניתם גיליון אחד בדיוק כמו שצריך. רצועת הכותרת מוזגה, רוחבי העמודות מתאימים לנתונים, שתי השורות העליונות קפואות, אזור ההדפסה והשוליים הוגדרו ליצוא נקי ל-A4, והלשונית נצבעה כך שמחלקת הכספים תמצא אותה בקלות. עכשיו הדוח צריך תריסר כאלה, אחד לכל אזור, וכל אחד מתחיל מאותה פריסת בסיס. בנייה מחדש של אותו גיליון בקוד שתים עשרה פעמים מכניסה סטיות זעירות: לאזור 7 עמודה צרה יותר בנקודה אחת, לאזור 11 נעלמת ההקפאה, ואף אחד לא שם לב עד שה-PDF נוחת על שולחנו של מנהל. מה שבאמת צריך הוא הגרסה התכנותית של לחיצה ימנית ב-Excel, Move or Copy, Create a copy: לקחת את הגיליון המוגמר ולשכפל ממנו עותקים עצמאיים
מנוע ה-XLSX ב-HotXLS, ספריית Delphi ו-C++Builder מקורית שקוראת וכותבת קבצי Excel בלי לבצע אוטומציה של Excel עצמו, כבר ידע להעביר גיליונות, למחוק גיליונות ולהעתיק טווחי תאים בין גיליונות. מה שלא ידע עד v2.91.0 היה לשכפל גיליון שלם בקריאה אחת. המהדורה הזו מוסיפה שתי נקודות כניסה: TXLSXWorksheet.CopyFrom, שמעתיקה מצב ברמת גיליון מגיליון מקור אל אחר, ו-TXLSXSheets.Duplicate, שמוסיפה גיליון חדש ומריצה בשבילכם את CopyFrom. החלק המעניין הוא לא עצם ההעתקה, אלא הקו המכוון בין מה עובר העתקה עמוקה ומה לא, ולמה הקו הזה ממוקם דווקא שם
קריאה אחת לשכפול גיליון מוכן
הפעולה ברמה הגבוהה היא Duplicate. מוסרים לה את האינדקס מבוסס-1 של גיליון המקור, והיא מחזירה גיליון חדש לגמרי שמחקה את הפריסה והנתונים של המקור. מוסכמת האינדקסים תואמת ל-Items[] בצד ה-XLSX, כך שגיליון אחד הוא אינדקס 1 ולא 0; אם מעבירים אינדקס מחוץ לטווח, מקבלים nil ולא חריגה, אותו חוזה כשל שבו משתמשת שאר אוסף הגיליונות ב-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;
שני דברים בקטע הזה שווים עצירה. ראשית, FreezePanes מקבלת את הארגומנטים שלה לפי שורה תחילה, FreezePanes(ARow, ACol), כך שהיא מתיישרת עם האינדוקס Cells[Row, Col]; העותק יורש את פיצול ההקפאה המדויק. שנית, השם של המתודה הוא Duplicate ולא Copy הברור יותר, ולא מדובר בהעדפת סגנון. Copy היא שגרה סטנדרטית ב-System, שמשתמשים בה כל הזמן למחרוזות ולמערכים דינמיים. מתודה בשם Copy על מחלקה הייתה מצלה עליה בתוך גוף המתודה ויוצרת בדיוק את עמימות הרזולוציה שתופסת אותך כעבור חצי שנה. Duplicate עוקפת את כל הבעיה וגם נקראת נכון בנקודת הקריאה
שם ברירת המחדל עוקב אחר הכלל של Excel
כשקוראים לגרסת הארגומנט היחיד, או מעבירים מחרוזת שם ריקה, הגיליון החדש ייקרא על שם המקור עם הסיומת (2) והמספר יעלה עד שהשם יהיה ייחודי. שכפלו פעם אחת את הגיליון Template וקיבלתם Template (2); שכפלו שוב וקיבלתם Template (3), כי Template (2) כבר תפוס. זה מחקה את השמות ש-Excel מייצר מתוך הפקודה שלו Create a copy, כך שחוברת עבודה שהקוד שלכם מפיק נראית כמו משהו שמשתמש היה מצפה לראות אחרי שכפול ידני. בדיקת הייחודיות רצה מול אוסף הגיליונות החי, ולכן היא מדלגת גם על שמות שיצרתם ידנית, לא רק על שמות משכפולים קודמים
אם אתם מייצרים גיליון אחד לכל אזור או לחודש, עדיף להישען על השם המפורש. סכמת Region-North, Region-South צפויה וקלה יותר לטיפול בהמשך מאשר שרשרת סיומות (2), (3), והיא שומרת על שמות מוגדרים ונוסחאות חוצות-גיליונות קריאים
מה CopyFrom מעתיקה לעומק
מאחורי הקלעים, Duplicate מוסיפה את הגיליון ואז קוראת ל-CopyFrom(ASource), שאפשר גם לקרוא לו ישירות כשאתם רוצים לשכפל אל גיליון שכבר יצרתם. CopyFrom שומר מראש מפני שני מקרים מנוונים: העתקה מ-nil, או העתקת גיליון אל עצמו, ושניהם חוזרים מיד בלי לעשות דבר. כל מה שמגיע אחר כך הוא ההעתקה עצמה, והיא רחבה במכוון
נתוני התאים באים ראשונים. CopyFrom מבקשת מהמקור את UsedRange, תיבת התחימה ההדוקה של התאים המאוכלסים והאזורים הממוזגים, ומשתמשת במנגנון CopyRangeTo הקיים כדי להעביר כל ערך, נוסחה ואינדקס סגנון לכל תא אל היעד שמתחיל ב-A1. מעל התאים היא משחזרת את כל שכבת מצב הגיליון שהופכת תבנית למוגמרת:
- טווחים ממוזגים, שנבנים מחדש לפי קואורדינטות כך שהבאנר פורש את אותו מלבן
- רוחבי עמודות וגבהי שורות, יחד עם רשימות התאים הנסתרים, המצומצמים ורמות המתאר, מועתקים מילה במילה כך ששורות ועמודות שאינן ברירת מחדל מיושרות בדיוק
- חלוניות קבועות ומצב התצוגה: רמת זום, תצוגת קווי הרשת והערכים האפסיים, כיוון ימין-לשמאל וסוג התצוגה
- מצב הגנה עם אפשרויות ההרשאה לכל פעולה, כך שתבנית נעולה תישאר נעולה באותה צורה
- כל בלוק הגדרת העמוד: שוליים, כיוון, גודל נייר, קנה מידה והתאמה לעמוד, אזור הדפסה, כותרות הדפסה, כותרות עליונות ותחתונות, והדגלים של קווי רשת להדפסה וכותרות להדפסה
- טווח ה-AutoFilter, צבע הלשונית ונראות הגיליון
התוצאה היא גיליון שמודפס, מסונן ומוצג בדיוק כמו המקור. ומכיוון שהתאים, המיזוגים ורשימות הממדים נוצרים מחדש פיזית על הגיליון החדש ולא נקשרים באותו אובייקט, העותק עצמאי לחלוטין. כתיבת 999 לתא בעותק לא משנה את ערך המקור; העצמאות הזאת היא המאפיין החשוב ביותר של שכפול שמיועד לדוחות אזוריים מקבילים, והדמו SheetCopy שמגיע עם המוצר בודק זאת במפורש
מה נשאר רדוד, ולמה
ועכשיו החלק הכנה. תרשימים, תמונות מוטמעות, טבלאות XLSX, אימותי נתונים וכללי עיצוב מותנה לא מועתקים. זהו גבול מדויק ומתועד, לא השמטה, ושווה להבין את ההיגיון כדי לתכנן סביבו במקום להיות מופתעים ממנו
כל אחד מהאוספים האלה נושא זהות וקשרים שלא שורדים העתקת שדות נאיבית. תרשים מצביע על טווח נתונים מקור וכולל קשר שרטוט בחבילת OOXML; שכפול האובייקט בלי מיפוי מחדש של הקשר ושל הפניות הסדרות מייצר תרשים שמוצג מול נתונים שגויים, או חבילה ש-Excel מסמן ככזו שדורשת תיקון. לטבלה יש שם שחייב להיות ייחודי בתוך חוברת העבודה, שורת כותרת שקשורה לעמודות מסוימות, והקשר שנוצר אוטומטית משלה. עיצוב מותנה ואימותי נתונים מצמידים את עצמם לטווחי קואורדינטות ובמקרה של אימות יכולים להפנות לטווחים אחרים באמצעות נוסחה. העתקה עמוקה נכונה של כל אחד מהם משמעה כתיבה מחדש של הפניות והנפקת זהויות חדשות, וזה כבר עבודה אמיתית עם כשלונות אמיתיים. לעשות זאת חצי דרך, כלומר להעתיק את האובייקט בלי ההפניות שלו, גרוע יותר מלא להעתיק בכלל: זה מייצר קובץ שנפתח עם בקשת תיקון ומפיל תוכן בשקט. לכן המנוע מעתיק את הדברים שהוא יכול להעתיק נקי ומשאיר את האוספים שתלויים בהפניות ללקוח, שיודע למה היעד אמור להצביע
בפועל, זה אומר שהזרימה לתבנית עשירה יותר היא: משכפלים את הגיליון כדי לקבל את התאים, הפריסה והגדרת ההדפסה, ואז בונים מחדש את התרשים, הטבלה, האימותים או העיצובים המותנים על העותק באותו API ששימש ליצירה הראשונה. מכיוון שאתם יוצרים אותם מחדש מול הטווחים של העותק עצמו, ההפניות יוצאות נכונות מעצם הבנייה. עבור תרשים שקורא A1:C10, מוסיפים על העותק תרשים חדש שמצביע על A1:C10 של העותק; עבור AutoFilter שרוצים שיהיה חי, שימו לב שטווח המסנן אכן עובר, ולכן צריך רק להחיל מחדש את תנאי העמודות. את כללי העיצוב המותנה ואימותי הנתונים תוסיפו מחדש באמצעות אותן קריאות שמתוארות במאמר על תאים ממוזגים ופריסת תבנית לדוח, שעובר על טבלת המיזוג ומודל הטווחים שהעותק יורש
איפה שכפול משתלב בצינור הפקת דוחות
שכפול גיליון הוא בן לוויה טבעי ליצירה מבוססת מצייני מקום. הגישה המוצמדת לאסימונים במדריך ליצירת דוחות מבוססת תבנית ב-Delphi פותרת את הבעיה של כתיבת נתונים אל תוך פריסה שאנשים אחרים עורכים; השכפול פותר את הבעיה של צורך באותה פריסה כמה פעמים באותה חוברת עבודה. חברו את השניים ותקבלו דפוס נקי: שומרים על גיליון Template אחד וצלול עם האסימונים, המיזוגים והגדרת ההדפסה, ואז לכל אזור או תקופה קוראים ל-Duplicate, ממלאים את האסימונים של העותק בחיתוך הנתונים הרלוונטי וממשיכים הלאה. התבנית המקורית לעולם לא משתנה, ולכן היא נשארת מקור אמין לשכפול הבא, וכל גיליון פלט מתחיל מפריסה זהה ברמת הבייט
הערת רצף אחת חוסכת סוג של בלבול. משכפלים את הגיליון לפני שמזרימים אליו נתונים, לא אחריהם. תבנית צריכה להחזיק מבנה ועיצוב, לא את המספרים של הרבעון שעבר, ושכפול של גיליון מעוצב וריק אומר שכל עותק מתחיל נקי. אם משכפלים גיליון שכבר יש בו נתונים, הנתונים עוברים איתו, כי CopyFrom מעתיקה את UsedRange בנאמנות; לפעמים זה בדיוק מה שרוצים, אבל בדוח שמתפזר להרבה גיליונות זה בדרך כלל לא המקרה
בדיקת אימות מהירה
מכיוון שההפרדה בין העתקה עמוקה להעתקה רדודה בלתי נראית עד שמחפשים אותה, עדיף לבנות לבדיקה של חמש שורות בתוך המשימה במקום לסמוך שהכול עבר. אחרי השכפול, קראו חזרה את האותות המבניים שהעותק אמור לרשת ואמתו שהם תואמים למקור
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.
מספר המיזוגים, רוחב עמודה, שורת ההקפאה וסימון צבע הלשונית אומרים לכם שהשכבה שכן מועתקת אכן הגיעה. בנפרד, בכל גיליון שנשא תרשים, טבלה, אימותים או עיצוב מותנה, התייחסו אליהם כרשימת דברים לבנייה מחדש על העותק: ההיעדר שלהם הוא לפי התכנון, והתיקון הוא כמה קריאות בלבד, לא דוח באג. המודל המנטלי הזה, עמוק במקום שבו זה בטוח ורדוד במקום שבו הפניות היו נשברות, הוא כל הסיפור של שימוש נכון בתכונה הזו
שכפול גיליון והעתקת מצב הגיליון באמצעות CopyFrom המתוארים כאן מגיעים ב-v2.91.0 של רכיב הגיליונות האלקטרוניים ל-Delphi של HotXLS, לצד דוגמת SheetCopy ניתנת להרצה שמפעילה את מחזור השכפול והשינוי מקצה לקצה