Draw, Select and Drag Rectangle WinApi - winapi

I have a problem with dragging the rectangles and also with selecting them.
I have to draw multiple rectangles (with the left mouse button), then select one of them and drag it all around the window (with the right mouse button). But when I select it, it doesn't move.
The problem with the selection - when I try to select them in descending order (of the way a draw them) they are selected all.
Here is my code:
POINT LeftButtonDown;
POINT ptCurrent;
POINT ptClicked;
vector<CRect> vRect;
bool isRubberBand = false;
bool IsClicked(CRect r, int x, int y);
void select(HWND hWnd, CRect r);
void deselect(HWND hWnd, CRect r);
void DrawRubberBand(HWND hWnd);
void MoveFromTo(HDC hdc, CRect rectSelected, POINTS anchor, POINTS now);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
CRect add;
CRect rectSelected;
HDC hdc;
static POINTS anchor;
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
for (auto& rc : vRect)
{
Rectangle(hdc,
rc.left,
rc.top,
rc.right,
rc.bottom
);
}
EndPaint(hWnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
LeftButtonDown.x = LOWORD(lParam); //Start point
LeftButtonDown.y = HIWORD(lParam);
isRubberBand = true;
ptCurrent.x = LOWORD(lParam); //current point
ptCurrent.y = HIWORD(lParam);
DrawRubberBand(hWnd); //draw the rect
}
break;
case WM_LBUTTONUP: // adding to a vector
{
if (!isRubberBand)
{
return 0;
}
isRubberBand = false;
//InvalidateRect(hWnd, NULL, TRUE);
add.SetRect(LeftButtonDown.x, LeftButtonDown.y, ptCurrent.x, ptCurrent.y); //setting the coords of the rect
vRect.push_back(add); //add the rect
UpdateWindow(hWnd);
}
break;
case WM_MOUSEMOVE:
if (wParam & MK_LBUTTON) //drawing the rectangle
{
hdc = GetDC(hWnd);
if (!isRubberBand)
{
break;
}
DrawRubberBand(hWnd);
ptCurrent.x = LOWORD(lParam);
ptCurrent.y = HIWORD(lParam);
DrawRubberBand(hWnd);
}
if (wParam & MK_RBUTTON) //dragging the rectangle
{
hdc = GetDC(hWnd);
POINTS now = MAKEPOINTS(lParam);
SetROP2(hdc, R2_NOTXORPEN);
MoveFromTo(hdc,rectSelected,anchor,now);
InvalidateRect(hWnd, NULL, TRUE);
anchor = MAKEPOINTS(lParam);
ReleaseDC(hWnd, hdc);
}
break;
case WM_RBUTTONDOWN:
{
int x{ LOWORD(lParam) }, y{ HIWORD(lParam) };
for (auto& rc : vRect)
{
if (IsClicked(rc, x, y))
{
select(hWnd, rc);
rectSelected= rc;
anchor = MAKEPOINTS(lParam);
break;
}
else {
deselect(hWnd, rc);
}
}
//select-deselect
}
break;
case WM_RBUTTONUP:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
bool IsClicked(CRect r, int x, int y)
{
POINT pt{ x, y };
return (bool)PtInRect(&r, pt);
}
void select(HWND hWnd, CRect r) {
HDC hdc = GetDC(hWnd);
HPEN selectPen = CreatePen(PS_DOT, 0, RGB(255, 0, 0));
SelectObject(hdc, selectPen);
Rectangle(hdc, r.left, r.top, r.right, r.bottom);
DeleteObject(selectPen);
}
void deselect(HWND hWnd, CRect r) {
HDC hdc = GetDC(hWnd);
Rectangle(hdc, r.left, r.top, r.right, r.bottom);
}
void DrawRubberBand(HWND hWnd) {
HDC hdc = GetDC(hWnd);
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc,
LeftButtonDown.x,
LeftButtonDown.y,
ptCurrent.x,
ptCurrent.y
);
ReleaseDC(hWnd, hdc);
}
void MoveFromTo(HDC hdc, CRect rectSelected, POINTS anchor, POINTS now)
{
Rectangle(hdc, rectSelected.left, rectSelected.top, rectSelected.right, rectSelected.bottom);
rectSelected.left = rectSelected.left + now.x - anchor.x; //x
rectSelected.top = rectSelected.top + now.y - anchor.y; //y
rectSelected.right = rectSelected.right + now.x - anchor.x; //x
rectSelected.bottom = rectSelected.bottom + now.y - anchor.y; //y
Rectangle(hdc, rectSelected.left, rectSelected.top, rectSelected.right, rectSelected.bottom);
}

The reason why all rectangles are selected in descending order is that the selected rectangle is found in WM_RBUTTONDOWN and the loop is breaked, resulting in the subsequent deselection of the rectangle is not performed.
There are several reasons why the rectangle cannot be dragged:
The MoveFromTo function should pass the reference of rectSelected, otherwise the coordinates of the modified rectangle in the function cannot be applied to the actual rectangle.
You need to find the rectangle before dragging, delete it from vRect, and then add the dragged rectangle to vRect, so as to ensure that the coordinates of the rectangle are updated every time in WM_PAINT.
Here is the code :
POINT LeftButtonDown;
POINT ptCurrent;
POINT ptClicked;
vector<CRect> vRect;
bool isRubberBand = false;
bool IsClicked(CRect r, int x, int y);
void select(HWND hWnd, CRect r);
void deselect(HWND hWnd, CRect r);
void DrawRubberBand(HWND hWnd);
void MoveFromTo(HDC hdc, CRect &rectSelected, POINTS anchor, POINTS now);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
CRect add;
static CRect rectSelected;
TCHAR s[100] = L"";
HDC hdc;
static POINTS anchor;
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
for (auto& rc : vRect)
{
Rectangle(hdc,
rc.left,
rc.top,
rc.right,
rc.bottom
);
}
EndPaint(hWnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
LeftButtonDown.x = LOWORD(lParam); //Start point
LeftButtonDown.y = HIWORD(lParam);
isRubberBand = true;
ptCurrent.x = LOWORD(lParam); //current point
ptCurrent.y = HIWORD(lParam);
DrawRubberBand(hWnd); //draw the rect
}
break;
case WM_LBUTTONUP: // adding to a vector
{
if (!isRubberBand)
{
return 0;
}
isRubberBand = false;
//InvalidateRect(hWnd, NULL, TRUE);
add.SetRect(LeftButtonDown.x, LeftButtonDown.y, ptCurrent.x, ptCurrent.y); //setting the coords of the rect
vRect.push_back(add); //add the rect
UpdateWindow(hWnd);
}
break;
case WM_MOUSEMOVE:
if (wParam & MK_LBUTTON) //drawing the rectangle
{
hdc = GetDC(hWnd);
if (!isRubberBand)
{
break;
}
DrawRubberBand(hWnd);
ptCurrent.x = LOWORD(lParam);
ptCurrent.y = HIWORD(lParam);
DrawRubberBand(hWnd);
}
if (wParam & MK_RBUTTON) //dragging the rectangle
{
hdc = GetDC(hWnd);
POINTS now = MAKEPOINTS(lParam);
if (PtInRect(&rectSelected, POINT{ now.x,now.y }))
{
SetROP2(hdc, R2_NOTXORPEN);
MoveFromTo(hdc, rectSelected, anchor, now);
InvalidateRect(hWnd, NULL, TRUE);
anchor = MAKEPOINTS(lParam);
}
ReleaseDC(hWnd, hdc);
}
break;
case WM_RBUTTONDOWN:
{
bool isFind = false;
int x{ LOWORD(lParam) }, y{ HIWORD(lParam) };
for (auto& rc : vRect)
{
if (!isFind && IsClicked(rc, x, y))
{
select(hWnd, rc);
rectSelected = rc;
anchor = MAKEPOINTS(lParam);
isFind = TRUE;
}
else {
deselect(hWnd, rc);
}
}
//select-deselect
}
break;
case WM_RBUTTONUP:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
bool IsClicked(CRect r, int x, int y)
{
POINT pt{ x, y };
return (bool)PtInRect(&r, pt);
}
void select(HWND hWnd, CRect r) {
HDC hdc = GetDC(hWnd);
HPEN selectPen = CreatePen(PS_DOT, 0, RGB(255, 0, 0));
SelectObject(hdc, selectPen);
Rectangle(hdc, r.left, r.top, r.right, r.bottom);
DeleteObject(selectPen);
}
void deselect(HWND hWnd, CRect r) {
HDC hdc = GetDC(hWnd);
Rectangle(hdc, r.left, r.top, r.right, r.bottom);
}
void DrawRubberBand(HWND hWnd) {
HDC hdc = GetDC(hWnd);
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc,
LeftButtonDown.x,
LeftButtonDown.y,
ptCurrent.x,
ptCurrent.y
);
ReleaseDC(hWnd, hdc);
}
void MoveFromTo(HDC hdc, CRect &rectSelected, POINTS anchor, POINTS now)
{
Rectangle(hdc, rectSelected.left, rectSelected.top, rectSelected.right, rectSelected.bottom);
for (auto it = vRect.begin();it != vRect.end();it++)
{
if (*it == rectSelected)
{
vRect.erase(it);
break;
}
}
rectSelected.left = rectSelected.left + now.x - anchor.x; //x
rectSelected.top = rectSelected.top + now.y - anchor.y; //y
rectSelected.right = rectSelected.right + now.x - anchor.x; //x
rectSelected.bottom = rectSelected.bottom + now.y - anchor.y; //y
Rectangle(hdc, rectSelected.left, rectSelected.top, rectSelected.right, rectSelected.bottom);
vRect.push_back(rectSelected);
}
It works for me:

Related

C++ Transparent image using windows.h / WinAPI

I'm trying to make a custom title bar/border and I want it to be half transparent. When I load the image it works fine, but as soon, as I make even just 1 pixel from 255 opacity to 254, it just shows a white image.
Help would really be appreciated!
I use the windows.h library.
I use a resource ( .rc ) file to get the location of the images.
Don't think about where im doing things right, it's just for testing. I know it's not very efficient.
The Code!
( or at least the most of it / the important )
#include "../resource/resource.h"
#include "../header/Window.h"
#pragma warning(disable : 4244)
/////////////////////////
// Window
//
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
HWND m_hWnd;
POINT wndSize = { 900, 900 };
Window::Window() : m_hInstance(GetModuleHandle(nullptr))
{
const wchar_t * className = L"Main Window";
WNDCLASS wc = {};
wc.lpszClassName = className;
wc.hInstance = m_hInstance;
wc.hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_ICON));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpfnWndProc = WindowProc;
wc.hbrBackground = CreatePatternBrush(LoadBitmap(m_hInstance, MAKEINTRESOURCE(IDB_BG)));
RegisterClass(&wc);
DWORD style = NULL;
RECT rect;
rect.left = GetSystemMetrics(SM_CXSCREEN) * .5 - (wndSize.x * .5);
rect.top = GetSystemMetrics(SM_CYSCREEN) * .5 - (wndSize.y * .5);
rect.right = rect.left + wndSize.x;
rect.bottom = rect.top + wndSize.y;
AdjustWindowRect(&rect, style, false);
m_hWnd = CreateWindowEx(
0,
className,
L"Chess",
style,
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
NULL,
NULL,
m_hInstance,
NULL
);
SetWindowLongPtr(m_hWnd, GWL_STYLE, NULL); // Remove the default borders
ShowWindow(m_hWnd, SW_SHOW);
}
Window::~Window()
{
const wchar_t * className = L"Main Window";
UnregisterClass(className, m_hInstance);
}
bool Window::ProcessMessage()
{
MSG msg = {};
RECT rectBorder;
HBRUSH colorBorder = CreatePatternBrush(LoadBitmap(m_hInstance, MAKEINTRESOURCE(IDB_TOP)));
//BitBlt(GetWindowDC(m_hWnd), 0, 0, 900, 30, GetWindowDC(m_hWnd), 0, 0, SRCINVERT);
//BitBlt(GetWindowDC(m_hWnd), 0, 0, 900, 30, GetWindowDC(m_hWnd), 0, 0, SRCAND);
//BitBlt(GetWindowDC(m_hWnd), 0, 0, 900, 30, GetWindowDC(m_hWnd), 0, 0, SRCINVERT);
while(PeekMessage(&msg, nullptr, 0u, 0u, PM_REMOVE))
{
switch(msg.message)
{
case WM_MOUSEMOVE: // On mouse motion
Window::updateWndEvents();
break;
case WM_PAINT: // On draw request
rectBorder = { 0, 0, wndSize.x, 30 };
FillRect(GetWindowDC(m_hWnd), &rectBorder, colorBorder); // Draw the top border
break;
case WM_QUIT: // On quit
return false;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return true;
}
/////////////////////////
// Window Functions
//
void Window::resizeWindow()
{
}
void Window::moveWindow()
{
}
void Window::updateWndEvents()
{
}
The alpha channel of a PNG image cannot be loaded using LoadBitmap.
It is recommended to use LockBits with PixelFormat32bppARGB in GDI+.

How do I make a static control with custom width and color?

To change the static's control color and width, I did process the WM_NCPAINT message, but it's not working properly:
the text isn't properly displayed. Should I draw it in this message? When I call SetWindowText(), the new text is properly set, but I still have the issue #2.
after a call to SetWindowText(), the text is set, but the border differs, it's smaller than the one I've draw in the WM_NCPAINT message.
Here's how I'm handling WM_NCPAINT:
case WM_NCPAINT:
{
PAINTSTRUCT ps;
HDC dc = BeginPaint(hwnd, &ps);
RECT rt = {0};
GetClientRect(hwnd, &rt);
HPEN pen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
HPEN holdPen = SelectObject(dc, pen);
DrawBorder(dc, &rt);
SelectObject(dc, holdPen);
EndPaint(hwnd, &ps);
DeleteObject(pen);
pen = NULL;
return 0;
}
When the application starts, the static control border looks like the one I've draw at WM_NCPAINT:
But once I call SetWindowText(), for example, from a button click, it looks like this:
What am I missing?
Here's full code:
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <Commctrl.h>
#include <crtdbg.h>
#include <strsafe.h>
#include <string.h>
#include <assert.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void EnableVisualStyles2(void);
LRESULT CALLBACK ButtonProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void SetDefaultFont(HWND hwnd);
void DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2);
void DrawBorder(HDC hdc, RECT *rect);
HFONT getSystemDefaultFont(void);
WNDPROC oldButtonProc;
HINSTANCE ghInstance;
HFONT hDefaultFont;
HWND btn, btn2;
enum
{
BTN_ID = 10,
BTN2_ID,
};
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow)
{
MSG msg = {0};
HWND hwnd;
WNDCLASSW wc = {0};
wc.lpszClassName = L"Window";
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
EnableVisualStyles2();
if(!RegisterClass(&wc)) {
return -1;
}
int width = 540;
int height = 460;
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
int cx = (screenWidth - width) / 2;
int cy = (screenHeight - height) / 2;
hwnd = CreateWindowW(wc.lpszClassName, L"Window",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
cx, cy, width, height, NULL, NULL,
hInstance, NULL);
ghInstance = hInstance;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
DeleteObject(hDefaultFont);
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
btn =
CreateWindow(L"static", L"+",
WS_VISIBLE | WS_CHILD | WS_TABSTOP |
SS_NOTIFY | SS_CENTER | SS_CENTERIMAGE,
15, 25, 50, 50,
hwnd, (HMENU) BTN_ID, NULL, NULL);
SetDefaultFont(btn);
oldButtonProc = (WNDPROC) SetWindowLongPtr(btn,
GWLP_WNDPROC,
(LONG_PTR) ButtonProc);
btn2 =
CreateWindow(L"Button", L"Click me!",
WS_VISIBLE | WS_CHILD | WS_TABSTOP,
15, 100, 70, 25,
hwnd, (HMENU) BTN2_ID,
NULL, NULL);
SetDefaultFont(btn2);
}
break;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case BTN_ID:
{
//InvalidateRect(btn, NULL, TRUE);
if(IsWindowVisible(btn2))
{
SetWindowText(btn, L"+");
ShowWindow(btn2, SW_HIDE);
}
else
{
SetWindowText(btn, L"-");
ShowWindow(btn2, SW_SHOW);
}
}
break;
case BTN2_ID:
{
InvalidateRect(btn, NULL, TRUE);
//SetWindowText(btn, L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0");
}
break;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK ButtonProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NCPAINT:
{
PAINTSTRUCT ps;
HDC dc = BeginPaint(hwnd, &ps);
RECT rt = {0};
GetClientRect(hwnd, &rt);
HPEN pen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
HPEN holdPen = SelectObject(dc, pen);
DrawBorder(dc, &rt);
SelectObject(dc, holdPen);
EndPaint(hwnd, &ps);
DeleteObject(pen);
pen = NULL;
return 0;
}
}
return CallWindowProc(oldButtonProc, hwnd, msg, wParam, lParam);
}
void EnableVisualStyles2(void)
{
TCHAR dir[MAX_PATH] = {0};
GetSystemDirectory(dir, sizeof(dir) / sizeof(*dir));
ACTCTX actCtx = {0};
actCtx.cbSize = sizeof(ACTCTX);
actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID |
ACTCTX_FLAG_SET_PROCESS_DEFAULT |
ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
actCtx.lpSource = L"shell32.dll";
actCtx.lpAssemblyDirectory = dir;
actCtx.lpResourceName = (LPCTSTR) 124;
ULONG_PTR cookie = FALSE;
HANDLE h = CreateActCtx(&actCtx);
assert(h != INVALID_HANDLE_VALUE);
assert(ActivateActCtx(h, &cookie));
}
HFONT getSystemDefaultFont(void)
{
if(!hDefaultFont)
{
// get system default font
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
hDefaultFont = CreateFontIndirect(&ncm.lfMessageFont);
}
return hDefaultFont;
}
void SetDefaultFont(HWND hwnd)
{
SendMessage(hwnd, WM_SETFONT, (WPARAM)getSystemDefaultFont(), FALSE);
}
void DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2)
{
MoveToEx(hdc, x1, y1, NULL);
LineTo(hdc, x2, y2);
}
void DrawBorder(HDC hdc, RECT *rect)
{
DrawLine(hdc, rect->left, rect->top, rect->left, rect->bottom);
DrawLine(hdc, rect->left, rect->top, rect->right, rect->top);
DrawLine(hdc, rect->right, rect->top, rect->right, rect->bottom);
DrawLine(hdc, rect->left, rect->bottom, rect->right, rect->bottom);
}
Your static control does not have a transparent background, so when you call the SetWindowText function, the static control partially covers the external lines you draw.
You can add the following code in WndProc:
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC)wParam;
SetBkMode(hdcStatic, TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
}
But this code tells the static control to draw the text without a background color and not to repaint the background. So the new text is drawn on top of the old text instead of on a fresh background.You can refer to this thread to try to solve it.
Of course, you can also extend the boundary of the straight line when drawing a straight line (you do not need to process the WM_CTLCOLORSTATIC message), as shown in the following code:
void DrawBorder(HDC hdc, RECT* rect)
{
DrawLine(hdc, rect->left - 3, rect->top - 3, rect->left - 3, rect->bottom + 3) ;
DrawLine(hdc, rect->left - 3, rect->top - 3, rect->right + 3, rect->top - 3);
DrawLine(hdc, rect->right + 3, rect->top - 3, rect->right + 3, rect->bottom + 3);
DrawLine(hdc, rect->left - 3, rect->bottom + 3, rect->right + 3, rect->bottom + 3);
}
This is the modified effect:

How am i supposed to draw colored rectangles

The rectangles are supposed to be randomly colored when I draw them with the left button. They have to be filled with color not only the border
I have tried to use pens and brushes but it doesn't work. I think that it is because of the method I am using to draw them. But I don't know how to do it otherwise.
Here is my code:
POINT LeftButtonDown;
POINT ptCurrent;
POINT ptClicked;
vector<CRect> vRect;
bool isRubberBand = false;
bool IsClicked(CRect r, int x, int y);
void select(HWND hWnd, CRect r);
void deselect(HWND hWnd, CRect r);
void DrawRubberBand(HWND hWnd);
void MoveFromTo(HDC hdc, CRect &rectSelected, POINTS anchor, POINTS now);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
CRect add;
static CRect rectSelected;
TCHAR s[100] = L"";
HDC hdc;
static POINTS anchor;
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
for (auto& rc : vRect)
{
Rectangle(hdc,
rc.left,
rc.top,
rc.right,
rc.bottom
);
}
EndPaint(hWnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
LeftButtonDown.x = LOWORD(lParam); //Start point
LeftButtonDown.y = HIWORD(lParam);
isRubberBand = true;
ptCurrent.x = LOWORD(lParam); //current point
ptCurrent.y = HIWORD(lParam);
DrawRubberBand(hWnd); //draw the rect
}
break;
case WM_LBUTTONUP: // adding to a vector
{
if (!isRubberBand)
{
return 0;
}
isRubberBand = false;
//InvalidateRect(hWnd, NULL, TRUE);
add.SetRect(LeftButtonDown.x, LeftButtonDown.y, ptCurrent.x, ptCurrent.y); //setting the coords of the rect
vRect.push_back(add); //add the rect
UpdateWindow(hWnd);
}
break;
case WM_MOUSEMOVE:
if (wParam & MK_LBUTTON) //drawing the rectangle
{
hdc = GetDC(hWnd);
if (!isRubberBand)
{
break;
}
DrawRubberBand(hWnd);
ptCurrent.x = LOWORD(lParam);
ptCurrent.y = HIWORD(lParam);
DrawRubberBand(hWnd);
}
if (wParam & MK_RBUTTON) //dragging the rectangle
{
hdc = GetDC(hWnd);
POINTS now = MAKEPOINTS(lParam);
if (PtInRect(&rectSelected, POINT{ now.x,now.y }))
{
SetROP2(hdc, R2_NOTXORPEN);
MoveFromTo(hdc, rectSelected, anchor, now);
InvalidateRect(hWnd, NULL, TRUE);
anchor = MAKEPOINTS(lParam);
}
ReleaseDC(hWnd, hdc);
}
break;
case WM_RBUTTONDOWN:
{
bool isFind = false;
int x{ LOWORD(lParam) }, y{ HIWORD(lParam) };
for (auto& rc : vRect)
{
if (!isFind && IsClicked(rc, x, y))
{
select(hWnd, rc);
rectSelected = rc;
anchor = MAKEPOINTS(lParam);
isFind = TRUE;
}
else {
deselect(hWnd, rc);
}
}
//select-deselect
}
break;
case WM_RBUTTONUP:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
bool IsClicked(CRect r, int x, int y)
{
POINT pt{ x, y };
return (bool)PtInRect(&r, pt);
}
void select(HWND hWnd, CRect r) {
HDC hdc = GetDC(hWnd);
HPEN selectPen = CreatePen(PS_DOT, 0, RGB(255, 0, 0));
SelectObject(hdc, selectPen);
Rectangle(hdc, r.left, r.top, r.right, r.bottom);
DeleteObject(selectPen);
}
void deselect(HWND hWnd, CRect r) {
HDC hdc = GetDC(hWnd);
Rectangle(hdc, r.left, r.top, r.right, r.bottom);
}
void DrawRubberBand(HWND hWnd) {
HDC hdc = GetDC(hWnd);
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc,
LeftButtonDown.x,
LeftButtonDown.y,
ptCurrent.x,
ptCurrent.y
);
ReleaseDC(hWnd, hdc);
}
void MoveFromTo(HDC hdc, CRect &rectSelected, POINTS anchor, POINTS now)
{
Rectangle(hdc, rectSelected.left, rectSelected.top, rectSelected.right, rectSelected.bottom);
for (auto it = vRect.begin();it != vRect.end();it++)
{
if (*it == rectSelected)
{
vRect.erase(it);
break;
}
}
rectSelected.left = rectSelected.left + now.x - anchor.x; //x
rectSelected.top = rectSelected.top + now.y - anchor.y; //y
rectSelected.right = rectSelected.right + now.x - anchor.x; //x
rectSelected.bottom = rectSelected.bottom + now.y - anchor.y; //y
Rectangle(hdc, rectSelected.left, rectSelected.top, rectSelected.right, rectSelected.bottom);
vRect.push_back(rectSelected);
}
Right now you're primarily missing two things:
where you draw your rectangles, you aren't creating and selecting a brush, so the default brush is being used to fill your rectangles.
Where you handle WM_LBUTTONUP, you're not invalidating the rectangle after you add it to your vector of rectangles to draw. So it doesn't get drawn (until you do something that forces it to be re-drawn like minimizing and restoring it).
That's pretty much all you're missing through.
So at the end of your WM_LBUTTONUP handler, you can replace your call to UpdateWindow, (which isn't necessary here) with a call to InvalidateRect, something like this: InvalidateRect(hWnd, add, true);. Note that this only invalidates the rectangle that we just drew (where your other code passes NULL for the invalid rectangle, so it invalidates everything). Only invalidating the part that's really invalid can improve efficiency (somewhat).
And in your WM_PAINT handler, you want code something like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// For the moment, I'm just hard-coding a fairly bright red fill:
HBRUSH brush = CreateSolidBrush(RGB(192, 0, 0));
HBRUSH original = (HBRUSH)SelectObject(hdc, brush);
// TODO: Add any drawing code that uses hdc here...
for (auto& rc : vRect)
{
Rectangle(hdc,
rc.left,
rc.top,
rc.right,
rc.bottom
);
}
// restore original brush before releasing the DC:
SelectObject(hdc, original);
EndPaint(hWnd, &ps);
// And destroy the brush:
DeleteObject(brush);
}
break;

How to paint over white line between menu bar and client area of window?

I tried to color customize menu items (pure WinAPI). But there is a line in the menu bar which does not draw with MenuInfo.hbrBack color. If the mouse cursor hover above items a part of this line is redrawn. But if I resize the window the line will return. And in the area of menu bar where no items the line drawn constantly. How can I draw over this annoying line?
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
struct
{
COLORREF text = RGB(200, 200, 250);
COLORREF clientBorder = RGB(120, 0, 0);
COLORREF clientBackground = RGB(100, 100, 100);
COLORREF itemBorder = RGB(0, 0, 255);
COLORREF itemBackground = RGB(0, 120, 0);
COLORREF pink = RGB(255, 0, 255);
} colorTheme;
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASSEX wc;
wc.cbSize = sizeof(wc);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.lpszMenuName = NULL;
wc.lpszClassName = "MainWindow";
wc.cbWndExtra = NULL;
wc.cbClsExtra = NULL;
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = CreateSolidBrush(colorTheme.clientBackground);
wc.hInstance = hInst;
RegisterClassEx(&wc);
HWND hMainWnd = CreateWindow(
"MainWindow",
"MainWindow",
WS_OVERLAPPEDWINDOW,
100, 100, 450, 120,
(HWND)NULL, NULL, HINSTANCE(hInst), NULL);
HMENU hMenu = CreateMenu();
HMENU hMenuSub1 = CreatePopupMenu();
HMENU hMenuSub2 = CreatePopupMenu();
HMENU hMenuSub3 = CreatePopupMenu();
AppendMenu(hMenu, MF_OWNERDRAW | MF_POPUP, (UINT)hMenuSub1, "SubMenu1");
AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item01");
AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item02");
AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item03");
AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item04");
AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item05");
AppendMenu(hMenu, MF_OWNERDRAW | MF_POPUP, (UINT)hMenuSub2, "SubMenu2");
AppendMenu(hMenu, MF_OWNERDRAW | MF_POPUP, (UINT)hMenuSub3, "SubMenu3");
MENUINFO menuInfo;
menuInfo.cbSize = sizeof(menuInfo);
menuInfo.fMask = MIM_BACKGROUND;
menuInfo.hbrBack = CreateSolidBrush(colorTheme.pink);
SetMenuInfo(hMenu, &menuInfo);
SetMenu(hMainWnd, hMenu);
ShowWindow(hMainWnd, nCmdShow);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hWnd, &ps);
HFONT hApplicationFont;
LOGFONT applicationFont;
applicationFont.lfHeight = 16;
applicationFont.lfWidth = 6;
applicationFont.lfEscapement = 0;
applicationFont.lfOrientation = 0;
applicationFont.lfWeight = FW_NORMAL;
applicationFont.lfItalic = FALSE;
applicationFont.lfUnderline = FALSE;
applicationFont.lfStrikeOut = FALSE;
applicationFont.lfCharSet = DEFAULT_CHARSET;
applicationFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
applicationFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
applicationFont.lfQuality = ANTIALIASED_QUALITY;
applicationFont.lfPitchAndFamily = DEFAULT_PITCH;
strcpy_s(applicationFont.lfFaceName, "Arial");
hApplicationFont = CreateFontIndirectA(&applicationFont);
SelectObject(hDC, hApplicationFont);
SelectObject(hDC, GetStockObject(DC_PEN));
SetDCPenColor(hDC, colorTheme.clientBorder);
SelectObject(hDC, GetStockObject(DC_BRUSH));
SetDCBrushColor(hDC, colorTheme.clientBackground);
RECT clientRect;
GetClientRect(hWnd, &clientRect);
Rectangle(hDC, 0, 0, clientRect.right, clientRect.bottom);
EndPaint(hWnd, &ps);
break;
}
case WM_MEASUREITEM:
{
LPMEASUREITEMSTRUCT itemStruct = (LPMEASUREITEMSTRUCT)lParam;
const char* str = (const char*)(itemStruct->itemData);
SIZE strSize;
HDC hDC = GetDC(hWnd);
GetTextExtentPoint32(hDC, str, lstrlen(str), &strSize);
itemStruct->itemWidth = strSize.cx;
itemStruct->itemHeight = 30;
ReleaseDC(hWnd, hDC);
return TRUE;
break;
}
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT itemStruct = (LPDRAWITEMSTRUCT)lParam;
HDC hDC = itemStruct->hDC;
SelectObject(hDC, GetStockObject(DC_PEN));
SetDCPenColor(hDC, colorTheme.itemBorder);
SelectObject(hDC, GetStockObject(DC_BRUSH));
SetDCBrushColor(hDC, colorTheme.itemBackground);
SetTextColor(hDC, colorTheme.text);
SetBkMode(hDC, TRANSPARENT);
Rectangle(hDC, itemStruct->rcItem.left,
itemStruct->rcItem.top,
itemStruct->rcItem.right,
itemStruct->rcItem.bottom + 1);
DrawText(hDC, (const char*)(itemStruct->itemData), -1, &(itemStruct->rcItem), DT_SINGLELINE | DT_CENTER | DT_VCENTER);
break;
}
case WM_DESTROY:
{
PostQuitMessage(NULL);
break;
}
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return NULL;
}
It seems to be part of the non-client area of the window. If that's the case then to paint there you need to handle WM_NCPAINT.
It is a single pixel line above the window's client area, so for example if I add the following code to your program I can paint it in red.
// ... in the WNDPROC
case WM_NCPAINT:
{
auto result = DefWindowProc(hWnd, WM_NCPAINT, wParam, lParam);
HDC hdc = GetWindowDC(hWnd);
RECT r = GetNonclientMenuBorderRect(hWnd);
HBRUSH red = CreateSolidBrush(RGB(255, 0, 0));
FillRect(hdc, &r, red);
DeleteObject(red);
ReleaseDC(hWnd, hdc);
return result;
}
// ... elsewhere
RECT MapRectFromClientToWndCoords(HWND hwnd, const RECT& r)
{
RECT wnd_coords = r;
// map to screen
MapWindowPoints(hwnd, NULL, reinterpret_cast<POINT*>(&wnd_coords), 2);
RECT scr_coords;
GetWindowRect(hwnd, &scr_coords);
// map to window coords by substracting the window coord origin in
// screen coords.
OffsetRect(&wnd_coords, -scr_coords.left, -scr_coords.top);
return wnd_coords;
}
RECT GetNonclientMenuBorderRect(HWND hwnd)
{
RECT r;
GetClientRect(hwnd, &r);
r = MapRectFromClientToWndCoords(hwnd, r);
int y = r.top - 1;
return {
r.left,
y,
r.right,
y+1
};
}
Now an issue with the above code is that it is over-painting the rectangle after the default non-client painting is done. In theory this could flicker; in practice I don't notice a flicker. If it did flicker, however, a safer way to do this would be to modify the WPARAM you pass to DefWindowProc(hWnd, WM_NCPAINT, ... ) such that it is the handle to a region that is the region passed to WM_NCPAINT minus the rectangle you want to paint. This doesnt seem necessary to me, for whatever reason.
If you are using themes / visual styles, which pretty much everything is nowadays, you can't override a lot of the menu styling without using a workaround like https://github.com/adzm/win32-custom-menubar-aero-theme which also uses the same approach to get rid of the white line. Note that you will need to handle this in WM_NCPAINT and WM_NCACTIVATE.

Visual studio wont work with "MoveTo" (win32api)

Here's the code below, VS said that on
line 109 ( MoveTo(hdc, x, y);
"MoveTo" is undefined.
Can someone explain me what's wrong ?
This is a win32api application.
// Win32Project.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "Win32Project4.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int pNum = 0;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
const WCHAR szAppName[] = L"Third_LAB";
const WCHAR szChildClass[] = L"Child";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_CROSS);
wndclass.hbrBackground = (HBRUSH)(NULL_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SMALL));
RegisterClassEx(&wndclass);
wndclass.lpfnWndProc = ChildWndProc;
wndclass.cbWndExtra = sizeof(WORD);
wndclass.hIcon = NULL;
wndclass.lpszClassName = szChildClass;
wndclass.hIconSm = NULL;
RegisterClassEx(&wndclass);
hwnd = CreateWindowEx(
0,
szAppName, // window class name
L"Third_LAB", // window caption
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, // window style
200, // initial x position
150, // initial y position
800, // initial x size
500, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL
); // creation parameters
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
{
const WCHAR szChildClass[] = L"Child";
static int cxBlock, cyBlock;
static HDC hdc;
static HPEN hPen;
static BOOL track = FALSE;
static HWND hwndCH;
static int x, y;
switch (iMsg) {
case WM_CREATE:
hdc = GetDC(hwnd);
hwndCH = CreateWindow(szChildClass, NULL,
WS_CHILDWINDOW | WS_VISIBLE,
0, 0, cxBlock / 2 + 3, cyBlock + 3,
hwnd, NULL,
(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
NULL);
MoveWindow(hwndCH, 0, 0, cxBlock / 2 + 3, cyBlock + 3, NULL);
return 0;
case WM_SIZE:
cxBlock = LOWORD(lParam);
cyBlock = HIWORD(lParam);
MoveWindow(hwndCH, 0, 0, cxBlock / 2 + 3, cyBlock + 3, NULL);
return 0;
case WM_KEYDOWN:
switch (wParam)
{
case (0x51):
PostQuitMessage(0);
break;
}
return 0;
case WM_LBUTTONDOWN:
track = TRUE;
x = LOWORD(lParam);
y = HIWORD(lParam);
MoveTo(hdc, x, y);
switch (pNum)
{
case (1):
hPen = CreatePen(PS_SOLID, 5, 0);
SelectObject(hdc, hPen); break;
case (2):
hPen = CreatePen(PS_SOLID, 15, 0);
SelectObject(hdc, hPen); break;
case (3):
hPen = CreatePen(PS_SOLID, 25, 0);
SelectObject(hdc, hPen); break;
}
return 0;
case WM_LBUTTONUP:
track = FALSE;
DeleteObject(hPen);
return 0;
case WM_MOUSEMOVE:
if (track)
{
x = LOWORD(lParam);
y = HIWORD(lParam);
LineTo(hdc, x, y);
MoveTo(hdc, x, y);
}
return 0;
case WM_DESTROY:
ReleaseDC(hwnd, hdc);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
}
LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HDC hdc;
PAINTSTRUCT ps;
static int cxBlock, cyBlock;
static int x, y;
static HPEN hPen;
switch (iMsg)
{
case WM_SIZE:
hdc = GetDC(hwnd);
cxBlock = LOWORD(lParam);
cyBlock = HIWORD(lParam);
InvalidateRect(hwnd, NULL, TRUE);
ReleaseDC(hwnd, hdc);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SetROP2(hdc, R2_BLACK);
Rectangle(hdc, 0, cyBlock / 2 - 100, cxBlock, cyBlock / 2 - 105);
Rectangle(hdc, 0, cyBlock / 2, cxBlock, cyBlock / 2 - 15);
Rectangle(hdc, 0, cyBlock / 2 + 100, cxBlock, cyBlock / 2 + 125);
InvalidateRect(hwnd, NULL, FALSE);
break;
case WM_RBUTTONDOWN:
x = LOWORD(lParam);
y = HIWORD(lParam);
if ((y <= (cyBlock / 2 - 100)) && (y >= (cyBlock / 2 - 105)))
pNum = 1;
else if ((y <= (cyBlock / 2)) && (y >= (cyBlock / 2 - 15)))
pNum = 2;
else if ((y >= (cyBlock / 2 + 100)) && (y <= (cyBlock / 2 + 125)))
pNum = 3;
return 0;
case WM_DESTROY:
DeleteObject(hPen);
ReleaseDC(hwnd, hdc);
DestroyWindow(hwnd);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
Waiting for a fast response, I would be very glad.
MoveTo() is a Windows 3.x function that is no longer supported. Use MoveToEx() instead, setting the last argument to NULL. You can define your own MoveTo() to do that, eg:
#define MoveTo(hdc, x, y) ((void)MoveToEx(hdc, x, y, NULL))
Or:
inline void MoveTo(HDC hdc, int X, int Y) { MoveToEx(hdc, X, Y, NULL); }

Resources