C++实现自定义撤销重做功能的示例代码
CodeOfCC 人气:0前言
在使用c++做界面开发的时候,需要涉及到到撤销重做操作,尤其是实现白板功能时需要自己实现一套撤销重做功能,如果是qt则有QUndoable对象,可以直接拿来用。但是如果是使用gdi绘图,则可能需要自己实现了。
一、完整代码
由于需要的功能相对简单,这里就不做原理以及接口设计思路说明了,直接展示完整代码。
Undoable.h
#ifndef AC_UNDOABLE_H #define AC_UNDOABLE_H #include<functional> #include<vector> namespace AC { /// <summary> /// 撤销重做管理对象 /// 最少行数实现,采用vector+下标浮动实现,性能也是很好的。 /// ceate by xin 2022.7.5 /// </summary> class Undoable { public: /// <summary> /// 撤销 /// </summary> void Undo(); /// <summary> /// 重做 /// </summary> void Redo(); /// <summary> /// 清除 /// </summary> void Clear(); /// <summary> /// 添加操作 /// </summary> /// <param name="undo">撤销</param> /// <param name="redo">重做</param> void Add(std::function<void()> undo, std::function<void()> redo); private: //记录的步骤 std::vector<std::pair<std::function<void()>, std::function<void()>>> _steps; //当前操作的下标 int _index = -1; }; } #endif
Undoable.cpp
#include "Undoable.h" namespace AC { void Undoable::Undo(){ if (_index > -1) _steps[_index--].first(); } void Undoable::Redo(){ if (_index < (int)_steps.size() - 1) _steps[++_index].second(); } void Undoable::Clear(){ _steps.clear(); _index = -1; } void Undoable::Add(std::function<void()> undo, std::function<void()> redo){ if (++_index < (int)_steps.size()) _steps.resize(_index); _steps.push_back({ undo ,redo }); } }
二、使用示例
1、基本用法
//1、定义撤销重做管理对象 AC::Undoable undoable; //2、添加操作 undoable.Add( [=]() { //撤销逻辑 }, [=]() { //重做逻辑 } ); //3、撤销重做 //执行撤销 undoable.Undo(); //执行重做 undoable.Redo();
2、gdi画线撤销
#include<Windows.h> #include<Windowsx.h> #include<vector> #include "Undoable.h" //笔画集合 std::vector<std::vector<POINT>*> strokes; //窗口上下文 HDC hdc; int xPos = 0; int yPos = 0; //撤销重做管理对象 AC::Undoable undoable; //窗口过程 static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_LBUTTONDOWN: //记录笔画起点 xPos = GET_X_LPARAM(lParam); yPos = GET_Y_LPARAM(lParam); strokes.push_back(new std::vector<POINT>()); strokes.back()->push_back({ xPos ,yPos }); MoveToEx(hdc, xPos, yPos, NULL); break; case WM_MOUSEMOVE: if ((wParam & MK_LBUTTON) != 0) //绘制笔画 { xPos = GET_X_LPARAM(lParam); yPos = GET_Y_LPARAM(lParam); LineTo(hdc, xPos, yPos); strokes.back()->push_back({ xPos ,yPos }); } break; case WM_LBUTTONUP: //记录笔画撤销重做 { auto currentStroke = strokes.back(); //补全一个点 if (strokes.back()->size()==1) { xPos = GET_X_LPARAM(lParam); yPos = GET_Y_LPARAM(lParam); LineTo(hdc, xPos, yPos); } //记录操作 undoable.Add( [=]() { //撤销逻辑 strokes.pop_back(); RECT rect; GetClientRect(hwnd, &rect); InvalidateRect(hwnd, &rect, 0); }, [=]() { //重做逻辑 strokes.push_back(currentStroke); RECT rect; GetClientRect(hwnd, &rect); InvalidateRect(hwnd, &rect, 0); } ); } break; case WM_PAINT: { //重绘笔画 RECT rect; GetClientRect(hwnd, &rect); FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); for (auto i : strokes) { MoveToEx(hdc, i->front().x, i->front().y, NULL); for (auto j : *i) { LineTo(hdc, j.x, j.y); } } } break; case WM_KEYDOWN: //响应消息 if (wParam == 'Z') { //执行撤销 undoable.Undo(); } if (wParam == 'Y') { //执行重做 undoable.Redo(); } break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProc(hwnd, msg, wParam, lParam); } void main() { //创建win32窗口 WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = GetModuleHandle(NULL); wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = 0; wndclass.lpszClassName = L"Win32Window"; if (!RegisterClass(&wndclass)) { MessageBox(NULL, TEXT("创建窗口失败!"), L"", MB_ICONERROR); } auto hwnd = CreateWindowW( L"Win32Window", L"重做撤销", WS_OVERLAPPEDWINDOW, 0, 0, 640, 360, NULL, NULL, GetModuleHandle(NULL), NULL ); //显示窗口 ShowWindow(hwnd, SW_SHOW); //获取窗口上下文 hdc = GetDC(hwnd); HPEN pen = ::CreatePen(PS_SOLID, 9, RGB(255, 0, 0));//创建一支红色的画笔 auto oldPen = SelectObject(hdc, pen); MSG msg; HACCEL hAccelTable = NULL; // 主消息循环: while (GetMessage(&msg, NULL, 0, 0)) { switch (msg.message) { case WM_PAINT: break; case WM_DESTROY: PostQuitMessage(0); break; } if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } //销毁资源 SelectObject(hdc, oldPen); DeleteObject(oldPen); ReleaseDC(hwnd, hdc); DestroyWindow(hwnd); }
效果预览
总结
以上就是今天要讲的内容,本文展示的撤销重做管理对象包含的功能可以满足大部分使用场景,而且由于实现非常简洁,很容易修改和拓展。这个对象经过笔者多个项目修改演变而成,最初的实现是使用两个栈来记录操作,其实并不是最优的,变长数组加下标记录应该是更好的方式。
加载全部内容