Я использую Win32 API для создания программы рисования, и когда я пытаюсь решить эту проблему с помощью мыши для рисования линий, остаются линии на холсте, это не работает должным образом.
Это функция процесса Win:
LRESULT MainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int canvasWidth = 1200; //初始的画布大小, initialize canvas width
static int canvasHeight = 700; //初始的画布大小, initialize canvas height
static int workWidth = canvasWidth + 60; //设定的工作区大小, set work area width
static int workHeight = canvasHeight + 60; //设定的工作区大小, set work area height
static std::vector<Scroll*> scrolls; //滚动条容器, Scroll container
static RECT clientRC; //窗口客户区矩形, window client rectangle
static HDC hMemDC; //内存设备上下文, memory device context
static HBITMAP hMemBM; //内存位图, memory bitmap
static POINT ptPreviouse; //鼠标上一次位置, previous mouse position
static POINT ptCurrent; //鼠标当前位置, current mouse position
static bool bDraw = false; //是否开始绘制, whether to start drawing
static HDC hDC; //绘图设备上下文, drawing device context
static RECT canvasRect; //画布区域, canvas area
TRACKMOUSEEVENT mouseEvent; //鼠标追踪事件, mouse track event
static int CanvasLTop_X = 0; //画布的左上x坐标, canvas left top x
static int CanvasLTop_Y = 0; //画布的左上y坐标, canvas left top y
static int OldCanvasLTop_X = 0; //上一个画布的左上x坐标, previous canvas left top x
static int OldCanvasLTop_Y = 0; //上一个画布的左上y坐标, previous canvas left top y
static int VscrollPos = 0; //垂直滚动条位置, vertical scroll bar position
static int HscrollPos = 0; //水平滚动条位置, horizontal scroll bar position
switch (message)
{
case WM_COMMAND:
{
// 分析菜单选择:
//analysis menu selection
switch (LOWORD(wParam))
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, MainWindow::About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case IDC_BUTTON1:
// TODO: 在此处添加按钮事件处理代码...
//TODO: add button event handling code here...
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_CREATE:
{
// TODO: 在此处添加控件创建代码...
//TODO: add control creation code here...
GetClientRect(hWnd, &clientRC);
hDC = GetDC(hWnd);
//创建垂直滚动条
//create vertical scroll bar
Scroll* VerticalScroll = new Scroll(hWnd, SB_VERT, 0, workHeight, clientRC.bottom);
scrolls.push_back(VerticalScroll);
//创建水平滚动条
//create horizontal scroll bar
Scroll* HorizontalScroll = new Scroll(hWnd, SB_HORZ, 0, workWidth, clientRC.right);
scrolls.push_back(HorizontalScroll);
//创建内存设备上下文
//create memory device context
hMemDC = CreateCompatibleDC(hDC);
hMemBM= CreateCompatibleBitmap(hDC, canvasWidth, canvasHeight);
SelectObject(hMemDC, hMemBM);
PatBlt(hMemDC, 0, 0, clientRC.right, clientRC.bottom, WHITENESS);
ReleaseDC(hWnd, hDC);
}
break;
case WM_DRAWITEM:
{
DRAWITEMSTRUCT* ptrDIS = (DRAWITEMSTRUCT*)lParam; // 转换为DRAWITEMSTRUCT结构, convert to DRAWITEMSTRUCT structure
// TODO: 在此处添加绘制控件代码...
//TODO: add control drawing code here...
}
break;
case WM_SIZE:
{
// TODO: 在此处添加大小变化事件处理代码...
//TODO: add size change event handling code here...
hDC = GetDC(hWnd);
//将内容保存至临时位图
//copy content to temporary bitmap
HDC tempDC = CreateCompatibleDC(hDC);
HBITMAP tempBM = CreateCompatibleBitmap(hDC, clientRC.right, clientRC.bottom);
SelectObject(tempDC, tempBM);
BitBlt(tempDC, 0, 0, clientRC.right, clientRC.bottom, hMemDC, 0, 0, SRCCOPY);
GetClientRect(hWnd, &clientRC);
//更新滚动条信息
//update scroll bar information
for (Scroll* scroll : scrolls)
{
if (scroll->widgeType == SB_VERT)
{
SCROLLINFO newVSI = scroll->GetScrollStruct();
newVSI.cbSize = sizeof(SCROLLINFO);
newVSI.fMask = SIF_ALL;
newVSI.nMax = workHeight;
newVSI.nPage = clientRC.bottom;
scroll->UpdateScrollInfo(hWnd, newVSI);
}
if (scroll->widgeType == SB_HORZ)
{
SCROLLINFO newHSI = scroll->GetScrollStruct();
newHSI.cbSize = sizeof(SCROLLINFO);
newHSI.fMask = SIF_ALL;
newHSI.nMax = workWidth;
newHSI.nPage = clientRC.right;
scroll->UpdateScrollInfo(hWnd, newHSI);
}
}
//计算画布的新位置
//calculate new canvas position
if (clientRC.right - 45 < canvasWidth) CanvasLTop_X = 30;
else CanvasLTop_X = (clientRC.right - canvasWidth) / 2;
if (clientRC.bottom - 45 < canvasHeight) CanvasLTop_Y = 30;
else CanvasLTop_Y = (clientRC.bottom - canvasHeight) / 2;
//将临时位图内容复制到内存设备上下文
//copy temporary bitmap content to memory device context
hMemDC= CreateCompatibleDC(hDC);
hMemBM = CreateCompatibleBitmap(hDC, clientRC.right, clientRC.bottom);
SelectObject(hMemDC, hMemBM);
PatBlt(hMemDC, 0, 0, clientRC.right, clientRC.bottom, WHITENESS);
BitBlt(hMemDC, CanvasLTop_X, CanvasLTop_Y, canvasWidth, canvasHeight, tempDC, OldCanvasLTop_X, OldCanvasLTop_Y, SRCCOPY);
//更新画布坐标
//update canvas coordinates
OldCanvasLTop_X = CanvasLTop_X;
OldCanvasLTop_Y = CanvasLTop_Y;
DeleteObject(tempBM);
DeleteDC(tempDC);
}
break;
case WM_PAINT:
{
// TODO: 在此处添加使用 hdc 的任何绘图代码...
//TODO: add any drawing code using hdc here...
PAINTSTRUCT ps;
hDC = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &clientRC);
//获取滚动条位置
//get scroll bar position
for (Scroll* scroll : scrolls)
{
if (scroll->widgeType == SB_VERT)
{
VscrollPos = scroll->GetScrollPos(hWnd);
}
if (scroll->widgeType == SB_HORZ)
{
HscrollPos = scroll->GetScrollPos(hWnd);
}
}
//跟新画布区域
//update canvas area
canvasRect = { CanvasLTop_X - HscrollPos, CanvasLTop_Y - VscrollPos , CanvasLTop_X + canvasWidth - HscrollPos, CanvasLTop_Y + canvasHeight - VscrollPos };
//将内存设备上下文内容复制到窗口设备上下文
//copy memory device context content to drawing device context
BitBlt(hDC, canvasRect.left, canvasRect.top, canvasWidth, canvasHeight, hMemDC, canvasRect.left, canvasRect.top, SRCCOPY);
EndPaint(hWnd, &ps);
ReleaseDC(hWnd, hDC);
}
break;
case WM_VSCROLL:
{
// TODO: 在此处添加垂直滚动条事件处理代码...
//TODO: add vertical scroll bar event handling code here...
SCROLLINFO verticalSI;
for (Scroll* scroll : scrolls)
{
if (scroll->widgeType == SB_VERT)
{
verticalSI = scroll->GetScrollStruct();
int newPos = scroll->GetScrollPos(hWnd);
switch (LOWORD(wParam))
{
case SB_LINEUP:
newPos -= 10;
break;
case SB_LINEDOWN:
newPos += 10;
break;
case SB_PAGEUP:
newPos -= verticalSI.nPage;
break;
case SB_PAGEDOWN:
newPos += verticalSI.nPage;
break;
case SB_THUMBTRACK:
newPos = verticalSI.nTrackPos;
break;
}
if (newPos < 0) newPos = 0;
if (newPos > verticalSI.nMax - verticalSI.nPage) newPos = verticalSI.nMax - verticalSI.nPage;
ScrollWindowEx(hWnd, 0, scroll->GetScrollPos(hWnd) - newPos, nullptr, nullptr, nullptr, nullptr, SW_INVALIDATE | SW_ERASE);
scroll->UpdateScrollInfo(hWnd, newPos);
UpdateWindow(hWnd);
break;
}
}
}
break;
case WM_HSCROLL:
{
// TODO: 在此处添加水平滚动条事件处理代码...
//TODO: add horizontal scroll bar event handling code here...
for (Scroll* scroll : scrolls)
{
if (scroll->widgeType == SB_HORZ)
{
SCROLLINFO horizontalSI = scroll->GetScrollStruct();
int newPos = scroll->GetScrollPos(hWnd);
switch (LOWORD(wParam))
{
case SB_LINEUP:
newPos -= 10;
break;
case SB_LINEDOWN:
newPos += 10;
break;
case SB_PAGEUP:
newPos -= horizontalSI.nPage;
break;
case SB_PAGEDOWN:
newPos += horizontalSI.nPage;
break;
case SB_THUMBTRACK:
newPos = horizontalSI.nTrackPos;
break;
}
if (newPos < 0) newPos = 0;
if (newPos > horizontalSI.nMax - horizontalSI.nPage) newPos = horizontalSI.nMax - horizontalSI.nPage;
ScrollWindowEx(hWnd, scroll->GetScrollPos(hWnd) - newPos, 0, nullptr, nullptr, nullptr, nullptr, SW_INVALIDATE | SW_ERASE);
scroll->UpdateScrollInfo(hWnd, newPos);
UpdateWindow(hWnd);
break;
}
}
}
break;
case WM_LBUTTONDOWN:
{
hDC = GetDC(hWnd);
bDraw = true;
ptPreviouse.x = LOWORD(lParam);
ptPreviouse.y = HIWORD(lParam);
}
break;
case WM_MOUSEMOVE:
{
if (bDraw)
{
MoveToEx(hMemDC, ptPreviouse.x, ptPreviouse.y, nullptr);
LineTo(hMemDC, LOWORD(lParam), HIWORD(lParam));
BitBlt(hDC, canvasRect.left, canvasRect.top, canvasWidth, canvasHeight, hMemDC, canvasRect.left, canvasRect.top, SRCCOPY);
ptPreviouse.x = LOWORD(lParam);
ptPreviouse.y = HIWORD(lParam);
mouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
mouseEvent.dwFlags = TME_LEAVE;
mouseEvent.hwndTrack = hWnd;
TrackMouseEvent(&mouseEvent);
}
}
break;
case WM_LBUTTONUP:
{
bDraw = false;
ReleaseDC(hWnd, hDC);
}
break;
case WM_MOUSELEAVE:
{
bDraw = false;
ReleaseDC(hWnd, hDC);
}
break;
case WM_DESTROY:
{
//释放scrolls容器中的对象
//release objects in scrolls container
for (Scroll* scroll : scrolls)
{
delete scroll;
}
scrolls.clear();
PostQuitMessage(0);
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Эффект:
Первый розыгрыш
Прокрутите окно вниз:
Прокрутите назад:
Второй розыгрыш
Не рисуйте прямо в окне за пределами обработчика сообщений WM_PAINT
. Все, что вы нарисуете, будет стерто при следующей перекраске окна.
У вас есть несколько вариантов:
Пусть ваш обработчик мыши сохранит координаты где-нибудь в списке, а затем обработчик WM_PAINT
(пере) нарисует все строки, находящиеся в данный момент в списке.
Создайте в памяти растровое изображение того же размера, что и ваше окно, и обработчик мыши будет рисовать линии на этом растровом изображении по мере необходимости. Всякий раз, когда растровое изображение меняется, вызывайте InvalidateRect()
, чтобы вызвать перерисовку окна. Затем обработчик WM_PAINT
нарисует текущее растровое изображение как есть в окне.
Похоже, вы пытаетесь использовать второй подход, за исключением того, что ваш обработчик WM_MOUSEMOVE
сразу рисует растровое изображение в окне, а не ждет прибытия следующего WM_PAINT
сообщения.
Попробуйте еще что-нибудь вроде этого:
RESULT MainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
switch (message)
{
...
case WM_PAINT:
{
PAINTSTRUCT ps;
hDC = BeginPaint(hWnd, &ps);
...
BitBlt(hDC, ..., hMemDC, ...);
...
EndPaint(hWnd, &ps);
}
break;
...
case WM_LBUTTONDOWN:
{
bDraw = true;
ptPreviouse.x = GET_X_LPARAM(lParam);
ptPreviouse.y = GET_Y_LPARAM(lParam);
}
break;
case WM_MOUSEMOVE:
{
if (bDraw)
{
MoveToEx(hMemDC, ptPreviouse.x, ptPreviouse.y, nullptr);
LineTo(hMemDC, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
ptPreviouse.x = GET_X_LPARAM(lParam);
ptPreviouse.y = GET_Y_LPARAM(lParam);
InvalidateRect(hWnd, nullptr, TRUE);
...
}
}
break;
case WM_LBUTTONUP:
case WM_MOUSELEAVE:
{
bDraw = false;
}
break;
...
}
return 0;
}
Большое спасибо за ваш ответ. У меня вопрос: если размер окна изменится, размер HBITMAP "hMemBM" необходимо обновить. Итак: ``` case WM_SIZE: { .... hMemBM = CreateCompatibleBitmap(hDC, clientRC.right, clientRC.bottom); ..... } ``` Содержимое hMemBM будет очищено. Итак, следует ли мне создавать временное растровое изображение во время записи моего кода, чтобы временно сохранить содержимое необновленного hMemBM?
Да, вам нужно создать новое растровое изображение памяти и постоянный ток памяти и скопировать старые пиксели в новое растровое изображение. Вы делаете это, но вам также придется освободить старых, чего вы не делаете.
Я понимаю. Еще раз спасибо
К слову: ваш
WM_SIZE
пропускает старыеhMemDC
иhMemBM
объекты при изменении размера окна. Необходимо освободить их. Кроме того,CreateCompatibleDC()
изначально создаетHDC
с растровым изображением 1x1, которое вы также теряете после замены его наSelectObject()
. Вам нужно либо освободить это исходное растровое изображение, либо сохранить его и восстановить перед освобождениемhMemDC
.