自研電子表格類庫 CGridCtrl,支援儲存格合併、文字對齊、字型/顏色、邊框自定義、讀寫權限、列排序、動態增刪列/行、剪貼板互通、圖形顯示、所見即所得列印預覽。核心難點在於公式引擎與虛擬模式渲染。
支援四則運算、括號優先級、儲存格引用 (A1、BC23)、遞歸計算依賴儲存格。
CString CGridCtrl::ParseExpression(CString strExpr)
{
strExpr.MakeUpper();
const CString ops = _T("+-*/(),");
CString strOut;
CString token;
int nState = 0; // 0=初始, 1=儲存格引用(字母開頭), 2=數值/常數
int nLen = strExpr.GetLength();
for (int i = 0; i < nLen; ++i) {
TCHAR ch = strExpr[i];
bool isOp = ops.Find(ch) >= 0;
if (isOp || i == nLen - 1) {
if (i == nLen - 1 && !isOp) token += ch; // 最後一字符非運算符
if (nState == 1) { // 儲存格引用,如 "A1" "BC23"
CCellID cell;
if (ConvertStrToCellID(token, cell)) {
// 遞歸解析被引用儲存格的公式
CString val = ParseCell(cell.row, cell.col);
strOut += _T("(") + val + _T(")");
}
else {
strOut += _T("#INVALID!");
}
}
else if (nState == 2) { // 數值/常數
strOut += _T("(") + token + _T(")");
}
else { // 運算符直接輸出
strOut += token;
}
if (isOp) strOut += ch; // 輸出運算符
token.Empty();
nState = 0;
}
else if (_istalpha(ch)) { // 字母 -> 儲存格引用開始
if (nState == 0) nState = 1;
token += ch;
}
else if (_istdigit(ch)) { // 數字 -> 數值開始
if (nState == 0) nState = 2;
token += ch;
}
}
return strOut;
}
ConvertStrToCellID:將 "A1"、"BC23" 轉換為 CCellID(row, col) (0-based)。ParseCell(row, col):取得儲存格公式字串,遞歸呼叫 ParseExpression,需加入循環引用偵測 (visited set)。IsValidCol:驗證欄標字母合法性 (A-Z, AA-ZZ...)。// 僅建立可見儲存格的 CGridCellBase* 物件
void CGridCtrl::OnDraw(CDC* pDC)
{
CRect clip; pDC->GetClipBox(&clip);
int topRow = RowAtY(clip.top);
int bottomRow = RowAtY(clip.bottom);
int leftCol = ColAtX(clip.left);
int rightCol = ColAtX(clip.right);
for (int r = topRow; r <= bottomRow; ++r)
for (int c = leftCol; c <= rightCol; ++c)
GetCell(r, c)->Draw(pDC, r, c, GetCellRect(r, c));
}
SetRecordset(CRecordset*) 自動映射欄位,支援分頁載入。