HotXLS 是 Delphi 與 C++Builder 的 Excel 元件。當插入或刪除列或欄,將規則涵蓋的範圍切成需要不同相對公式錨點的片段時,它會自動將條件格式或資料驗證規則分割成兩個或多個獨立的規則物件,接著為每個條件格式規則重新指派全新且唯一的優先順序編號。這項行為隨 XLSX 引擎 2.196 版推出並自動執行,沒有可停用的設定。觸發條件雖然狹窄卻很常見:某個 cellIs 或 expression 規則的公式相對於自身範圍讀取儲存格,而所在工作表之後在該精確範圍的中間插入或移除列
多數 Excel 自動化說明會停留在公式文字問題:調整每個 SUM() 與每個 VLOOKUP() 內的列號和欄號,讓參照仍指向正確的儲存格。這部分確實存在,而且HotXLS 在列與欄移動時如何改寫公式參照的配套文章已有說明,但條件格式或資料驗證規則不只是放在儲存格中的公式。它將公式與範圍配對,在 ECMA-376 術語中是 sqref,兩者必須一起移動。當結構性編輯將該範圍切成需要兩種不同相對偏移才能保持正確的兩個片段時,保留一個只有一段公式文字的規則物件便不再可行;若假裝可以,醒目提示規則就會悄悄開始比較錯誤的列
為什麼插入列會分割條件格式規則,而不是只移動它
條件格式或資料驗證規則會為整個範圍保留一個公式,並相對於單一錨點儲存格進行評估。因此,一旦編輯使該範圍的兩個部分需要兩種不同的相對偏移,一個公式就無法再正確描述兩個部分。ECMA-376 會將規則涵蓋範圍表示為 conditionalFormatting 或 dataValidation 元素上的 sqref 屬性,而 Excel 會將 Formula1 和 Formula2 視為輸入該 sqref 左上角儲存格的文字,再填入其餘範圍,方式與普通相對公式向下填滿欄位相同。假設 B2:B50 上有一個差異醒目提示,用來標示任何超過預算的實際數字;它是 cellIs 規則,Formula1 為字面文字 C2,意思是將目前列的 B 儲存格與同一列的 C 儲存格比較
Idx := Sheet.AddConditionalFormat('B2:B50', xlsxCfOpGreaterThan, 'C2');
Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);
Sheet.InsertRows(25, 1); // one blank separator row, starting at old row 25
在舊列 25 插入這一列分隔列後,插入點上方的列不會移動,因此規則的這部分仍能正確將 Formula1 視為 C2。原本位於 25 到 50 的列會向下移至 26 到 51,而對這些列而言,C2 現在完全是錯誤的儲存格,因為第 26 列需要與 C26 比較,而不是與上方二十多列的預算數字比較
HotXLS 如何判斷規則是否需要分割
HotXLS 只有在幾何關係確實需要時才建立額外的規則物件:內部常式 XlsxBuildShiftedRuleParts 會走訪規則 sqref 中每個不相鄰區域,計算該區域的錨點儲存格在編輯前是什麼、編輯後變成什麼,並檢查每個產生的片段是否都需要相同的相對偏移修正。如果所有片段一致,便保留一個規則,將其 sqref 重建為移動後片段的聯集,並重新設定一次公式錨點。只有在片段不一致時才會真正分割,正是上面的 B2:B50 案例:上方區塊保留原始錨點,而下方區塊需要新的錨點
重新設定片段公式錨點是兩步驟流程,會重用 HotXLS 為 OOXML 共用公式群組保留的機制:首先,將公式視為原本就錨定在該片段自身的左上角儲存格,使用展開共用公式範圍時相同的相對偏移計算來轉譯公式;接著讓結果通過相同的列與欄移動掃描器,改寫普通工作表公式。這就是 Formula1 如何透過兩次移動從 C2 變成 C26,而不是使用手寫的特殊案例:先將 C2 向前平移 23 列得到 C25,如同規則一直從該處開始,然後讓第 25 列的普通移動再將它推進到 C26。其他每項屬性,包括填滿色彩、停止若為真以及運算子本身,都會不變地隨附到新的規則物件,因此兩半仍會以原本的色彩標示儲存格
// ConditionalFormats now holds two rules instead of one:
// B2:B25 Formula1 = 'C2' (rows above the insert)
// B26:B51 Formula1 = 'C26' (rows that shifted down)
資料列與圖示集也會像 cellIs 規則一樣分割嗎
不會:HotXLS 只會分割正確性確實取決於每個區域相對公式的規則類型,也就是 cellIs 比較與 expression 規則;其他條件格式類型都會保留為單一規則物件,其 sqref 只會擴大為涵蓋移動片段的多區域聯集。內部判斷只是普通的 Kind 檢查,cf.Kind in [cfkCellIs, cfkExpression],沒有更複雜的內容。資料列、雙色與三色尺度、圖示集、前幾名與後幾名排名,以及重複、空白和錯誤偵測器,都攜帶描述整個涵蓋範圍的負載,包括資料列色彩、尺度停止點和圖示家族,而不是每個儲存格的相對比較,因此將它們分割成多個具優先順序的規則物件不會帶來正確性,反而只會增加需要管理的規則。當編輯分割它們的範圍時,HotXLS 會將片段重新組合成一個具有多區域 sqref 的規則,並將負載作為單一單元重新設定錨點,而不是為每個片段複製新的規則物件。這項區分與條件格式與豐富文字基礎文章中的規則類型分類一致:資料列、色彩尺度和圖示集本來就因完全忽略 Style 屬性而與 cellIs 規則不同,現在也證明它們基於相同的根本原因,不需要依區域重新設定錨點
為什麼結構性編輯後規則優先順序會改變
優先順序會改變,是因為每個複本一開始都持有與來源規則完全相同的優先順序值,而 HotXLS 之後會執行正規化流程,將產生的重複值整理成乾淨且無間隔的順序,不讓兩個規則維持相同排名。第二個內部常式 XlsxNormalizeConditionalFormatPriorities 會取得每個條件格式目前的優先順序;若規則從未明確設定,便退回使用該規則在集合中的位置;接著穩定排序整個清單,使相同值保留原本的相對順序,再將排序結果重新編號為沒有間隔且沒有重複的密集 1、2、3 序列。HotXLS 會在移動開始前執行一次,讓複製從乾淨基準開始;每次分割及移除空規則後也會再執行,因此最後儲存的檔案不會有兩個規則項目宣稱相同的優先順序。如果你依照條件格式基礎文章的建議,在優先順序值之間保留間隔,以便日後插入規則而不重新編號其餘規則,這一點就很重要:間隔會一直保留到下一次列或欄編輯觸及該工作表,之後便會收縮,因為正規化只保證唯一性和穩定順序,不保證原始編號方案不變
資料驗證規則也會分割,但沒有優先順序可重新編號
資料驗證規則會使用與 cellIs 和 expression 條件格式相同的範圍分割邏輯,而且不同於條件格式,每種驗證類型都會一致地經過這條路徑:HotXLS 沒有像資料列和圖示集對條件格式那樣,為資料驗證提供獨立的非公式家族,因此普通清單或整數規則會由處理相對自訂公式的相同常式分割。差異在於優先順序:ECMA-376 完全沒有為 dataValidation 元素提供 priority 屬性,因此資料驗證沒有條件格式所具有的重新編號步驟。假設有一個自訂公式驗證,用來防止每一列的實際金額超過旁邊欄位中該列自己的預算
Sheet.AddCustomValidation('D2:D400', 'D2<=C2');
Sheet.DeleteRows(150, 5); // remove five rows out of the validated range
// DataValidations now holds two rules instead of one:
// D2:D149 Formula1 = 'D2<=C2' (rows above the deletion)
// D150:D395 Formula1 = 'D150<=C150' (rows that shifted up)
這一點之所以重要,原因與資料驗證基礎文章提醒不要在列數確定前附加規則相同:驗證只涵蓋你提供的明確儲存格,之後的結構性編輯可能使兩個或多個規則共同完成原本一個規則的工作。功能上不會出錯:原始範圍中的每個儲存格仍由某個規則驗證,但假設每欄只有一個 DataValidations 項目的程式碼,在第一次編輯觸及該範圍後便會開始錯誤索引。這種分割有明確上限:如果分割會使工作表超過 65,534 個資料驗證規則,HotXLS 會擲出例外,而不是寫入 Excel 會悄悄拒絕的檔案;這表示函式庫拒絕製造損毀的活頁簿,而不是一般使用中很可能達到的限制
大量插入或刪除後要檢查什麼
當指令碼在充滿條件格式與資料驗證的工作表上執行一批列或欄編輯後,值得驗證的兩件事是規則總數與優先順序,因為兩者都可能以程式碼審查不易察覺、但使用者開啟 Excel 中的管理規則時立刻明顯的方式漂移。一次編輯通常不會造成太大影響:在單一 cellIs 規則中間插入一次,最多只會從一個規則物件產生兩個。當報表產生常式逐列插入,而工作表原本已帶有數個以公式錨定的規則時,風險會累積:每次執行都可能再次分割前一次已分割的規則,五個原始 cellIs 規則最後可能變成數倍於原數量的低價值片段,覆蓋原始範圍的細小區段。將結構性編輯批次處理,一次呼叫插入整個新增區塊,而不是逐列插入,可以讓規則數量與真正不同的錨點數量相關,而不是與執行的編輯次數相關
規則分割與優先順序正規化是 HotXLS Delphi Excel 元件中 XLSX 引擎的標準行為,適用於 Delphi 與 C++Builder;產品頁提供完整的工作表編輯 API 參考,包括本文所述的條件格式與資料驗證方法