I am trying to create a borderless window with the standard shadow. I read on the winapi documentation that setting pMarInset with negative values to the DwmExtendFrameIntoClientArea function, creates the "sheet of glass" effect where the client area is rendered without a window border. This gives me the result I wanted, which is a borderless window with the standard shadow. However, when I resize the window, I can see the standard frame behind the solid background color I gave in the WNDCLASS struct. I tried clearing the screen with a solid color but I get some weird results:
This is the window when active:
And when the window is inactive:
When I hover over the app icon in the taskbar, I see the solid color I tried clearing the screen with:
Here is my window procedure:
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(handle, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_HIGHLIGHT+1));
EndPaint(handle, &ps);
return 0;
} break;
case WM_ACTIVATE: {
MARGINS margins = {-1};
DwmExtendFrameIntoClientArea(handle, &margins);
return 0;
} break;
case WM_NCCALCSIZE: {
if(wparam){ return 0; }
return DefWindowProcA(handle, message, wparam, lparam);
} break;
Related
I am trying to draw a radiobutton with a transparent background using a subclassed radiobutton & WM_PAINT. I know how to do this using the TransparentBlt function & an off-screen (back)buffer.
My problem is that intially the radiobutton by default draws some text & circle (see image 1). My WM_PAINT message only consists of BeginPaint() and EndPaint(). When i minimize the window and afterwards activate the window again, the default text & circle get replaced by a black square like you would expect (see image 2).
The yellow window is also painted in the same manner using WM_PAINT.
Both procedures return 1 for WM_ERASEBKGND and return 0 for WM_PAINT as required for custom painting & double buffering.
Is this normal behaviour? I found a "fix" by using WS_EX_TRANSPARENT, but I would like to understand first why the radiobutton initially gets painted like that to determine if this is the right fix for me.
Thanks in advance.
Radiobutton creation:
MControlRect rect(0, 0, 100, 20);
unsigned long style = WS_CHILD | BS_AUTORADIOBUTTON;
if (isGroupStarter) {
style += WS_GROUP;
}
::HWND hWnd = _create(pControlParent, WC_BUTTON, style, rect);
::WNDPROC systemProc = (::WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC);
::SetWindowLong(hWnd, GWL_WNDPROC, (long)customRadiobuttonProcedure);
::SetWindowLong(hWnd, GWL_USERDATA, (long)systemProc);
::UpdateWindow(hWnd);
::ShowWindow(hWnd, SW_SHOW);
Radiobutton procedure:
switch (msg) {
case WM_ERASEBKGND:
{
return 1;
break;
}
case WM_NCPAINT:
{
return 0;
break;
}
case WM_PAINT:
{
::PAINTSTRUCT ps;
::HDC hdc = ::BeginPaint(hWnd, &ps);
::EndPaint(hWnd, &ps);
return 0;
break;
}
}
::WNDPROC defaultWindowProc = (::WNDPROC)::GetWindowLong(hWnd, GWL_USERDATA);
return ::CallWindowProc(defaultWindowProc, hWnd, msg, wParam, lParam);
Image 1: initial radiobutton painted
Image 2: radiobutton after minimize > show again
I am trying to draw an image using GDI+. When I do it inside WM_PAINT it works:
case WM_PAINT: {
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
Gdiplus::Image gdiImage(L"unt.png");
graphics.DrawImage(&gdiImage, 40, 40);
EndPaint(hWnd, &ps);
break;
}
But when I do it on a button click or inside WM_CREATE it doesn't draw the image:
HDC hdc2 = GetDC(hWnd);
Gdiplus::Graphics graphics(hdc2);
Gdiplus::Image gdiImage(L"unt.png");
graphics.DrawImage(&gdiImage, 40, 40);
Even if I use BeginPaint() and EndPaint() it still fails. So, is there any way to draw the image outside of WM_PAINT?
In Win32, almost all drawing must happen in WM_PAINT handler.
Probably you don't see any drawing because as soon you finish to handle the button click, you receive a WM_PAINT.
If you draw outside WM_PAINT your drawing have short life because of invalidation of windows and then WM_PAINT message.
So the correct way to draw in Win32 is the WM_PAINT handler.
I edited the answer after a comment of author.
Suppose you need to change the image after a mouse click. You can do:
case WM_PAINT: {
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
Gdiplus::Image gdiImage(clicked ? L"image_A.png" : L"image_B.png");
graphics.DrawImage(&gdiImage, 40, 40);
EndPaint(hWnd, &ps);
break;
}
case WM_LBUTTONDOWN: {
clicked = true;
InvalidateRect(hwnd, NULL, true);
break;
}
The InvalidateRect function is the answer. With that function you tell to Windows to redraw the window. This is the link to man page:InvalidateRect
If you need to update an image based on a button click (or any other Windows Event handler for that matter), you have to invalidate the area that the image occupies on the screen—usually through InvalidateRect()—and then have your WM_PAINT handler draw the image.
I'm trying to make a layered window with c++ win32 but I'm having a problem with the drawing or "collision" of it
For reference the picture that I'm trying to display.
This is the basic creation of the window
//window
DWORD exFlags = 0;
if(m_bTransparent)
exFlags |= WS_EX_LAYERED;
Create(WS_POPUP, exFlags);
std::wstring sPic(L"power-disconnected.png");
m_pAlertPic = m_pPowerMon->GetGPPicMan()->LoadPicture(sPic.c_str());
// make the window layered when using transparency
if(m_bTransparent && m_pAlertPic != nullptr)
{
HDC hdcScreen = GetDC(GetHandle());
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpold = (HBITMAP)SelectObject(hdc, m_pAlertPic->GetBuffer());
POINT dcOffset = {0, 0};
SIZE size = {ww, wh};
BLENDFUNCTION bf = {AC_SRC_OVER, 0, (int) (2.55 * 100), AC_SRC_ALPHA}; // blend function combines opacity and pixel based transparency
UpdateLayeredWindow(GetHandle(), hdcScreen, NULL, &size, hdc, &dcOffset, RGB(255, 255, 255), &bf, ULW_ALPHA);
SelectObject(hdc, hbmpold);
DeleteDC(hdc);
ReleaseDC(GetHandle(), hdcScreen);
}
and the message loop
int WindowAlert::WndProc(Gnaq::WindowBase* pWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
Hide();
return 1;
case WM_PAINT:
// only draw when the widow is not transparent
// layered window redraw them self
if(!m_bTransparent)
m_pCanvas->Draw(m_pGraphics);
break;
case WM_LBUTTONUP:
pWnd->Hide();
m_bDismised = true;
break;
}
return DefWindowProcW(pWnd->GetHandle(), msg, wParam, lParam);
}
So this is the result I get
As you can see with this method I'm getting white borders where is should actually be fully transparent, but the semi transparent parts do work correctly.
here's what I've tried that gave me a "useful" change.
First I just tried to add the ULW_COLORKEY flag to hide the white color
UpdateLayeredWindow(GetHandle(), hdcScreen, NULL, &size, hdc, &dcOffset, RGB(255, 255, 255), &bf, ULW_ALPHA | ULW_COLORKEY);
And the result.
So this hides the white border but also all the white in the picture.
Next thing I've tried was using SetLayeredWindowAttributes in combination of UpdateLayeredWindow, without the ULW_COLORKEY flag
SetLayeredWindowAttributes(GetHandle(), 0xFFFFFF00, 255, LWA_COLORKEY);
Also in the window proc enable the paint, like this
case WM_PAINT:
m_pCanvas->Draw(m_pGraphics);
break;
This way I'm visually getting what I want like this
But the problem with his approach is that it the complete window is click able while with just using the UpdateLayeredWindow only the parts that should be fully transparent are click able like it should be. I also have the feeling with this last approach that it is more a "hack" than a decent approach.
So i hope that someone can tell me what I'm doing wrong.
The first way was the correct. The fault was in the bitmap, which didn't had premultiplied alpha
I am trying to implement double buffering but it doesn't seem to work i.e. the graphic still flickers.
The WM_PAINT gets called everytime when the mouse moves. (WM_MOUSEMOVE)
Pasted WM_PAINT below:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rect;
GetClientRect(hWnd, &rect);
int width=rect.right;
int height=rect.bottom;
HDC backbuffDC = CreateCompatibleDC(hdc);
HBITMAP backbuffer = CreateCompatibleBitmap( hdc, width, height);
int savedDC = SaveDC(backbuffDC);
SelectObject( backbuffDC, backbuffer );
HBRUSH hBrush = CreateSolidBrush(RGB(255,255,255));
FillRect(backbuffDC,&rect,hBrush);
DeleteObject(hBrush);
if(fileImport)
{
importFile(backbuffDC);
}
if(renderWiredCube)
{
wireframeCube(backbuffDC);
}
if(renderColoredCube)
{
renderColorCube(backbuffDC);
}
BitBlt(hdc,0,0,width,height,backbuffDC,0,0,SRCCOPY);
RestoreDC(backbuffDC,savedDC);
DeleteObject(backbuffer);
DeleteDC(backbuffDC);
EndPaint(hWnd, &ps);
}
Add the following handler:
case WM_ERASEBKGND:
return 1;
The reason it works is because this message is sent before painting to ensure that painting is done on the window class's background. The flashing is going back and forth between the background and what's painted over it. Once the background has stopped being painted, it stops conflicting with what is painted over it, which includes filling the window with a solid colour, so there will still be a background anyway.
Can you change the background of text in area of edit control that would stay static?
In the parent of the edit control, handle the WM_CTLCOLORSTATIC message, the wParam of this message is the HDC that the Edit control is about to draw with,
for most CTLCOLOR messages, if you set text and background colors into this DC, the control will use the colors you set.
You can also return an HBRUSH and the contol will use that for any brush painting that it wil do, but many controls don't use brushes much, so that will have limited effect for some
CTLCOLOR messages. Your best bet here is to return the DC brush, and set the DC Brush color to match the BkColor of the DC.
LRESULT lRet = 0; // return value for our WindowProc.
COLORREF crBk = RGB(255,0,0); // use RED for Background.
...
case WM_CTLCOLORSTATIC:
{
HDC hdc = (HDC)wParam;
HWND hwnd = (HWND)lParam;
// if multiple edits and only one should be colored, use
// the control id to tell them apart.
//
if (GetDlgCtrlId(hwnd) == IDC_EDIT_RECOLOR)
{
SetBkColor(hdc, crBk); // Set to red
SetDCBrushColor(hdc, crBk);
lRet = (LRESULT) GetStockObject(DC_BRUSH); // return a DC brush.
}
else
{
lRet = DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
break;
WM_CTLCOLORSTATIC is for static text control.
To be simple, you can do this in your winproc:
...
case WM_CTLCOLOREDIT:
{
HDC hdc = (HDC)wParam;
SetTextColor(hdc, yourColor); // yourColor is a WORD and it's format is 0x00BBGGRR
return (LRESULT) GetStockObject(DC_BRUSH); // return a DC brush.
}
...
If you have more than 1 edit control, you can use the item id and lParam to check which one need to be change.
WM_CTLCOLOREDIT allows you to set text and background color(+brush), if you want more control than that, you have to subclass and paint yourself
you could do something like this:
CBrush bkBrush;
RECT ctrlRect;
COLORREF crBk = RGB(255,0,0); // Red color
bkBrush.CreateSolidBrush(crBk);
CWnd* pDlg = CWnd::GetDlgItem(IDC_EDIT);
pDlg->GetClientRect(&ctrlRect);
pDlg->GetWindowDC()->FillRect(&ctrlRec, &bkBrush);
pDlg->GetWindowDC()->SetBkColor(crBk);
This should change the background color of the edit control
All you need is to set the required color in control's device context and pass an HBRUSH with same color in WM_CTLCOLOREDIT message. If you want to change both foreground & background colors, use SetTextColor t0 change the text color. But you must pass the background color HBRUSH. But if you want to change the text color only, then you must pass a DC_BRUSH with GetStockObject function.