C++实现自定义撤销重做功能的示例代码
作者:CodeOfCC
在使用c++做界面开发的时候,尤其是实现白板功能时需要自己实现一套撤销重做功能.如果是qt则有QUndoable对象,可以直接拿来用。但是如果是使用gdi绘图,则可能需要自己实现了。本文就来用C++实现自定义撤销重做功能,需要的可以参考一下
前言
在使用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); }
效果预览
总结
以上就是今天要讲的内容,本文展示的撤销重做管理对象包含的功能可以满足大部分使用场景,而且由于实现非常简洁,很容易修改和拓展。这个对象经过笔者多个项目修改演变而成,最初的实现是使用两个栈来记录操作,其实并不是最优的,变长数组加下标记录应该是更好的方式。
到此这篇关于C++实现自定义撤销重做功能的示例代码的文章就介绍到这了,更多相关C++撤销重做功能内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!