سلولهای یک قالب گزارش تازهبازشده را پیمایش کنید و یک عنوان ادغامشده مثل یک نشتی رفتار میکند. A1 را میخوانید و «Quarterly Statement» میگیرید؛ B1 تا F1 را میخوانید، که بهوضوح زیر همان بنر نشستهاند، و هیچچیز نمیگیرید. مقداری در C1 بنویسید تا سرصفحه را وصله کنید و هرگز روی صفحه ظاهر نمیشود. شبکه داده شما را گم نکرده است. دقیقاً همان کاری را میکند که معنای ادغام است: در هر دو قالب XLS و XLSX، یک مستطیل ادغامشده محتوای یک سلول، یعنی لنگر بالا-چپ، را رندر میکند و با بقیه بهعنوان فضای پوشیدهشدهای رفتار میکند که مقادیر را نگه میدارد ولی هرگز نشانشان نمیدهد. کاربران اکسل این را با آزمون و خطا جذب میکنند. یک تولیدکننده گزارش باید آن را بهصورت یک قاعده رمزگذاری کند، چون در کد تولیدشده، نشانه بیماری یک ناحیه خالی است بدون هیچ استثنایی که بتوان آن را به آن ردیابی کرد. HotXLS، یک کتابخانه Object Pascal بومی که هر دو قالب اکسل را از دلفی و C++Builder میخواند و مینویسد، جدول ادغام را آنقدر صریح افشا میکند که بتوانید بر اساس قاعده برنامه بنویسید نه اینکه آن را در یک تیکت پشتیبانی دوباره کشف کنید
یک مقدار، یک لنگر
ادغام یک دستور نمایشی است که روی شبکهای لایهبندی شده که شکلش تغییر نمیکند. هر سلول پوشیدهشده همچنان بهصورت جایگاه خودش در فایل وجود دارد؛ رکورد ادغام فقط به مصرفکننده میگوید محتوای لنگر را روی مستطیل رنگ کند. این تمایز سه رفتار را رقم میزند که ارزش درونیکردن پیش از نوشتن هر کد چیدمانی را دارند. خواندن یک سلول پوشیدهشده مقدار ذخیرهشده خودش را برمیگرداند، که برای بنری که شما ساختهاید معمولاً خالی است، پس هر کدی که یک عنوان ادغامشده را بازرسی میکند باید لنگر را حل کند و بخواند. نوشتن در یک سلول پوشیدهشده در سطح فایل موفق میشود و هیچجا ظاهر نمیشود، که همان دام سرصفحه نامرئی از ابتدای مقاله است. و لغو ادغام یک ناحیه هر چیزی را که در تمام این مدت زیر آن نشسته بود آشکار میکند، پس مقداری سرگردان که در فضای پوشیدهشده نوشته شده، روزی که کسی ادغام را منحل کند به یک نقص قابل مشاهده تبدیل میشود
در سمت XLSX آن جدول یک شیء درجهیک است. Sheet.MergedCells متدهای Add('A1:C1')، FindAt(Row, Col)، DeleteAt و Items را حمل میکند، و فراخوانیای که بیش از همه به سراغش میروید FindAt است: هر مختصاتی به آن بدهید و ناحیه ادغامشدهای را که آن سلول را میپوشاند برمیگرداند، یا وقتی سلول تنهاست nil. همین یک جستجو پایه هر دو نیمه مدیریت درست ادغام است، خواندن امن و محافظ نوشتن، و هر دو بعداً ظاهر میشوند
دو نما، دو اصطلاح ادغام
HotXLS موتور کلاسیک BIFF8 برای .xls و موتور OOXML برای .xlsx را بهصورت مدلهای شیء جداگانه نگه میدارد، و آنها ادغام را متفاوت هجی میکنند چون از قراردادهای متفاوتی نسب میبرند. نمای (facade) XLS از اصطلاح COM اکسل پیروی میکند: یک بازه را از یک ویژگی اندیسدار دوآرگومانی میگیرید و Merge را با یک OleVariant فراخوانی میکنید که مقدارش هندسهای را که در نهایت به دست میآورید تعیین میکند
var
Book: IXLSWorkbook; // شمارش از طریق اینترفیس: نیازی به Free دستی نیست
Sh: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
Sh := Book.Sheets[1]; // مجموعه کاربرگهای XLS یکپایه است
Sh.Range['A1', 'F1'].Merge(False); // False = یک بلوک ادغامشده واحد
Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
Sh.Range['A3', 'F4'].Merge(True); // True = ادغام در عرض: یک ادغام به ازای هر ردیف
Book.SaveAs('layout.xls');
end;
آرگومان Merge همان بخشی است که افراد اشتباه میگیرند. روی یک بازه دو ردیفی، Merge(True) دو ادغام یکردیفی مستقل تولید میکند، که همان «Merge Across» اکسل است و دقیقاً همان چیزی که برای یک نوار سرصفحه چندطبقه که باید ردیفهایش جداشدنی بمانند میخواهید. Merge(False) کل مستطیل را در یک بلوک واحد جوش میدهد. بازه همچنین MergeCells را بهصورت یک پرچم وضعیت گزارش میدهد، ناحیه دربرگیرنده را از طریق MergeArea برمیگرداند، و با Unmerge خودش را منحل میکند. نمای XLSX همان عملیات را زیر نامهای متفاوتی افشا میکند: Sheet.MergeCells(Row1, Col1, Row2, Col2) کرانهای صحیح میگیرد، TXLSXRange.Merge گونه معادل Across را میپذیرد، و مجموعه MergedCells نتیجه را نگه میدارد
قالبی که با دادههایش رشد میکند
یک قالب گزارش واقعی یک شبکه ثابت نیست. سرصفحه و جمعها ثابتاند، اما بخش جزئیات میان آنها تا هر اندازهای که پرسوجو برمیگرداند کش میآید. الگویی که دوام میآورد یک ردیف جزئیات کاملاً استایلشده را در قالب نگه میدارد، آن را یک بار به ازای هر رکورد شبیهسازی میکند، و سپس شکافی جلوی بلوک جمعها باز میکند تا هر چیزی که در پایین لنگر شده بدون از دست دادن قالببندیاش به پایین بلغزد
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611'; // مقدار به لنگر میرود، یعنی A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1; // اندیس استخر صفرپایه، سمت سلول یکپایه
// ردیف 5 خط قالب جزئیات استایلشده است
for I := 0 to ItemCount - 1 do
Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1); // استایلها و فرمولها همراه آن میروند
// بازکردن یک شکاف بالای بلوک جمعها؛ محتوای پایین به پایین جابهجا میشود
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);
دو خط ارزش نگاه دوباره دارند. تخصیص فونت یک خطای یکواحدی حمل میکند که بیسروصدا گاز میگیرد: Fonts.Add یک موقعیت استخر صفرپایه پس میدهد، در حالی که یک سلول یک ارجاع فونت یکپایه ذخیره میکند که در آن صفر یعنی فونت پیشفرض، پس جاانداختن + 1 هیچ خطایی نمیدهد، فقط عنوان شما را با قلم اشتباه استایل میدهد. خط دیگر CopyRange است، که قالببندی و فرمولها را همراه با مقادیر جابهجا میکند. این کل دلیل شبیهسازی یک ردیف قالب دستساز بهجای بازسازی ظاهرش در کد است. یک طراح ظاهر را یک بار، در قالب، مالک است؛ تولیدکننده فقط داده را در کپیهای آن میریزد
این تفکیک وقتی چیدمان قابل استفاده مجدد در کارپوشه خودش زندگی میکند، مثلاً یک کاربرگ از نوارهای سرصفحه و پاصفحه که میان گزارشها به اشتراک گذاشته میشود، بیشتر مقیاس میگیرد. CopyRangeTo همان شبیهسازی را در عرض مرزهای کاربرگ انجام میدهد، با گرفتن یک کاربرگ هدف بهعلاوه مختصات مقصد، پس یک تولیدکننده میتواند یک کاربرگ قالب دستنخورده نگه دارد و نواحیاش را در هر تعداد کاربرگ خروجی که یک کار نیاز دارد مهر بزند. جایگزین، یعنی تغییردادن قالب در جای خود و تلاش برای بازیابی آن پس از کار، از آن نوع چیزهایی است که کار میکند تا روزی که یک اجرا در نیمه راه متوقف شود
InsertRows چه چیزی را جابهجا میکند و چه چیزی را نه
الگوی رشددادن قالب فقط به این دلیل کار میکند که InsertRows در XLSX یک ویرایش ساختاری است نه یک بُر زدن سلولها. وقتی شکافی باز میکند، نواحی ادغامشده، ارتفاع ردیفها، پیوندها، یادداشتها، پنجرههای ثابتشده، بازههای فیلتر خودکار، قالبهای شرطی، اعتبارسنجیهای داده، جدولها، نامهای تعریفشده، لنگرهای تصویر و لنگرهای نمودار را که زیر نقطه درج نشستهاند جابهجا میکند، نه فقط مقادیر سلول را. همین است که میگذارد بلوک جمعها با ادغامها و قالبهای عددی دستنخورده به ردیف جدیدش برسد، نه لختشده
دو محدودیت مستندشده آن همانهایی هستند که باید حولشان طراحی کرد. تعدیل فرمول به کاربرگی که ویرایش میشود محدود است: ارجاعهای درون آن کاربرگ بازنویسی میشوند، و فرمولی روی کاربرگ دیگر که به ناحیه جابهجاشده اشاره میکند هم بازنویسی میشود، اما تعدیل فقط ارجاعهایی را دنبال میکند که کاربرگ ویرایششده را هدف میگیرند، پس هر طرح ارجاع بینکارپوشهای سزاوار ممیزی خودش است نه اعتماد کورکورانه. محدودیت دوم تیزتر است، و در سمت XLS قرار دارد. جدولهای محوری چرخههای باز-ذخیره را بهصورت رکوردهای خام حفظشده پشت سر میگذارند، نه بهصورت اشیای مدلشدهای که HotXLS بتواند جابهجا کند، پس درج ردیفها ردپای یک جدول محوری را جابهجا نمیکند. هر قالبی که برای قالب .xls میسازید باید نواحی جدول محوریاش را کاملاً دور از هر نواری که رشد میکند پارک کند
امتناع از نوشتن داده در فضای چیدمان
شکست سلول ادغامشدهای که واقعاً به تولید میرسد، شکست ظاهری نیست. ساختاری است: یک ردیف جزئیات به یک نوار چیدمان ادغامشده میلغزد، مقادیرش در سلولهای پوشیدهشده فرود میآیند و نامرئی میشوند، و جمعهای ستون بیسروصدا از مطابقت با آنچه هر خواننده کاربرگ میتواند ببیند بازمیمانند. چون FindAt به پرسش ناحیه پوشاننده برای هر مختصاتی پاسخ میدهد، تولیدکننده میتواند آن نوشتن را در همان لحظهای که قرار است رخ دهد رد کند، نه اینکه گزارشی را عرضه کند که بیسروصدا کمشماری میکند
// امتناع از نوشتن داده جزئیات در یک ناحیه چیدمان ادغامشده
if Sheet.MergedCells.FindAt(Row, 1) <> nil then
raise Exception.CreateFmt('row %d overlaps a merged layout region', [Row]);
Sheet.Cells[Row, 1].Value := Detail.Description;
همان بررسی مرزی به هرجایی تعلق دارد که یک کاربر بعداً خروجی را مرتب یا فیلتر خواهد کرد. بازهای که ادغامهایی درونش دارد نمیتواند تمیز مرتب شود، چون مرتبسازی ردیفها را مستقل جابهجا میکند و ادغامی که چند ردیف را در بر میگیرد هیچ ردیف واحدی ندارد که با آن سفر کند؛ اکسل با یک خطا یا یک چیدمان بههمریخته پاسخ میدهد. انضباطی که گزارشها را درست نگه میدارد جغرافیایی است. ادغامها را به نوارهای عنوان، جداکنندههای بخش و بلوکهای امضا محدود کنید، و میانه جدولی کاربرگ را تخت نگه دارید. مقاله تولید گزارش مبتنی بر قالب این تفکیک چیدمان در برابر داده را به یک جریانکار کامل مبتنی بر جانگهدار بسط میدهد، و مقاله قالببندی شرطی و متن غنی استایلدهی آن نوار داده تخت را پوشش میدهد
ادغامها در مسیر خروج چگونه تنزل مییابند
ادغام یک مفهوم کارپوشه است، و هر قالب صادرات متنمحور آن را به درجهای متفاوت رعایت میکند. دانستن این سه رفتار از ابتدا یک چرخه QA را صرفهجویی میکند. صادرات HTML ادغامها را وفادارانه بازتولید میکند، و colspan و rowspan را روی یک جدول واحد مینویسد، پس یک گزارش مقصد-مرورگر ظاهر نواردارش را حفظ میکند. صادرات RTF اصلاً ستونها را در بر نمیگیرد: متن لنگر در سلول خودش فرود میآید و عرض باقیمانده ادغام بهصورت سلولهای خالی بیرون میآید، که یک عنوان پهن را در یک واژهپرداز از نظر بصری به چپ هلداده رها میکند. CSV هیچ مفهومی از ادغام ندارد، پس مقدار لنگر یک فیلد را اشغال میکند و هر سلول پوشیدهشده بهصورت یک فیلد خالی نوشته میشود. نتیجه برای کارپوشهای که صادراتهای جداشده با جداکننده را هم تغذیه میکند این است که هر چیز باربر را بیرون از هندسه ادغامشده نگه دارید؛ مقاله صادرات CSV، TSV و HTML هر قالب را به تفصیل مرور میکند
یک اطمینانبخشی برای هرکسی که این را در برابر اندازه فایل میسنجد: ادغامها در مقیاس گزارش تقریباً هیچ هزینهای ندارند. جدول ادغام در کنار داده سلولها ناچیز است، و خواندن یک سلول پوشیدهشده همچنان از طریق FindAt انجام میشود نه با پویش. فشار کارایی روی کارپوشههای بزرگ از جای دیگری میآید، عمدتاً رشد استخر استایل و حافظهای که مسیر ذخیره نگه میدارد، که مقاله کارایی کارپوشههای بزرگ مستقیماً به آن میپردازد. هر دو API ادغام، عملیات ویرایش ساختاری و دموهای قالب همراه با HotXLS Delphi Component عرضه میشوند