I want to draw the line by clicking the mouse on the first coordinate where the line should start and second coordinate where the line should end.
When I run my project nothing happens.
I cannot find out what my code is missing.
LONG WINAPI WndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
BOOL fDraw = FALSE;
POINT ptPrevious = { 0 };
HPEN Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
switch (Message) {
case WM_LBUTTONDOWN: {
fDraw = TRUE;
ptPrevious.x = LOWORD(lParam);
ptPrevious.y = HIWORD(lParam);
break;
}
case WM_LBUTTONUP: {
if (fDraw)
{
hdc = GetDC(hWnd);
MoveToEx(hdc, ptPrevious.x, ptPrevious.y, NULL);
LineTo(hdc, LOWORD(lParam), HIWORD(lParam));
ReleaseDC(hWnd, hdc);
}
fDraw = FALSE;
break;
}
case WM_MOUSEMOVE: {
if (fDraw)
{
hdc = GetDC(hWnd);
MoveToEx(hdc, ptPrevious.x, ptPrevious.y, NULL);
LineTo(hdc, ptPrevious.x = LOWORD(lParam),
ptPrevious.y = HIWORD(lParam));
ReleaseDC(hWnd, hdc);
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0;
}
Fundamentally, the problem is because none of your data is persistent. Since they are all local variables, their content disappears when WndProc returns. fDraw is set to FALSE on every message, and will never be true for the WM_LBUTTONDOWN or WM_MOUSEMOVE messages. Therefore nothing happens.
You'll want to create some sort of a class to hold fDraw, ptPrevious, and a structure to hold the coordinates of the line(s) to draw. Use InvalidateRect in the WM_MOUSEMOVE and WM_LBUTTONUP messages. Then only draw them in response to a WM_PAINT message (using the DC provided in the paint message).
Using the CWnd MFC class can greatly simplify those tasks.
Because declared variables are local variables, variables can be declared with Static.
static BOOL fDraw = FALSE;
static POINT ptPrevious = { 0 };
In addition, you forget to use the SelectObject function to select pen into DC.
You can also use WM_PAINT redrawing to improve your code:
Example:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static BOOL fDraw = FALSE;
static BOOL fDraw_begin = FALSE;
static POINT ptPrevious;
static RECT rcClient;
static POINT pt;
static HPEN Pen;
switch (message)
{
case WM_CREATE:
{
Pen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
break;
}
case WM_LBUTTONDOWN:
{
ptPrevious.x = (LONG)LOWORD(lParam);
ptPrevious.y = (LONG)HIWORD(lParam);
return 0;
}
case WM_LBUTTONUP:
{
if (fDraw = TRUE)
{
fDraw = FALSE;
fDraw_begin = TRUE;
InvalidateRect(hwnd, &rcClient, TRUE);
UpdateWindow(hwnd);
}
return 0;
}
case WM_MOUSEMOVE:
{
if (wParam && MK_LBUTTON)
{
GetClientRect(hwnd, &rcClient);
hdc = GetDC(hwnd);
SetROP2(hdc, R2_NOTXORPEN);
if (!IsRectEmpty(&rcClient)) // Detecting whether the rectangular area is empty
{
MoveToEx(hdc, ptPrevious.x, ptPrevious.y, NULL);
LineTo(hdc, (LONG)LOWORD(lParam),
(LONG)HIWORD(lParam));
}
MoveToEx(hdc, ptPrevious.x, ptPrevious.y, NULL);
LineTo(hdc, (LONG)LOWORD(lParam),
(LONG)HIWORD(lParam));
pt.x = (LONG)LOWORD(lParam);
pt.y = (LONG)HIWORD(lParam);
fDraw = TRUE;
ReleaseDC(hwnd, hdc);
}
return 0;
}
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, Pen);
if (fDraw_begin)
{
fDraw_begin = FALSE;
MoveToEx(hdc, ptPrevious.x, ptPrevious.y, NULL);
LineTo(hdc, pt.x,
pt.y);
}
EndPaint(hwnd, &ps);
ReleaseDC(hwnd, hdc);
return 0;
}
case WM_DESTROY:
{
DeleteObject(Pen);
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
Do not get the DC three times, get it once and release it on mouse up.
Also, your drawing will be eventually validated, so you need to log the mouse move and repeat this stuff in WM_PAINT handler.
Related
I have a timer that is responsible for displaying the GIF image frame by frame. I noticed that when I right-clicked and hold the titilebar the timer pause for I think 1 second and when I left-clicked and hold the titlebar`, the timer pause until I released the mouse.
LRESULT CALLBACK GDIHelper::StaticControlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch(uMsg) {
case WM_TIMER:
{
OnTimer(); // Do something on timer.
InvalidateRect(staticControl, NULL, FALSE);
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics g(hdc);
g.DrawImage(m_pImage, 0, 0, width, height);
EndPaint(hwnd, &ps);
return TRUE;
}
case WM_DESTROY:
{
return 0;
}
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
case WM_CREATE: {
staticControl = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, xPosition, yPosition, width, height, hWnd, NULL, NULL, NULL); //create the static control.
SetWindowSubclass(staticControl, &StaticControlProc, unique_id, 0);
gdiHelper.AnimateGIF();
break;
}
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
//Paint other images and text here...
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
{
gdiHelper.Destroy();
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
and here are the functions responsible for creating the timer.
void GDIHelper::OnTimer() {
if(isPlayable) {
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
}
}
void GDIHelper::AnimateGIF() {
if(m_bIsPlaying == TRUE) {
return;
}
m_iCurrentFrame = 0;
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
SetTimer(staticControl, 120, ((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10, NULL);
++m_iCurrentFrame;
m_bIsPlaying = TRUE;
}
How to possibly prevent that?
The InvalidateRect() will not immediately redraw your control. It will simply schedule a future redraw for a specific rectangular area of the window. Making the animation freeze when you clicked or hold on the titlebar. Use InvalidateRect() followed by UpdateWindow(), this will forcefully perform immediate redrawing of the specified area of the window.
But it won't just solve the issue. Forget about your timer and use a non-blocking thread instead together with InvalidateRect() and UpdateWindow().
int animation_duration = 0;
void GDIHelper::TheAnimation() { //This function should be static.
if(m_bIsPlaying == TRUE) {
return;
}
m_iCurrentFrame = 0;
GUID Guid = FrameDimensionTime;
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
++m_iCurrentFrame;
m_bIsPlaying = TRUE;
animation_duration = ((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10;
while(isPlayable) { //Make sure to set isPlayable to false on destroy to stop this thread.
std::this_thread::sleep_for(std::chrono::milliseconds(animation_duration)); //instead of timer, use sleep.
m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
InvalidateRect(staticControl, NULL, FALSE);
UpdateWindow(staticControl); //perform immediate redrawing
}
}
LRESULT CALLBACK GDIHelper::StaticControlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch(uMsg) {
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics g(hdc);
g.DrawImage(m_pImage, 0, 0, width, height); //draw your image.
EndPaint(hwnd, &ps);
return TRUE;
}
....
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
Then just start the thread and use detach() for non-blocking UI.
std::thread t(TheAnimation);
t.detach(); // this will be non-blocking thread.
Read the comments in the code for some clarification and don't forget to set isPlayable to false on destroy to stop the thread.
I'm new and I'm trying to write a simple program drawing lines with the mouse.
I have a problem with drawing these lines, because it leaves traces behind.
Here is an image of my problem:
And here is a sample of my code:
LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:PostQuitMessage(0);break;
case WM_LBUTTONDOWN:
hdc = GetDC(hwnd);
last_x = LOWORD(lParam);
last_y = HIWORD(lParam);
isDown = true;
break;
case WM_MOUSEMOVE:
if (isDown)
{
Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
Box = (HPEN)SelectObject(hdc, Pen);
int x = LOWORD(lParam);
int y = HIWORD(lParam);
MoveToEx(hdc, last_x, last_y, NULL);
LineTo(hdc, x, y);
}
break;
case WM_LBUTTONUP:
isDown = false;
ReleaseDC;
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
EDIT:
Now it's working well, but if You could explain me one another thing, how can i make that my old lines stayed on the Client area when i drawing new lines? Cause now i can draw only one line. Should i use Bitmap to save screen or something?
EDIT:EDIT:
Ok i used Vector to save coordinates of every line. Thank You guys for help!
You are getting residual traces because you are not erasing your old drawings before drawing a new line, or at least updating last_x and last_y on each move so that a new line connects to the end of the previous line, like in Microsoft's example.
But, you really should not draw on the window directly in the mouse message handlers at all. As soon as the window needs a repaint for any reason, all of your drawing will be lost. The correct way to handle this is to perform all of your drawing in a WM_PAINT message handler instead. Use the mouse messages to keep track of line information as needed, and then do all of the actual drawing in WM_PAINT only.
For example:
LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
break;
case WM_DESTROY:
DeleteObject(Pen);
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
x = last_x = LOWORD(lParam);
y = last_y = HIWORD(lParam);
isDown = true;
InvalidateRect(hwnd, NULL, TRUE);
break;
case WM_MOUSEMOVE:
if (isDown)
{
x = LOWORD(lParam);
y = HIWORD(lParam);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_LBUTTONUP:
isDown = false;
InvalidateRect(hwnd, NULL, TRUE);
break;
/* if your WNDCLASS sets hbrBackground=NULL, uncomment this handler...
case WM_ERASEBKGND:
{
HDC hdc = (HDC) wParam;
draw a background on the hdc as needed...
return 1;
}
*/
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (last_x != x) || (last_y != y)
{
HPEN OldPen = (HPEN) SelectObject(hdc, Pen);
MoveToEx(hdc, last_x, last_y, NULL);
LineTo(hdc, x, y);
SelectObject(hdc, OldPen);
}
EndPaint(hwnd, &ps);
break;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
That will draw a single line that starts at the point where the mouse was first held down, and then follow the mouse as it moves around.
Or, if you want to draw multiple lines end-to-end that follow the mouse while it is held down, try this:
std::vector<POINT> points;
LRESULT APIENTRY WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
Pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
points.clear();
break;
case WM_DESTROY:
DeleteObject(Pen);
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
{
points.clear();
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
points.push_back(pt);
isDown = true;
InvalidateRect(hwnd, NULL, TRUE);
break;
}
case WM_MOUSEMOVE:
if (isDown)
{
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
points.push_back(pt);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_LBUTTONUP:
isDown = false;
InvalidateRect(hwnd, NULL, TRUE);
break;
/* if your WNDCLASS sets hbrBackground=NULL, uncomment this handler...
case WM_ERASEBKGND:
{
HDC hdc = (HDC) wParam;
draw a background on the hdc as needed...
return 1;
}
*/
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (points.size() > 1)
{
HPEN OldPen = (HPEN) SelectObject(hdc, Pen);
MoveToEx(hdc, points[0].x, points[0].y, NULL);
for (size_t i = 1; i < points.size(); ++i) {
LineTo(hdc, points[i].x, points[i].y);
}
SelectObject(hdc, OldPen);
}
EndPaint(hwnd, &ps);
break;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
I am late to party here and it may be obvious to experienced Win32 programmers, but I wanted to add an important note to Remy Lebeau example:
both the boolean isDown and the 4 ints x,y,last_x and last_y have to be static, or else the entire solution won't work.
I'm just trying to draw an ellipse:
case WM_PAINT:
hdc = BeginPaint(parentWindow, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(parentWindow, &ps);
, and then erase it with drawing a new ellipse with some new parameters every second using timer:
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
break;
case WM_TIMER:
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
break;
But ellipses are not erased and layered:
However, i tried to trace WM_ERASEBKGND and it really is sent every InvalidateRect.
Full code:
#include <Windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <iostream>
TCHAR szWindowClass[] = TEXT("CreateThreadWindow");
TCHAR szAppName[] = TEXT("CreateThreadExample");
BOOL InitWindow(HINSTANCE, int);
ATOM MyRegisterClass(HINSTANCE);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND parentWindow;
MSG msg;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MyRegisterClass(hInstance);
if (!InitWindow(hInstance, nCmdShow))
return FALSE;
BOOL bRet;
while ((bRet = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0)
{
if (bRet == -1)
return FALSE;
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASS wndClass;
memset(&wndClass, 0, sizeof(wndClass));
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hInstance;
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = szWindowClass;
return RegisterClass(&wndClass);
}
BOOL InitWindow(HINSTANCE hInstance, int nCmdShow)
{
parentWindow = CreateWindow(szWindowClass, szAppName, WS_OVERLAPPEDWINDOW,
300, 0, 600, 600, NULL, NULL, hInstance, NULL);
ShowWindow(parentWindow, nCmdShow);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
{
PAINTSTRUCT ps;
HDC hdc;
static int x = 0, y = 0, width = 200, height = 100;
switch (message) {
case WM_ERASEBKGND:
_RPT1(0, "%s\n", "erase");
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
break;
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
break;
case WM_TIMER:
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wparam, lparam);
}
}
Your code isn't erasing anything. It's just drawing an ellipse at the specified coordinates. The previously-drawn ellipse is still there.
You mention the WM_ERASEBKGND message, but there are two reasons why that isn't working for you:
In your window procedure (WndProc), you handle the WM_ERASEBKGND message explicitly, which means that it doesn't get passed to the default window procedure (DefWindowProc). A better way to write your window procedure would be the following:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
{
static int x = 0, y = 0, width = 200, height = 100;
switch (message) {
case WM_ERASEBKGND:
{
_RPT1(0, "%s\n", "erase");
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
return 0;
}
case WM_CREATE:
{
SetTimer(hWnd, 1, 1000, NULL);
break;
}
case WM_TIMER:
{
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
default:
break;
}
return DefWindowProc(hWnd, message, wparam, lparam);
}
Now, the default window procedure gets called every time, unless you explicitly return from inside of a case label.
When you register your window class (inside of MyRegisterClass), you zero all fields of the WNDCLASS structure and then explicitly initialize a couple of them. You don't explicitly initialize the hbrBackground field, so it is being set to 0. And when hbrBackground is 0,
When this member is NULL, an application must paint its own background whenever it is requested to paint in its client area. To determine whether the background must be painted, an application can either process the WM_ERASEBKGND message or test the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function.
This means that the default window procedure isn't doing anything in response to the WM_ERASEBKGND message because you didn't give your window a background brush.
You will either need to set hbrBackground to something like COLOR_WINDOW + 1, or you will need to add code to your WM_ERASEBKGND message handler to erase the window's background yourself.
Or, perhaps an even better option would be to forget about the WM_ERASEBKGND message altogether, as many Windows programmers do, because this two-step erase-and-paint approach tends to cause flicker. Leave the hbrBackground field set to NULL, don't do anything in response to the WM_ERASEBKGND message, and do your erasing at the top of the WM_PAINT handler:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// Erase background of entire client area.
RECT rcClient;
GetClientRect(hWnd, &rcClient);
FillRect(hdc, &rcClient, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
// Do normal drawing.
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
return 0;
}
The problem I am having is that when I draw my line, the screen flickers every time it redraws. I just can't quite figure out how to not make it flicker. I understand the flicker is coming from me redrawing the client area hundreds of times a second as I move my mouse with my left button down but how would I be able to get around this?
LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
static HDC hdc;
PAINTSTRUCT ps;
RECT rect;
RECT size;
static POINT point1;
static POINT point2;
static HBRUSH origBrush;
static bool drawingLine = false;
switch (message)
{
case WM_CREATE:
origBrush = CreateSolidBrush(RGB(0, 0 , 0) );
break;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps);
GetClientRect (hwnd, &rect);
GetWindowRect(hwnd, &size);
Rectangle(hdc, 10, 10, 80, 80 );
if(drawingLine == true)
{
MoveToEx(hdc, point1.x, point1.y, NULL);
LineTo(hdc, point2.x, point2.y);
}
EndPaint (hwnd, &ps);
return 0;
//Has all the commands that exist in your program.
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_COLOR_RED:
break;
case ID_COLOR_BLUE:
break;
case ID_COLOR_BLACK:
break;
case ID_THICKNESS_1:
break;
case ID_THICKNESS_2:
break;
case ID_THICKNESS_3:
break;
}
break;
case WM_LBUTTONDOWN:
drawingLine = true;
SelectObject(hdc, GetStockObject(BLACK_BRUSH));
point1.x = LOWORD(lParam);
point1.y = HIWORD(lParam);
MoveToEx(hdc, point1.x, point1.y, NULL);
break;
case WM_MOUSEMOVE:
point2.x = LOWORD(lParam);
point2.y = HIWORD(lParam);
InvalidateRect(hwnd, NULL, 1);
break;
case WM_LBUTTONUP:
point2.x = LOWORD(lParam);
point2.y = HIWORD(lParam);
drawingLine = false;
break;
//Causes the program to exit.
case WM_DESTROY:
PostQuitMessage(0);
break;
}
The main thing to do is respond to WM_ERASEBKGND and return true to prevent the default re-drawing of the background.
Then you'll need to erase the old line you before drawing the new one. Just for example, you might save bits from under the line, draw it, then restore those (and only those) pixels before drawing the line, so when you need to erase it, you can just restore those pixels to erase it.
The obvious alternative (that's usually simpler though theoretically at least a little slower) is to use double buffering. Do roughly the drawing you are now, but to an off-screen bitmap. Then, when it's all complete just BitBlt from there to the screen.
I am trying to draw a rectangle as you left click on a point and then drag across , while your dragging across the rectangle gets form to show you a preview of how the rectangle will look . This works fine except when I drag back into a smaller rectangle the old rectangles still remain.
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
if(painting)
{
HPEN greenPen=CreatePen(PS_SOLID, 1, RGB(0,255,0));
SelectObject(hdc,greenPen);
Rectangle(hdc, x1, y1, x2, y2);
}
EndPaint(hWnd, &ps);
break;
case WM_LBUTTONDOWN:
painting=true;
x1=LOWORD(lParam);
y1=HIWORD(lParam);
InvalidateRect(hWnd, NULL ,false);
break;
case WM_MOUSEMOVE:
x2=LOWORD(lParam);
y2=HIWORD(lParam);
InvalidateRect(hWnd, NULL ,false);
break;
case WM_LBUTTONUP:
painting=false;
break;
#user1788175 - just use a RECT struct. When the drawing starts, you set the left & top members to the x,y pos of the mouse. When the mouse is released, set the right,bottom members. Swap left,right if necessary, to ensure that left
Here's some code ripped from a class I wrote to deal with with selection rectangles. You can ignore the normalize code - it just converts the pixel coordinates to ones in the range [0..1], so I can draw the selection on a scaled-down version of the image, yet still select the same region if the image is shown at a different scale. My images were 4944x6992, so I had to display a scaled-down version of them.
LRESULT CALLBACK cSelBoxImg::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
RECT mappedRect, tmpRect, myRect;
HBRUSH myBrush;
BITMAP bm;
PAINTSTRUCT ps;
HDC mDC;
HBITMAP tmpBmp;
switch (uMsg)
{
case WM_LBUTTONDOWN:
if (bMouseSelectionEnabled)
{
bRubberBanding = true;
mouseClickDownPos.x = LOWORD(lParam);
mouseClickDownPos.y = HIWORD(lParam);
curMousePos = mouseClickDownPos;
selectionRect.left = min(curMousePos.x, mouseClickDownPos.x);
selectionRect.right = max(curMousePos.x, mouseClickDownPos.x);
selectionRect.top = min(curMousePos.y, mouseClickDownPos.y);
selectionRect.bottom = max(curMousePos.y, mouseClickDownPos.y);
normalizeSelRect();
InvalidateRect(mHwnd, NULL, isBkgTransparent);
PostMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), (LPARAM)hWnd);
}
return 1;
case WM_LBUTTONUP:
if (bMouseSelectionEnabled)
if (bRubberBanding)
{
bRubberBanding = false;
mouseClickUpPos.x = LOWORD(lParam);
mouseClickUpPos.y = HIWORD(lParam);
selectionRect.left = min(mouseClickUpPos.x, mouseClickDownPos.x);
selectionRect.right = max(mouseClickUpPos.x, mouseClickDownPos.x);
selectionRect.top = min(mouseClickUpPos.y, mouseClickDownPos.y);
selectionRect.bottom = max(mouseClickUpPos.y, mouseClickDownPos.y);
normalizeSelRect();
InvalidateRect(mHwnd, NULL, isBkgTransparent);
PostMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), (LPARAM)hWnd);
}
return 1;
case WM_MOUSEMOVE:
if (bMouseSelectionEnabled)
if (bRubberBanding)
{
curMousePos.x = LOWORD(lParam);
curMousePos.y = HIWORD(lParam);
selectionRect.left = min(curMousePos.x, mouseClickDownPos.x);
selectionRect.right = max(curMousePos.x, mouseClickDownPos.x);
selectionRect.top = min(curMousePos.y, mouseClickDownPos.y);
selectionRect.bottom = max(curMousePos.y, mouseClickDownPos.y);
// UpdateWindow(hWnd);
//RedrawWindow(hWnd, NULL, NULL, RDW_UPDATENOW);
normalizeSelRect();
InvalidateRect(hWnd, NULL, false);
PostMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), (LPARAM)hWnd);
// printf("Message posted\n");
}
return 0;
case WM_PAINT:
mDC = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &tmpRect);
// GetObject(mBmp, sizeof(bm), &bm);
mappedRect.left = mLeft * tmpRect.right;
mappedRect.right = mRight * tmpRect.right;
mappedRect.top = mTop * tmpRect.bottom;
mappedRect.bottom = mBottom * tmpRect.bottom;
displayImage();
if (mBmp) drawRect(mDC, mappedRect, RGB(0,0,255));
DeleteObject(tmpBmp);
EndPaint(hWnd, &ps);
return 0;
case WM_ERASEBKGND:
if (isBkgTransparent)
{
GetClientRect(hWnd, &myRect);
myBrush = (HBRUSH) GetWindowLong(hWnd, GCL_HBRBACKGROUND);
FillRect((HDC)wParam, &myRect, myBrush);
printf("background cleared\n");
}
return true;
case WM_SETCURSOR:
SetCursor(mCursor);
return true;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}