Reputation: 1661
I'm trying to display text on the screen, on top of everything, not clickable, without having any window. The idea is to be able to show notifications. I'm somewhat close from what I want, but a really weird problem just showed. This is the code:
#include <Windows.h>
int main(void){
HDC hdc = ::GetDC(0);
RECT rect;
SetTextColor(hdc, RGB(0, 0, 255));
SetBkMode(hdc, TRANSPARENT);
SetBkColor(hdc, RGB(0, 0, 0));
auto hFont = CreateFont(40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, L"Verdana");
auto hTmp = (HFONT)SelectObject(hdc, hFont);
rect.left = 40;
rect.top = 10;
while (true){
DrawText(hdc, L"THIS IS A TEXT", -1, &rect, DT_SINGLELINE | DT_NOCLIP);
Sleep(1);
}
DeleteObject(SelectObject(hdc, hTmp));
::ReleaseDC(0, hdc);
return 0;
}
and this is what happens when I change the text settings from red
to blue
and size 80
to 40
:
For some reason I can still see the old text, after rerunning the program, which tells me that I misunderstood something. Is there a better, cleaner way to do this?
EDIT: I checked windows notifications and that's NOT a solution. Imagine you are playing a full screen game, and want to know if an email arrives. Another essential thing is that it cannot be clickable, so that clicks by mistake don't minimize your game. How annoying is that skype popup that minimizes your application when you get a call?
Upvotes: 1
Views: 2968
Reputation: 1661
I ended up running into a lot of problems, while trying to make this work. If someone ends up visiting this page looking for an answer to the same problems I ran into, I hope you have an easier time than I had. This is the code that worked for me:
#include <Windows.h>
INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR lpCmdLine, INT nCmdShow)
{
// Define and initialize variables
HDC hdc;
HDC hdcMem;
HBITMAP hbmMem;
HANDLE hOld;
RECT rect;
SIZE sz;
int win_width = 0;
int win_height = 0;
int font_size = 20;
int location_x = 40;
int location_y = 40;
int border = font_size / 4;
int duration = 10000; // In miliseconds. The notification will always stay up more time
wchar_t* font_face = L"Consolas";
wchar_t message[100];
// Save command-line arguments to message; They are showed by the notification
MultiByteToWideChar(0, 0,
lpCmdLine,
strlen(lpCmdLine),
message,
100
);
message[strlen(lpCmdLine)] = L'\0';
// Acquire screen
hdc = ::GetDC(0);
//Create necessary font
HFONT hFont = CreateFont(font_size, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, font_face);
HFONT hTmp = (HFONT)SelectObject(hdc, hFont);
// Calculate size of the text
GetTextExtentPoint32(hdc, message, wcslen(message), &sz);
win_width = sz.cx;
win_height = sz.cy;
rect = { 0, 0, sz.cx, sz.cx };
// Create an off-screen DC for double-buffering
hdcMem = CreateCompatibleDC(hdc);
hbmMem = CreateCompatibleBitmap(hdc, win_width + 2 * border, win_height + 2 * border);
// Configure off-screen DC
SetBkMode(hdcMem, OPAQUE);
SetTextColor(hdcMem, RGB(125, 125, 255));
SetBkColor(hdcMem, RGB(0, 0, 0));
SelectObject(hdcMem, hFont);
hOld = SelectObject(hdcMem, hbmMem);
// Draw loop
for (int i = 0; i < duration; i++)
{
// Draw into hdcMem
DrawText(hdcMem, message, -1, &rect, DT_SINGLELINE);
// Transfer the off-screen DC to the screen
BitBlt(hdc, location_x, location_y, win_width + 2 * border, win_height + 2 * border, hdcMem, -5, -5, SRCCOPY);
// Don't eat all the cpu!
Sleep(1);
}
// Delete notification right after time expires
::InvalidateRect(0, &rect, false);
::UpdateWindow(0);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
// Release created objects
DeleteObject(SelectObject(hdc, hTmp));
::ReleaseDC(0, hdc);
return 0;
}
It can still be improved, a lot. The only thing that appears is the rectangle with the notification.
The arguments that you pass to the program will be shown as a message. Everything related with hdcMem
was implemented to avoid flickering. I can't yet change the background of the bigger rectangle.
Upvotes: 3
Reputation: 2761
You have bypassed all of the windows/client controls and so the system has no idea that this area needs to be cleared. You need to tell it manually, especially since you are not using the windows message notification mechanism.
Before drawing to it, you want to invalidate that part of the screen and tell windows to redraw it:
::InvalidateRect (0, &rect, false); // Redraw without erasing. If doesn't help, try true
::UpdateWindow (0);
while (true)...
Upvotes: 2