當您在 XLSX 工作表中插入或刪除列或欄時,HotXLS 會自動調整公式參照;引擎的 InsertRows、DeleteRows、InsertCols 與 DeleteCols 方法會重寫每一個存留的公式,使其 A1 參照(相對參照、絕對參照與範圍參照)在結構編輯後繼續指向相同的資料,且指向已刪除區塊的參照會變成 #REF!,這與 Excel 的行為一致
這所防止的錯誤是報告生成中最隱蔽的錯誤之一;產生器將每日數據寫入 C2:C9,並在下方帶有 =SUM(C2:C9),接著在後續的步驟中於頂部插入一個標頭列;如果引擎僅移動儲存格的值而保留公式文字,該 SUM 仍然會讀取 C2:C9,而此時資料已經位於 C3:C10——因此總額會默默漏掉最後一天,並將標頭列計算了兩次;沒有任何內容會引發異常,檔案開啟正常,但數字就是錯的;在 2.160 版本之前,HotXLS XLSX 引擎在結構編輯期間不會更動公式;自 2.160 版本起,重寫是自動進行的,無需進行任何設定
當您在 Excel 中插入列時,公式會發生什麼事?
Excel 的規則是參照跟隨資料,而不是地址;當插入一列時,每一個列索引位於或低於插入點的參照,都會向下移動所插入的列數;完全位於插入點上方的參照則不受影響;刪除列則是反向執行相同的規則:已刪除區塊下方的參照向上滑動,而指向已刪除區塊本身的參照會變成 #REF!,因為它們指名的儲存格已不存在;在另一個軸向上,欄的行為完全相同;一個希望其輸出能禁得起 Excel 使用者考驗的試算表函式庫,必須精確重現這些機制,因為使用者會本能地以這些邏輯來推導他們的公式,而根本不用多加思考
讓開發人員感到驚訝的部分是,絕對參照也會移動;$B$2 中的 $ 錨點控制著公式被「複製或填滿」到另一個儲存格時會發生什麼事——它們在結構編輯期間不起作用;在第 2 列上方插入一列,Excel 會將 $B$2 重寫為 $B$3,金錢符號保持原樣,因為公式所依賴的值實際上已移至第 3 列;僅移動相對參照的引擎,將會損毀人們最刻意錨定的公式;HotXLS 會移位這兩種形式,並在重寫的文字中保留 $ 標記
HotXLS 如何自動移位公式參照?
TXLSXWorksheet 上的所有四種結構編輯方法都委派給單一的幾何引擎:ShiftSheetGeometry(RowFrom, RowDelta, ColFrom, ColDelta);InsertRows(BeforeRow, Count) 以正的列 delta 呼叫它,DeleteRows(StartRow, Count) 以負的呼叫它,而欄方法則在欄軸向上做相同的事情;該常式首先重新定位儲存格本身——捨棄 any 落入已刪除區塊的儲存格——然後走訪每個存留的公式儲存格,並將其文字傳遞給 XlsxAdjustFormulaRowColRefs,這是一個尋找 A1 樣式參照並針對移位重寫其列與欄成分的掃描器;同一次掃描還會重新定位合併範圍、超連結、註解、圖片、圖表、條件化格式、資料驗證和表格範圍,使整個工作表做為一個單元進行移動
// Layout before the edit:
// C2..C9 daily figures
// C10 =SUM(C2:C9)
Sheet.InsertRows(2, 1); // one blank row before row 2
// Layout after the call:
// C3..C10 daily figures
// C11 =SUM(C3:C10) -- the range moved with the data
掃描器的幾個細節值得了解;XlsxAdjustFormulaRowColRefs 可識別所有四種錨定形式(A1、$A1、A$1、$A$1)的單一儲存格參照以及雙角範圍(例如 A1:B3),並獨立調整每個端點;文字已以 #(來自早期編輯的錯誤標記)開頭的公式會被跳過,而不是重新掃描;此外,InsertCols 新增了一個與 Excel 行為一致的優點:新插入的欄會繼承其左側鄰居的寬度,這正是 Excel 的「插入工作表欄」命令所做的
被刪除的參照何時會變成 #REF!?
單一列或欄索引的移位規則有三種結果;編輯點之前的索引保持不變;位於或超過編輯點的索引則移動該 delta 值;而在刪除情況下,落入已刪除區塊內部的索引沒有任何有意義的新值——該儲存格已消失——因此掃描器會將整個參照重寫為 #REF!;對於範圍參照,兩個端點都執行相同的規則,且如果任何一個端點落入已刪除區塊內部,該參照就會被重寫為 #REF!,而不是保持半有效狀態
// A12 holds =A4+A6+A10
Sheet.DeleteRows(5, 3); // delete rows 5..7
// The formula, now in A9, reads =A4+#REF!+A7
// A4 : above the deleted block, unchanged
// A6 : inside rows 5..7, gone -> #REF!
// A10 : below the block, slides up -> A7
產生顯眼的 #REF! 而不是默默重新定位是正確的權衡,這也正是 Excel 的做法;在真正的輸入被刪除後,指向鄰近儲存格的公式會傳回一個看起來合理的數字;而 #REF! 則會在依賴的公式中傳播,並在第一次冒煙測試中顯現出來;相同的轉換也適用於欄軸向
// E1 holds =B1*$C$1
Sheet.DeleteCols(3, 1); // remove column C
// The formula, now in D1, reads =B1*#REF!
// The absolute anchor did not protect $C$1 -- the cell itself is gone
哪些參照形式不會被重寫?
掃描器針對具有明確「欄字母加上列數字」形狀的同工作表 A1 參照,且有必要精確指出哪些內容落在該範圍之外;諸如 A:A 的整欄參照和諸如 1:1 的整列參照缺少兩個成分之一,因此掃描器會將它們保持原樣;結構化表格參照(Table1[Amount])同樣不予變動;此重寫也嚴格在 A1 標記法上運作——如果您的程式碼以 R1C1 樣式建立公式,請在結構編輯前將其轉換為 A1,這在 Delphi 中 R1C1 公式標記法的隨附文章 中有詳細說明
跨工作表和活頁簿層級的結構是由獨立的階段處理,而不是由儲存格文字掃描器處理;在調整編輯後的工作表後,ShiftSheetGeometry 會將相同的幾何變更傳播至其他工作表上參照該編輯工作表的公式、圖表數列範圍、內部超連結目標以及定義名稱;定義名稱在工作表生命週期層級獲得了進一步的保護:自 2.150 版本起,刪除工作表會將定義名稱公式內部的每個 SheetN! 限定詞重寫為 #REF!,而重新命名工作表則會將該限定詞重寫為新名稱,因此名稱絕不會指向已不存在的工作表;名稱與跨工作表公式如何搭配使用,已在 定義名稱與跨工作表公式的文章 中進行探討
在移位後重新計算
參照調整會重寫公式的「文字」,它不會重新計算結果;在結構編輯後,與公式一併儲存的快取值描述的是舊的幾何結構,因此可靠的順序是:先執行所有插入與刪除,然後觸發一次重新計算,接著進行儲存;在重新計算之前執行移位也能保持相依性資訊的真實性——每個重寫的參照都指名了其真實的前導,這正是 增量重新計算引擎及其相依性圖 重新計算最小受影響儲存格集所需要的;如果重寫的公式現在包含 #REF!,重新計算會立即呈現該錯誤值,而不是在檔案中留下過時的數字
公式參照調整隨 Delphi 與 C++Builder 的 HotXLS Delphi Excel 元件 中的 XLSX 引擎標準行為出貨;產品頁面帶有完整的工作表編輯 API 參照,包括此處顯示的插入與刪除方法