成熟系統必備復原與重做功能。傳統做法完整備份文件資料佔用大量記憶體,導致步數受限;全畫面重繪造成閃爍。本文提出臨時檔案模擬堆疊機制:僅儲存操作變化量(差異資料),復原/重做時依變化量還原,並針對性更新視圖,達到記憶體高效、無限步數、零閃爍。
// Undo/Redo 資料排放示意(m_UndoDataList)
// ====
// |###| } Redo 資料(堆疊頂端)
// |###| }
// |###| }
// ||| } 分界線
// ||| } Undo 資料(堆疊底部)
// ||| }
// =====
if (m_File.Open(m_TempPath, CFile::modeCreate | CFile::modeReadWrite | CFile::shareExclusive)) {
m_File.SeekToBegin();
m_UndoCount = 0; // 可復原步數
m_RedoCount = 0; // 可重做步數
}
int CRedoUndoEngine::PushData(LPVOID pData, DWORD size, DWORD param1, DWORD param2, int* pIndex) {
// 1. 清除 Redo 資料
if (m_RedoCount) {
while (m_RedoCount--) delete (LPISEEUNDOINFO)m_UndoDataList.RemoveTail();
m_RedoCount = 0;
}
// 2. 填寫索引資訊
LPISEEUNDOINFO pInfo = new ISEEUNDOINFO;
pInfo->m_index = m_UndoCount;
pInfo->m_UserData1 = param1; // 操作類型標識
pInfo->m_UserData2 = param2;
pInfo->m_DataSize = size;
pInfo->m_FilePosition = _get_current_overwrite_pos(); // 臨時檔寫入位置
m_UndoDataList.AddTail(pInfo);
// 3. 寫入使用者資料到臨時檔
m_File.Seek(pInfo->m_FilePosition, CFile::begin);
m_File.Write(pData, size);
m_UndoCount++;
::GlobalFree(pData); // 釋放呼叫端記憶體
return 1;
}
int CIUndoEngine::RedoData(LPVOID* ppData, DWORD* pSize, DWORD* pParam1, DWORD* pParam2, int* pIndex) {
if (!m_RedoCount) return 0;
POSITION pos = m_UndoDataList.FindIndex(m_UndoCount);
LPISEEUNDOINFO pInfo = (LPISEEUNDOINFO)m_UndoDataList.GetAt(pos);
m_File.Seek(pInfo->m_FilePosition, CFile::begin);
m_File.Read(*ppData, pInfo->m_DataSize);
m_UndoCount++; m_RedoCount--;
if (pSize) *pSize = pInfo->m_DataSize;
if (pParam1) *pParam1 = pInfo->m_UserData1;
if (pParam2) *pParam2 = pInfo->m_UserData2;
if (pIndex) *pIndex = m_RedoCount;
return 1;
}
#define REUNDO_MOV 0x0001 // 圖元移動
#define REUNDO_SEL 0x0002 // 選取變更
// 移動操作前
int nByte = 4 * sizeof(DWORD);
HGLOBAL hMem = GlobalAlloc(GMEM_FIXED, nByte);
LPVOID pData = GlobalLock(hMem);
memcpy((DWORD*)pData, &m_oldPoint, 2 * sizeof(DWORD)); // 移動前座標
memcpy((DWORD*)pData + 2, &point, 2 * sizeof(DWORD)); // 移動後座標
m_pReUndoEngine->PushData(pData, nByte, REUNDO_MOV, 0, NULL);
void CMarkView::MovUndo(LPVOID pData) {
CPoint pt1, pt2;
memcpy(&pt1, (DWORD*)pData, 8);
memcpy(&pt2, (DWORD*)pData + 2, 8);
// 計算位移量,將圖元移回原位
// 僅更新該圖元視圖區域,避免全畫面重繪
}
MovRedo 邏輯類似,套用移動後座標。
引擎提供精確變化量,視圖層僅根據座標差異呼叫 InvalidateRect 更新局部區域,而非 Invalidate() 全域重繪。配合 OnEraseBkgnd 回傳 TRUE 並雙緩衝繪圖,徹底消除閃爍。
| 傳統全量備份 | 本文差異堆疊法 |
|---|---|
| 記憶體隨步數線性增長 | 臨時檔儲存,記憶體常數級 |
| 步數受限 | 理論無限步數(受磁碟空間限制) |
| 全域重繪閃爍 | 局部更新零閃爍 |
| 實作複雜 | 引擎封裝,業務邏輯單純 |
以臨時檔模擬堆疊、僅儲存操作差異量,配合引擎封裝與局部視圖更新,完美解決復原/重做的記憶體、步數、閃爍三大痛點。此架構已成功應用於商業級 CAD 系統,穩定性與效能俱佳,強烈推薦給開發文件導向應用的工程師參考採用。