I apologize for the vagueness of the title, but i'm not entirely sure how i want to go about solving the issue at hand. Basically, i have 2 groups of buttons. When the user selects a button from one of the groups, i want to set an indicator that that group has been selected. So either a rectangle surrounding them (in blue) or coloring the background (or background image) behind the group. The end user indicated that a rectangle surrounding them would be nice so that would be preferential. I have tried overriding the onCtlColor on a group box, but I don't know how to limit the coloring to just the border. I do know that it absolutely needs to be the farthest back in z-order Any advice?
Example code (i'm aware that it pains the entirety of the box)
pDC->SetBkColor(GetSysColor(RGB(100,149,237)));
CRect rect;
testGb.GetClientRect(rect);
CBrush brushBlue(RGB(0, 0, 255));
CBrush* pOldBrush = pDC->SelectObject(&brushBlue);
pDC->Rectangle(rect);
pDC->SelectObject(pOldBrush);
I could find only a way to change the background colour of the Text of a group box:
EDIT You can paint anything you want on the Group box!
a) Declare a CBrush member variable m_br and create a coloured brush with it
b) Override WindowProc for a group box with ID = IDC_GROUPBOX:
LRESULT CTestMFCDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{ if (message == WM_CTLCOLORSTATIC)
if (::GetDlgCtrlID((HWND) lParam) == IDC_GROUPBOX)
return (LRESULT) m_br.GetSafeHandle();
return CDialogEx::WindowProc(message, wParam, lParam);
}
LRESULT CTestMFCDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{ if (message == WM_CTLCOLORSTATIC)
if (::GetDlgCtrlID((HWND) lParam) == IDC_GROUPBOX)
{ HWND hWnd = (HWND) lParam;
HDC hDC = (HDC) wParam;
RECT rc;
::GetClientRect(hWnd, &rc);
HBRUSH hOldBrush = (HBRUSH) ::SelectObject(hDC, m_br);
::Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
::SelectObject(hDC, hOldBrush);
// return (LRESULT) m_br.GetSafeHandle();
}
return CDialogEx::WindowProc(message, wParam, lParam);
}
Better code (at least for me):
void MyClass::OnPaint()
{
CPaintDC dc(this); // device context for painting
COLORREF highlightFillColor;
CPen nPen, *pOldPen = NULL;
CBrush nBrush, *pOldBrush = NULL;
CRect rect;
GetWindowRect(rect);
ScreenToClient(rect);
BmsMemDC memDc(&dc, &rect);
memDc.SetBkMode(TRANSPARENT);
//dc.Re
highlightFillColor = RGB(0x99,0xB4,0xFF);
nPen.CreatePen( PS_SOLID, 4, highlightFillColor);
nBrush.CreateSolidBrush( GetSysColor(COLOR_3DFACE ));
pOldPen = memDc.SelectObject(&nPen);
pOldBrush = memDc.SelectObject(&nBrush);
if(leftGroupSelected)
{
rect.SetRect(rect.left + 4, rect.top+30, rect.left + 126, rect.bottom - 5);
memDc.FillRect(&rect,&nBrush);
memDc.RoundRect(rect.left, rect.top, rect.right, rect.bottom, 8, 8);
}
if (rightGroupSelected)
{
rect.SetRect(rect.left + 134, rect.top+30, rect.left + 256, rect.bottom - 5);
memDc.FillRect(&rect,&nBrush);
memDc.RoundRect(rect.left, rect.top, rect.right, rect.bottom, 8, 8);
}
}
Related
I have an owner-drawn button that I would like to highlight, when the mouse hovers over it. Here is the simplified code, that doesn't seem to work:
case WM_DRAWITEM:
LPDRAWITEMSTRUCT pDraw = (LPDRAWITEMSTRUCT)lParam;
if(pDraw->itemState == ODS_HOTLIGHT) DrawButton(pDraw->hDC, HIcolor);
else DrawButton(pDraw->hDC, normcolor);
return 0;
DrawButton is a custom function that draws the button. The second parameter of this function is the primary color of the button. The function has no trouble drawing the button. The problem in in detecting the "item state".
I have also tried using if(pDraw->itemState & ODS_HOTLIGHT). That does not work either.
Obviously, I am missing something. In my search, I have come across many answers. However, none of them are in C++.
ODS_HOTLIGHT may not be used by the button. See What states are possible in a DRAWITEMSTRUCT structure?.
You can use subclassing, and then use TrackMouseEvent to get the event in SUBCLASSPROC.
LRESULT CALLBACK Subclassproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if (uMsg == WM_MOUSEMOVE)
{
TRACKMOUSEEVENT ev = {};
ev.cbSize = sizeof(TRACKMOUSEEVENT);
ev.dwFlags = TME_HOVER | TME_LEAVE;
ev.hwndTrack = hWnd;
ev.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&ev);
}
else if (uMsg == WM_MOUSEHOVER)
{
RECT rc = {};
GetClientRect(hWnd, &rc);
HDC hdc = GetDC(hWnd);
SetDCBrushColor(hdc, RGB(255, 0, 0));
SelectObject(hdc, GetStockObject(DC_BRUSH));
RoundRect(hdc, rc.left, rc.top,
rc.right, rc.bottom, 0, 0);
}
else if (uMsg == WM_MOUSELEAVE)
{
RECT rc = {};
GetClientRect(hWnd, &rc);
HDC hdc = GetDC(hWnd);
SetDCBrushColor(hdc, RGB(0, 255, 0));
SelectObject(hdc, GetStockObject(DC_BRUSH));
RoundRect(hdc, rc.left, rc.top,
rc.right, rc.bottom, 0, 0);
TRACKMOUSEEVENT ev = {};
ev.cbSize = sizeof(TRACKMOUSEEVENT);
ev.dwFlags = TME_HOVER | TME_LEAVE | TME_CANCEL;
ev.hwndTrack = hWnd;
ev.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&ev);
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
...
HWND hButton = CreateWindow(L"BUTTON", L"", WS_CHILD | WS_VISIBLE |
BS_OWNERDRAW, 10, 10, 60, 30, hWnd,
(HMENU)10001, hInst, NULL);
SetWindowSubclass(hButton, Subclassproc, 101, 0);
As stated in the description of the ODS_HOTLIGHT value:
ODS_HOTLIGHT: The item is being hot-tracked, that is, the item will be highlighted when the mouse is on the item.
So, I suppose that in order to receive this message you need to use the TrackMouseEvent function first.
Basically, I'm making something that imitates a screen melting effect, but I can only get it working on my primary monitor. I've looked up as much as I could and there was only one forum on GetDC for all monitors but it was to no use, all it done was make a rectangle from my primary monitor to my secondary monitor with the effect still only working on my primary monitor. This is the thread I read: GetDC(NULL) gets primary monitor or virtual screen?
LRESULT CALLBACK Melter(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch (Message) {
case WM_CREATE: {
HDC Desktop = GetDC(HWND_DESKTOP);
HDC Window = GetDC(hWnd);
BitBlt(Window, 0, 0, ScreenWidth, ScreenHeight, Desktop, 0, 0, SRCCOPY);
ReleaseDC(hWnd, Window);
ReleaseDC(HWND_DESKTOP, Desktop);
SetTimer(hWnd, 0, Interval, 0);
ShowWindow(hWnd, SW_SHOW);
break;
}
case WM_PAINT: {
ValidateRect(hWnd, 0);
break;
}
case WM_TIMER: {
HDC Window = GetDC(hWnd);
int uX = (rand() % ScreenWidth) - (150 / 2), uY = (rand() % 15), Width = (rand() % 150);
BitBlt(Window, uX, uY, Width, ScreenHeight, Window, uX, 0, SRCCOPY);
ReleaseDC(hWnd, Window);
break;
}
case WM_DESTROY: {
KillTimer(hWnd, 0);
PostQuitMessage(EXIT_SUCCESS);
break;
}
return EXIT_SUCCESS;
}
return DefWindowProc(hWnd, Message, wParam, lParam);
}
The line I changed was HDC Window = GetDC(Window) to HDC Window = GetDC(NULL) and then some other stuff like the RECT. It'd be great if someone could help me, thanks :)
PS, ScreenWidth = 3600, ScreenHeight = 1080 whilst PMScreenWidth = 1920, PMScreenHeight = 1080. PM as in Primary Monitor, so I've got all the stuff in that function set to ScreenWidth/ScreenHeight so it's the width/height of all monitors. Still doesn't work though.
GetDC(HWND_DESKTOP) (same as GetDC(0)) already returns the DC for all monitors. The problem with above code is mainly with the usage of BitBlt and choosing the coordinates. See the MCVE below that addresses the question.
Don't draw in response to WM_CREATE, it's just going to get erased in WM_PAINT or when background is erased.
Don't call ValidateRect in response to WM_PAINT. If you want to erase the window then just use FillRect, or force repaint from a command or another route.
Use GetSystemMetrics(SM_CXVIRTUALSCREEN) and GetSystemMetrics(SM_CYVIRTUALSCREEN) to return width and height for the virtual monitor.
Also be sure the process is DPI aware. For testing you can call SetProcessDPIAware(); at the start of the program. Ideally DPI awareness should be set in manifest file.
int uX = (rand() % ScreenWidth) - (150 / 2);
int uY = (rand() % 15);
int Width = (rand() % 150);
BitBlt(Window, uX, uY, Width, ScreenHeight, Window, uX, 0, SRCCOPY);
Above code is copying bits from client DC the the same client DC, it's not going to do anything. Presumably you want to copy from desktop DC to client DC.
Moreover, the coordinates are basically picked at random. It assumes the primary monitor is on top-left. If uX is greater than window's own width, it will not get copied, unless the window stretches the whole virtual monitor.
LRESULT CALLBACK Melter(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch(Message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
auto hdc = BeginPaint(hWnd, &ps);
RECT rc;
GetClientRect(hWnd, &rc);
HDC hdesktop = GetDC(0);
int screenx = GetSystemMetrics(SM_XVIRTUALSCREEN);
int screeny = GetSystemMetrics(SM_YVIRTUALSCREEN);
int screenw = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int screenh = GetSystemMetrics(SM_CYVIRTUALSCREEN);
StretchBlt(hdc, 0, 0, rc.right, rc.bottom,
hdesktop, screenx, screeny, screenw, screenh, SRCCOPY);
ReleaseDC(0, hdesktop);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, Message, wParam, lParam);
}
You can use
HDC dc = CreateDC(("DISPLAY"), NULL, NULL, NULL);
Check below links:
Using Multiple Monitors as Independent Displays
CreateDCA function
Multiple Display Monitors Functions
I have a custom subclassed button created in the WM_CREATE message of my WindowProc callback. Here are the creation and subclassing instructions, along with a structure used to control the button state:
static button_state btnstateBtnInstall;
hBtnInstall = CreateWindow(WC_BUTTON, L"Button", WS_CHILD | WS_VISIBLE, (window_width / 2) - (btn_install_width / 2), window_height - (window_height / 6) - (btn_install_height / 2), btn_install_width, btn_install_height, hwnd, (HMENU)HMENU_btn_install, NULL, NULL);
SetWindowSubclass(hBtnInstall, BtnInstallProc, 0, (DWORD_PTR)&btnstateBtnInstall);
The struct is defined as follows:
struct button_state
{
bool pushed;
button_state() { pushed = false; }
};
The subclassed procedure is coded as follows:
LRESULT CALLBACK BtnInstallProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubClass, DWORD_PTR dwRefData)
{
button_state* state = (button_state*)dwRefData;
// Omitted part where I create brushes and font to be used for painting
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
BOOL hover = PtInRect(&rc, pt);
if (state->pushed)
{
// Pushed
FillRect(hdc, &rc, hBrPushed);
}
else if (hover)
{
// Mouse over
FillRect(hdc, &rc, hBrHover);
}
else
{
// Normal
FillRect(hdc, &rc, hBrNormal);
}
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255, 255, 255));
SelectFont(hdc, SegoeUI);
static LPCWSTR InstallBtnTxt = L"Install";
static int InstallBtnTxtLen = static_cast<int>(wcslen(InstallBtnTxt)); // Should be a safe cast, for small arrays like this one
DrawText(hdc, InstallBtnTxt, InstallBtnTxtLen, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
}
case WM_LBUTTONDOWN:
{
state->pushed = true;
break;
}
case WM_LBUTTONUP:
{
state->pushed = false;
break;
}
// Omitted part where I handle WM_DESTROY to do cleanup
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
To compare the behaviour of my button with a standard one, I created another button without subclassing and using only the standard BUTTON class and WS_VISIBLE | WS_CHILD attributes.
As you can see from the attached gif, when I repeatedly click on a standard class button, every click produces the animation while when hitting multiple times the button I created with this code it seems like it isn't catching every click but (as you can see) something like 50% of them.
What am I missing? Why isn't my button as responsive as the one that comes with the standard class?
TL;DR: when clicking slowly on my subclassed button, the painting is correct. As you can see from my attached image, the difference between mine and a standard button is big when repeating clicks on them. How can I make my control be as responsive as the standard one?
When you are clicking fast some WM_LBUTTONDOWN messages are substituted by the WM_LBUTTONDBLCLK (default double click handling). You need to handle it just like you are handling the WM_LBUTTONDOWN message.
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK: // <-- Added
{
state->pushed = true;
break;
}
Pretty much what the title says.
Im trying to make my own border, like that of visual studio 2015.
Once i have the border working like it should i will add a child window that is the main window for my program, the border will be the parent.
Will also try and add a outer glow once i get it working.
But the problem im having right now is, When i drag my border to resize it to make it smaller, The right or bottom start to get thinner depending on how fast i drag the mouse.
Is there a better way to do this or is there a simple step i can take to fix it.
#include <windows.h>
LPTSTR className_ = TEXT("BorderWindow");
BOOL WINAPI Init(HINSTANCE hInstance, INT cmdShow);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
INT WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, INT nCmdShow) {
MSG msg;
if (!Init(hInstance, nCmdShow)) {
return FALSE;
}
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (INT)msg.wParam;
}
BOOL WINAPI Init(HINSTANCE hInstance, INT cmdShow)
{
WNDCLASSEX wcex{ 0 };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = 0;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_HIGHLIGHT + 1);
wcex.lpszClassName = className_;
wcex.hIconSm = NULL;
if (!RegisterClassEx(&wcex)) {
return FALSE;
}
HWND hwnd_ = CreateWindow(className_, className_, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 200, 500, nullptr, nullptr, hInstance, nullptr);
if (!hwnd_)
return FALSE;
ShowWindow(hwnd_, cmdShow);
UpdateWindow(hwnd_);
return TRUE;
}
void CreateHole(HWND hWnd)
{
HRGN WindowRgn;
HRGN HoleRgn;
//Get the window region:
RECT windowrect;
GetWindowRect(hWnd, &windowrect);
int width = windowrect.right - windowrect.left;
int height = windowrect.bottom - windowrect.top;
WindowRgn = CreateRectRgn(0, 0, width, height);
//Create the hole region:
HoleRgn = CreateRectRgn(2, 2, width - 2, height - 2);
CombineRgn(WindowRgn, WindowRgn, HoleRgn, RGN_DIFF);
SetWindowRgn(hWnd, WindowRgn, TRUE);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_SIZE:
CreateHole(hwnd);
return 0;
case WM_NCCALCSIZE:
// remove default borders
return 0;
case WM_NCHITTEST:
{
RECT rc;
GetClientRect(hwnd, &rc);
POINT pt = { LOWORD(lparam), HIWORD(lparam) };
ScreenToClient(hwnd, &pt);
if (pt.y > (rc.bottom - 5))
{
if (pt.x > (rc.right - 5))
{
return HTBOTTOMRIGHT;
}
}
return HTBORDER;
}
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
If you only need rectangular frame, an easier solution can be achieved as follows:
Window style should be such that normally the whole frame would be shown (WS_CAPTION|WS_POPUP works well for me).
Call DwmExtendFrameIntoClientArea() with MARGINS{0,0,0,1}.
Call SetWindowPos(nullptr, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED) to recalculate NC area.
Return 0 from WM_NCCALCSIZE if wParam is TRUE. This has the effect of extending the client area to the window size including frame. The regular window frame will be removed, but shadow will still be drawn by DWM (see also remarks section of WM_NCCALCSIZE).
In WM_PAINT draw your frame and content area as you like but make sure to set opaque alpha channel for the margin defined by the DwmExtendFrameIntoClientArea() call. Otherwise part of regular frame would be visible in this area. You may use GDI+ for that as most regular GDI functions ignore alpha channel.
You can put child controls into this window, just as normal. Just make sure child controls don't overlap the margin defined by the DwmExtendFrameIntoClientArea() call because most GDI controls ignore the alpha channel.
I have a main window which is created with the following styles
WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_TABSTOP | WS_GROUP | WS_VISIBLE
and with ex-stles
WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT | WS_EX_LEFT | WS_EX_LTRREADING.
This main window has a child window on it, which is an edit control created with styles
WS_VISIBLE | WS_CHILD | ES_READONLY
and ex-style
WS_EX_CLIENTEDGE.
I am going to use this edit control as a progress-bar control. I don't want to use standard Wind32 progress-bar control (PROGRESS_CLASS), because I want to do a some custom painting on it (e.g.; dynamically changing fill color, displaying text on it, etc).
I can paint any region of the main window by the following code:
// hWnd: Handle of the main window
case WM_PAINT:
hDc = BeginPaint(hWnd, &Ps);
Rect = AFunctionToGetCornerThePointsOfTheEditControl();
Rect.right = Rect.left + 3 * (Rect.right - Rect.left) / 4; // Fill 3/4 (75%) of it
Rect.left -= 10; // Enlarge the paint region a little
Rect.top -= 10; // so that we can see it if it stays
Rect.bottom += 10; // under the edit control.
hBrush = CreateSolidBrush(RGB(50,100,255));
ret = FillRect(hDc, &Rect, hBrush); // ret = 1 always
ler = GetLastError(); // ler = 0
EndPaint(hWnd, &Ps);
break;
It looks like this:
I changed this code a little to paint the child control instead:
// hWndEdit: Handle of the edit control
case WM_PAINT:
hDc = BeginPaint(hWndEdit, &Ps);
Rect = AFunctionToGetCornerThePointsOfTheEditControl();
Rect.right = Rect.left + 3 * (Rect.right - Rect.left) / 4; // Fill 3/4 (75%) of it
Rect.left -= 10;
Rect.top -= 10;
Rect.bottom += 10;
hBrush = CreateSolidBrush(RGB(50,100,255));
ret = FillRect(hDc, &Rect, hBrush); // ret = 0 always
ler = GetLastError(); // ler = 6 (ERROR_INVALID_HANDLE)
EndPaint(hWndEdit, &Ps);
break;
This time it doesn't work. The main windows completely disappears as soon as I drag some part it out of the screen area, and it becomes totally unresponsive. Desktop icons under it are visible, but are not clickable.
So, what do I have to do in order to paint the child window (the edit control)?
This article helped me a lot: Subclassing Controls
First, I create a separate message processing function for processing child messages.
LRESULT CALLBACK MyClass::ChildWindowProc( HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData)
{
static PAINTSTRUCT Ps;
static RECT Rect;
static HBRUSH hBrush1 = CreateSolidBrush(RGB(50,100,255));
static HBRUSH hBrush2 = CreateSolidBrush(RGB(255,100,50));
HDC hDc;
LRESULT lResult;
switch (uMsg)
{
case WM_PAINT:
switch (uIdSubclass)
{
case 1:
hDc = BeginPaint(hWnd, &Ps);
Rect.left = 0;
Rect.right = (LONG) (((double) ITEM2_WIDTH) * Status::GI()->Get_JobCurPercentage());
Rect.top = 0;
Rect.bottom = ITEM_HEIGHT - 3;
FillRect(hDc, &Rect, hBrush1);
EndPaint(hWnd, &Ps);
break;
case 2:
hDc = BeginPaint(hWnd, &Ps);
Rect.left = 0;
Rect.right = (LONG) (((double) ITEM2_WIDTH) * Status::GI()->Get_JobTotPercentage());
Rect.top = 0;
Rect.bottom = ITEM_HEIGHT - 3;
FillRect(hDc, &Rect, hBrush2);
EndPaint(hWnd, &Ps);
break;
default:
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
break;
case WM_NCDESTROY:
//ReleaseDC(hWnd, hDc);
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
break;
default:
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
Next, I bind this function to the controls:
SetWindowSubclass( /*_In_ HWND hWnd*/ ed_cur_Progress.hWnd,
/*_In_ SUBCLASSPROC pfnSubclass*/ ChildWindowProc,
/*_In_ UINT_PTR uIdSubclass*/ 1,
/*_In_ DWORD_PTR dwRefData*/ (DWORD_PTR) NULL);
SetWindowSubclass( /*_In_ HWND hWnd*/ ed_tot_Progress.hWnd,
/*_In_ SUBCLASSPROC pfnSubclass*/ ChildWindowProc,
/*_In_ UINT_PTR uIdSubclass*/ 2,
/*_In_ DWORD_PTR dwRefData*/ (DWORD_PTR) NULL);
And, that's all! The result is amazing.
WM_PAINT you are handling is main window's. you need to draw the editbox in its owner WM_PAINT message. I guess you get the error from "hDc = BeginPaint(hWndEdit, &Ps);", you may check it.