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
Related
I have a couple of issues with this function:
The text turns into gibberish if I move the window a bit, or if I switch it to another monitor (Which has a different scaling, by the way)
A smaller version of the text appears before the initial text if I make a call to CreateFont and SelectObject before printing the text
Am I doing it right? Maybe there's too many calls inside the WM_PAINT case? I'm not sure how else it could be done
My code:
case(WM_PAINT):
{
HDC hDC = GetWindowDC(Window);
RECT lpRect;
GetClientRect(Window,
&lpRect
);
SetTextColor(hDC, RGB(0, 0, 0));
SetBkMode(hDC, TRANSPARENT);
DrawTextW(hDC,
L"Loading...",
-1,
&lpRect,
(DT_SINGLELINE | DT_TOP | DT_VCENTER | DT_NOCLIP)
);
DeleteDC(hDC);
break;
}
case(WM_ERASEBKGND):
{
HDC hDC = GetWindowDC(Window);
RECT lpRect;
GetClientRect(Window, &lpRect);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
FillRect(hDC, &lpRect, hBrush);
DeleteObject(hBrush);
break;
}
In WM_PAINT, you must call BeginPaint() and EndPaint(). It's this way you obtain the device context. If you don't call EndPaint() the rect is not validated.
Can somebody explain how to draw text on a bitmap in memory?
I have the following code but i can't figure out how to do it.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC buffDC = CreateCompatibleDC(hdc);
SelectObject(buffDC, hFnt);
SetTextColor(buffDC, RGB(1, 1, 1));
SetBkColor(buffDC, RGB(0, 255, 0));
RECT rc;
GetClientRect(hWnd, &rc);
HBITMAP buffBitmap = CreateCompatibleBitmap(buffDC, rc.right, rc.bottom);
int savedDC = SaveDC(buffDC);
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
FillRect(buffDC, &rc, hBrush);
DeleteObject(hBrush);
//This is the part where i would like to draw to the bitmap
TextOutA(buffDC, 0, 0, "Hello", 6);
SelectObject(buffDC, buffBitmap);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);
RestoreDC(buffDC, savedDC);
DeleteObject(buffBitmap);
DeleteDC(buffDC);
EndPaint(hWnd, &ps);
break;
}
I have seen a lot of different solutions mostly using MFC, however I would like to avoid that approach if possible.
EDIT: I have checked the other already asked questions but I couldn't find one wich would cover this without MFC.
My original problem was that I'm using a timer to call RedrawWindow and update the position of the text and make a sort of scrolling text which moves from right to left.
When I was in the testing process I have noticed that on some machines the app runs with up to 25% CPU usage and on some others it uses < 1%. I have tested the app one two machines with exactly the same specs running Windows 7 and the app ran on one with ~10% and the other with 0%.
(By the way my timer is getting called every 33ms and RedrawWindow uses RDW_UPDATENOW and I didn't handle the WM_ERASEBKGND message either :P
Since WM_TIMER (as far as I know) is a low priority message I was not concerned about the timer causeing the issue with the CPU usage.)
I satrted to think that maybe I should be using a bitmap and BitBlt it to the screen rather than just simply drawing to the dc and updating the x coordinate every time I repaint the screen.
Thanks
Before you can draw onto a bitmap, you have to select it into a memory device context.
Move SelectObject(buffDC, buffBitmap); before the first call to a drawing function, but usually as soon as possible after you created the bitmap.
In your sample code it appears suitable to insert it after the SaveDC() call so the original bitmap will be restored later when you call RestoreDC():
int savedDC = SaveDC(buffDC);
SelectObject(buffDC, buffBitmap);
As commenters noted, CreateCompatibleBitmap(buffDC, rc.right, rc.bottom) should be changed to CreateCompatibleBitmap(hdc, rc.right, rc.bottom).
From the reference of CreateCompatibleBitmap():
When a memory device context is created, it initially has a 1-by-1
monochrome bitmap selected into it. If this memory device context is
used in CreateCompatibleBitmap, the bitmap that is created is a
monochrome bitmap. To create a color bitmap, use the HDC that was used
to create the memory device context
Finally a suggestion: If you just need a temporary bitmap (as in your sample code), there is a more efficient API available since Windows Vista. It is called the buffered paint API. MSDN does not appear to provide a good overview, here is a tutorial and the reference (all functions that have "BufferedPaint" in their name).
Here is the Window Procedure which worked for me and is based on the answer from zett42.
This piece of code is just for testing purposses as I cannot post the original source code of the application I'm working on due to work.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int xPos;
const bool bIsBufferedPaint = true;
switch (message)
{
case WM_CREATE:
{
BufferedPaintInit();
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
if(bIsBufferedPaint)
{
HDC newDC;
RECT rc;
RECT dstrc;
GetClientRect(hWnd, &rc);
dstrc = rc;
dstrc.left = rc.right + xPos;
HPAINTBUFFER hBufferedPaint = BeginBufferedPaint(hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &newDC);
if(hBufferedPaint)
{
BufferedPaintClear(hBufferedPaint, NULL);
SetTextColor(newDC, RGB(0, 0, 0));
DrawText(newDC, L"Using buffered paint", -1, &dstrc, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
Sleep(2);
EndBufferedPaint(hBufferedPaint, TRUE);
}
else
{
// buffer paint did not work.
}
}
else
{
HDC buffDC = CreateCompatibleDC(hdc);
SetTextColor(buffDC, RGB(0, 0, 0));
SetBkColor(buffDC, RGB(255, 255, 255));
RECT rc;
GetClientRect(hWnd, &rc);
HBITMAP buffBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
int savedDC = SaveDC(buffDC);
SelectObject(buffDC, buffBitmap);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
FillRect(buffDC, &rc, hBrush);
DeleteObject(hBrush);
std::string testText = "Not using the buffered paint API";
TextOutA(buffDC, xPos, 0, testText.c_str(), testText.size());
BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);
RestoreDC(buffDC, savedDC);
DeleteObject(buffBitmap);
DeleteDC(buffDC);
}
EndPaint(hWnd, &ps);
}
break;
case WM_ERASEBKGND:
return 1;
case WM_TIMER:
{
switch(wParam)
{
case TIMER1:
{
xPos--;
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ERASE);
if(bIsBufferedPaint)
{
if(xPos <= -500)
xPos = 0;
}
else
{
if(xPos <= -50)
xPos = 1000;
}
}
break;
}
}
break;
case WM_NCDESTROY:
{
BufferedPaintUnInit();
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
It seems the flickering is generated by the CombineRgn function, but I really have no idea why this happens, since i've never used regions that much I'm possibly missing some knowledge on the matter.
Some events in the program triggers the addition of little rectangles to the main region, here's the code that handles that:
HRGN ActualRegion = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, ActualRegion);
HRGN AddedRect = CreateRectRgn(//long code that creates a rectangle)
CombineRgn(ActualRegion, ActualRegion, AddedRect, RGN_OR);
SetWindowRgn(hwnd, ActualRegion, FALSE);
InvalidateRect(hwnd, NULL, FALSE);
White Flickering appears only after the invalidation if new regions where combined to the main one.
Here's how I'm implementing double buffering in WM_PAINT:
PLEASE NOTE that on creation i'm enabling the DWM blur behind function with an invalid region (different from the Main one) which means that everything painted with BLACK_BRUSH will result in a 100% "invisible" portion of the program
RECT r; GetClientRect(hwnd, &r);
PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps);
HDC MemDc = CreateCompatibleDC(hdc);
HBITMAP hBmp = CreateCompatibleBitmap(hdc, r.right, r.bottom);
HBITMAP hOld = (HBITMAP)SelectObject(MemDc, hBmp);
//Making sure this dc is filled with "invisible" pixels to display
SelectObject(MemDc, GetStockObject(BLACK_BRUSH));
Rectangle(MemDc, //arbitrary values that matches the entire screen);
BitBlt(hdc, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), MemDc, 0, 0, SRCCOPY);
//clean-up
SelectObject(MemDc, hOld);
DeleteObject(hBmp);
DeleteDC(MemDc);
EndPaint(hwnd, &ps);
WM_ERASEBKGND obviously returns TRUE without further handling, the WNDCLASSEX instance of the window has a default BLACK_BRUSH as the hbrBackground field.
I also tried to intercept and return TRUE from WM_NCPAINT message.
I'm doing everything necessary to avoid intermediate drawcalls, everything handled inside the WM_PAINT uses a backbuffer, also i'd like to mention i'm not working with images/bitmaps. Everything is drawn with gdi/gdi+, and in no place i'm actually issuing a "white" redraw that may possibly cause said flicker. I'm a bit lost here
Is it something that i'm possibly missing ? I can't really understand what may be causing white flickering in this scenario
The problem is not the CombineRgn function but the SetWindowRgn function which you call before the system draws the window for the first time. If you call SetWindowRgn after the first draw, no flicker. Unfortunatelly I don't know why. So, a way to counter that is to set the window region after the first draw (take the code that sets window region from WM_CREATE and leave only the DwmEnableBlurBehindWindow):
static int stc = 0;
//in WM_PAINT after the EndPaint(hwnd, &ps); add
HRESULT lr = DefWindowProc(hwnd, message, wParam, lParam);
if( stc == 0 ){
OnlyOnce();
stc++;
}
return lr;
and the OnlyOnce:
void OnlyOnce(void){
int X_Screen = GetSystemMetrics(SM_CXSCREEN);
int Y_Screen = GetSystemMetrics(SM_CYSCREEN);
HRGN ActualRegion = CreateRectRgn(X_Screen - 100, Y_Screen - 100, X_Screen - 100 + 40, Y_Screen - 100 + 40);
SetWindowRgn(hWnd, ActualRegion, true);
return;
}
INTRODUCTION:
I have decided to make a test project in MS Visual Studio 2008, in C++ to test a small program in pure WIN32, regarding painting a bitmap as window's background.
PROBLEM:
Window should have gray brush and a bitmap picture stretched over its client area.
In my code for WM_PAINT, if I try to paint gray brush for window, without bitmap, everything seems to work well.
Same goes if I just try to put a bitmap as a background.
Yet, when I combine these two, so I can get a bitmap picture stretched, and gray background behind bitmap, this is what happens:
Bitmap picture appears to "stands still", but gray brush appears over entire window for a second, then disappears entirely, so only stretched bitmap is seen, and then appears again, and so on.
It seems as if it is drawn from top going to the bottom, and it seems as if application is doing it all over again.
This is how I see it when I start my program.
RELEVANT INFORMATION:
The program was made by choosing option File->New, and then choosing Win32 project from the options.
Window class was set automatically, and the following members were set like this:
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
I have added static global variable to store bitmap handle:
static HBITMAP bmp;
In the window procedure, made by the wizard, I have initialized it with following code:
case WM_CREATE:
bmp = LoadBitmap( hInst, MAKEINTRESOURCE(IDB_BITMAP1) );
return 0;
break;
In the window procedure, made by the wizard, I have added WM_PAINT handler, with following code:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
RECT r;
GetClientRect( hWnd, &r );
// TODO: Add any drawing code here...
// fill client area with gray brush, just to test
FillRect( hdc, &r, (HBRUSH)GetStockObject( GRAY_BRUSH ) );
// memory DC for double buffering
HDC MemDC = CreateCompatibleDC( hdc );
// select our bitmap into memory DC
HBITMAP old = (HBITMAP)SelectObject( MemDC, bmp );
// get bitmap's width and height so we can stretch it
BITMAP b;
GetObject( bmp, sizeof(BITMAP), &b );
// stretch our bitmap
StretchBlt( hdc, 0, 0, r.right - r.left, r.bottom - r.top,
MemDC, 0, 0, b.bmWidth, b.bmHeight, SRCCOPY );
// perform proper cleanup
SelectObject( MemDC, old );
DeleteDC(MemDC);
EndPaint(hWnd, &ps);
}
return 0L;
break;
I have also invalidated client area when window is resized, or erasing of background happens, like this:
case WM_ERASEBKGND:
InvalidateRect( hWnd, NULL, TRUE );
return 1L;
break;
case WM_SIZE:
InvalidateRect( hWnd, NULL, TRUE );
return 0L;
Bitmap is destroyed like this:
case WM_DESTROY:
DeleteObject( bmp );
PostQuitMessage(0);
break;
IMPORTANT NOTE:
Even if I comment out handlers for WM_SIZE and WM_ERASEBKGND, the effect still occurs.
I do not have much experience with double buffering, but this is simple thing to do.
I just fail to see the mistake, so I ask more experienced and skillfull colleagues to help.
If additional source code is required, ask for it and I will post it, but until then I will omit it to keep the question brief.
You should not call InvalidateRect in WM_ERASEBKGND. That's just going to force an endless series of paint cycles.
The job of WM_ERASEBKGND is to paint the background, and that's all you should ever do. If your WM_PAINT is going to paint the entire window, then there's no need to paint any background. In which case, and I think this is your scenario, you should do nothing in WM_ERASEBKGND.
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.