I am learning WIN32 API in C/C++ and want to know how to create custom slider/trackbar control with custom shaped thumb that is a child of a dialog. An example would be of benefit on how to do it as there is very low information about WIN32 programming on the internet, especially for customizing your own child controls.
Please do not post MFC examples.
Here is the code example I am trying to complete:
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#include <string>
#include <Windows.h>
#include <CommCtrl.h>
using namespace std;
HWND window = nullptr;
HWND trackBar = nullptr;
HWND progressBar = nullptr;
HWND staticText = nullptr;
WNDPROC defWndProc = nullptr;
static HBITMAP hBitmapThumb, hBitmapBar;
static BITMAP bm;
LRESULT OnWindowClose(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
PostQuitMessage(0);
return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}
LRESULT OnTrackBarChanged(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
LRESULT value = SendMessage(trackBar, TBM_GETPOS, 0, 0);
SendMessage(progressBar, PBM_SETPOS, value, 0);
SendMessage(staticText, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(to_wstring(value).c_str()));
return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}
void DrawBitmapTransparent(HDC hDCDest, int nXDest, int nYDest, int nBitmapWidth, int nBitmapHeight, HBITMAP hBitmap, int nXSrc, int nYSrc, int nTransparentColor)
{
HDC hDCSrc;
HBITMAP hBitmapOld;
HDC hDCMask;
HBITMAP hBitmapMask;
HBITMAP hBitmapMaskOld;
HDC hDCMem;
HBITMAP hBitmapMem;
HBITMAP hBitmapMemOld;
int nBkColorOld;
int nTextColorOld;
BITMAP bm;
GetObject( hBitmap, sizeof( BITMAP ), &bm );
if (!nBitmapWidth) {
nBitmapWidth = bm.bmWidth;
}
if (!nBitmapHeight) {
nBitmapHeight = bm.bmHeight;
}
hDCSrc = CreateCompatibleDC( hDCDest );
hBitmapOld = reinterpret_cast<HBITMAP>(SelectObject( hDCSrc, hBitmap ));
hDCMask = CreateCompatibleDC( hDCDest );
hBitmapMask = CreateBitmap( nBitmapWidth, nBitmapHeight, 1, 1, 0 );
hBitmapMaskOld = reinterpret_cast<HBITMAP>(SelectObject( hDCMask, hBitmapMask ));
hDCMem = CreateCompatibleDC( hDCDest );
hBitmapMem = CreateCompatibleBitmap( hDCDest, nBitmapWidth, nBitmapHeight );
hBitmapMemOld = reinterpret_cast<HBITMAP>(SelectObject( hDCMem, hBitmapMem ));
nBkColorOld = SetBkColor( hDCSrc, nTransparentColor );
BitBlt( hDCMask, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCCOPY );
SetBkColor( hDCSrc, nBkColorOld );
nBkColorOld = SetBkColor( hDCDest, RGB(255,255,255) );
nTextColorOld = SetTextColor( hDCDest, RGB(0,0,0) );
BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCDest, nXDest, nYDest, SRCCOPY );
BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCINVERT );
BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCMask, 0, 0, SRCAND );
BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCINVERT );
BitBlt( hDCDest, nXDest, nYDest, nBitmapWidth, nBitmapHeight, hDCMem, 0, 0, SRCCOPY );
SetBkColor( hDCDest, nBkColorOld );
SetTextColor( hDCDest, nTextColorOld );
SelectObject( hDCMem, hBitmapMemOld );
DeleteDC( hDCMem );
DeleteObject( hBitmapMem );
SelectObject( hDCMask, hBitmapMaskOld );
DeleteDC( hDCMask );
DeleteObject( hBitmapMask );
SelectObject( hDCSrc, hBitmapOld );
DeleteDC( hDCSrc );
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
case WM_NOTIFY :
{
LPNMHDR lpNmhdr = reinterpret_cast<LPNMHDR>(lParam);
if (lpNmhdr->code == NM_CUSTOMDRAW)
{
LPNMCUSTOMDRAW lpNMCustomDraw = reinterpret_cast<LPNMCUSTOMDRAW>(lParam);
if (lpNMCustomDraw->dwDrawStage == CDDS_PREPAINT) {
return CDRF_NOTIFYITEMDRAW;
}
else if (lpNMCustomDraw->dwDrawStage == CDDS_ITEMPREPAINT)
{
long nLeft = lpNMCustomDraw->rc.left;
long nTop = lpNMCustomDraw->rc.top;
long nRight = lpNMCustomDraw->rc.right;
long nBottom = lpNMCustomDraw->rc.bottom;
if (lpNMCustomDraw->dwItemSpec == TBCD_THUMB && hBitmapThumb)
{
long nWidth = nRight - nLeft;
long nHeight = nBottom - nTop;
if (nWidth - bm.bmWidth > 0)
{
nLeft += (nWidth - bm.bmWidth)/2;
nWidth = bm.bmWidth;
}
if (nHeight - bm.bmHeight > 0)
{
nTop += (nHeight - bm.bmHeight) / 2;
nHeight = bm.bmHeight;
}
DrawBitmapTransparent(lpNMCustomDraw->hdc , nLeft, nTop, nWidth, nHeight, hBitmapThumb, 0, 0, RGB( 255, 0, 255 ));
return CDRF_SKIPDEFAULT ;
}
}
}
}
break;
}
if (message == WM_CLOSE && hwnd == window) return OnWindowClose(hwnd, message, wParam, lParam);
if (message == WM_HSCROLL && hwnd == window && reinterpret_cast<HWND>(lParam) == trackBar) return OnTrackBarChanged(hwnd, message, wParam, lParam);
return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}
int main() {
window = CreateWindowEx(0, WC_DIALOG, L"TrackBar example", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, nullptr, nullptr, nullptr, nullptr);
trackBar = CreateWindowEx(0, TRACKBAR_CLASS, nullptr, WS_CHILD | TBS_HORZ | TBS_BOTTOM | WS_VISIBLE | TBS_FIXEDLENGTH, 150, 10, 250, 70, window, nullptr, nullptr, nullptr);
progressBar = CreateWindowEx(0, PROGRESS_CLASS, nullptr, WS_CHILD | PBS_SMOOTH | WS_VISIBLE, 20, 100, 200, 23, window, nullptr, nullptr, nullptr);
staticText = CreateWindowEx(0, WC_STATIC, L"100", WS_CHILD | WS_VISIBLE, 20, 150, 100, 23, window, nullptr, nullptr, nullptr);
defWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WndProc)));
hBitmapThumb = reinterpret_cast<HBITMAP>(LoadImage(NULL, reinterpret_cast<LPCWSTR>("pink.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));
GetObject( hBitmapThumb, sizeof( BITMAP ), &bm );
SendMessage(trackBar, TBM_SETRANGEMIN, 1, 0);
SendMessage(trackBar, TBM_SETRANGEMAX, 1, 200);
SendMessage(trackBar, TBM_SETTHUMBLENGTH, bm.bmWidth*2, 0);
SendMessage(progressBar, PBM_SETRANGE32, 0, 200);
SendMessage(progressBar, PBM_SETPOS, 100, 0);
ShowWindow(window, SW_SHOW);
MSG message = { 0 };
while (GetMessage(&message, nullptr, 0, 0))
DispatchMessage(&message);
}
In the example I am trying to replace the trackbar thumb with a bitmap image but obviously nothing is happening, it compiles but the trackbar is very small and not replaced by the image. Please I need advice on how to solve this problem.
I copy/pasted your code and ran it and saw the missing thumb too. Started tracing it and found that hbitmapThumb was null.... of course, because I didn't have pink.bmp! I changed that to load it from an absolute path... and noticed you missed the L on the thing:
hBitmapThumb = reinterpret_cast<HBITMAP>(LoadImage(NULL, reinterpret_cast<LPCWSTR>(L"c:\\users\\me\\Documents\\pink.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));
You might not need the absolute path, depends on where you have the files on your computer.
But you absolutely do need that L before the quote - otherwise you are reinterpret casting a narrow string as a wide string, which will be an invalid filename!
So yeah, I think you did the harder part of getting the custom draw etc right, but just made a char vs w_char mistake there. Let me know if fixing that L fixes the issue for you too.
Related
Please another way than using a layered window.
Setting SetLayeredWindowAttributes caused some issues in my GUI, for example, some controls inherit the background color as they contains transparency, same with pictures containing transparency, also there are a lot of other controls loaded on it which makes it hard to work with a layered window.
My goal is to create a GUI with rounded borders, for that, I will load a picture to behave as the background.
SetWindowRgn would not help, as it doesn't produce good edges borders because the pictures are being drawn with rounded corners and anti-aliasing.
Result usingSetLayeredWindowAttributes and SetWindowRgn:
I have tried to set WM_ERASEBKGND to true and inside of WM_PAINT use BitBlt with the rasters SRCCOPY | CAPTUREBLT painting an empty bitmap into the window DC, but the window still contains a background.
Also tried to just paint the image above, but the empty area is painted with the default background color.
The image used: https://i.imgur.com/TTLHoCf.png
I have created a similar ask-for-help topic in the Microsoft forum, the code below was adapted from an answer given by the user Castorix:
#include <windows.h>
#include <tchar.h>
// Gdiplus
#pragma comment( lib, "gdiplus.lib" )
#pragma comment( lib, "Msimg32.lib" )
#include <gdiplus.h>
#include <wingdi.h>
//#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int nWidth = 601, nHeight = 301;
#define IDC_BUTTON 11
HBITMAP hBitmap = NULL;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
hInst = hInstance;
WNDCLASSEX wcex =
{
sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),
LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("WindowClass"), NULL,
};
if (!RegisterClassEx(&wcex))
return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);
int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;
HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Test"), WS_POPUP, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);
if (!hWnd)
return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
//WndProc(hWnd, 15, 0, 0);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND hWndButton = NULL, hWndStatic = NULL;
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hDC;
BLENDFUNCTION bf{};
int x = 0, y = 0, dx = 0, dy = 0;
switch (message)
{
case WM_CREATE:
{
hWndButton = CreateWindowEx(0, L"Button", L"Click", WS_CHILD | WS_VISIBLE | BS_PUSHLIKE, 50, 60, 60, 32, hWnd, (HMENU)IDC_BUTTON, hInst, NULL);
// https://i.imgur.com/TTLHoCf.png
// Start Gdiplus
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// Load the image
Gdiplus::Color Color{ 255, 255, 255 };
hBitmap = NULL;
Gdiplus::Bitmap* bitmap = Gdiplus::Bitmap::FromFile(L"C:\\MEGAsync\\pic2.png", false);
if (bitmap)
{
bitmap->GetHBITMAP(Color, &hBitmap);
delete bitmap;
}
//hBitmap = (HBITMAP)LoadImage(NULL, L"C:\\MEGAsync\\pic.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
return 0;
}
break;
case WM_COMMAND:
{
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDC_BUTTON:
{
if (wmEvent == BN_CLICKED)
{
Beep(1000, 10);
}
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_ERASEBKGND:
return 0;
case WM_NCHITTEST:
return HTCAPTION;
case WM_NCRBUTTONDOWN:
{
Sleep(200);
DestroyWindow(hWnd);
}
break;
case WM_PAINT:
{
hDC = BeginPaint(hWnd, &ps);
OutputDebugString(L"WM_PAINT");
if (hBitmap)
{
BITMAP bm;
GetObject(hBitmap, sizeof(bm), &bm);
HDC hDCMem = CreateCompatibleDC(NULL);
HBITMAP hBitmapOld = (HBITMAP)SelectObject(hDCMem, hBitmap);
//SetBkMode(hDC, TRANSPARENT);
//SetBkMode(hDCMem, TRANSPARENT);
//TransparentBlt(hDC, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, bm.bmWidth, bm.bmHeight, RGB(192, 0, 192));
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.AlphaFormat = 1; // 0 - ignore source alpha, AC_SRC_ALPHA (1) - use source alpha
bf.SourceConstantAlpha = 10;
x = 0;
y = 0;
dx = nWidth;
dy = nHeight;
AlphaBlend(hDC, x, y, dx, dy, hDCMem, x, y, dx, dy, bf);
auto err = GetLastError();
//BitBlt(hDC, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, SRCCOPY | CAPTUREBLT);
//SelectObject(hDCMem, hBitmapOld);
DeleteDC(hDCMem);
}
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Working into the WM_PAINT message, I tried to draw the picture into the background using AlphaBlend, but it resulted in the window background being drawn with whatever be underneath it:
A layered window with the LWA_COLORKEY attribute is problematic if you want (Windows standard) child controls because there is no perfect color to pick as the transparent color.
However, layered windows have another mode; UpdateLayeredWindow. This function is perfect if you have an image (with alpha transparency) you want to use as the background. Just make sure the bitmap is pre-multiplied 32-bit ARGB before selecting it into the DC.
If for some crazy reason you can't use layered windows, the older option is SetWindowRgn.
The newer option is DirectComposition but I'm not sure if you are forced to set the layered style on the window.
Here's my code. I don't want a lib for device context/rc creation.
Nothing will draw at all. Maybe I set up DC or RC up wrong?
I'm trying to make a game engine from scratch.
#pragma once
#include <cstdlib>
#include <windows.h>
#include <GL/GL.h>
#pragma comment (lib, "opengl32.lib")
#include <iostream>
using namespace std;
class shadoeGE
{
public:
HWND s_Window = NULL;
int __cdecl Init(const char*, int, int);
private:
};
LRESULT __stdcall s_WindowEvent(HWND hWnd, unsigned int message, unsigned int wParam, long lParam)
{
switch (message)
{
default:
return DefWindowProc(hWnd, message, wParam, lParam);
};
};
int shadoeGE::Init(const char* title, int x, int y)
{
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = s_WindowEvent;
wc.hInstance = GetModuleHandle(NULL);
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = "sWindowClass";
wc.style = CS_OWNDC;
if (!RegisterClass(&wc))
{
return 0;
};
this->s_Window = CreateWindowA(wc.lpszClassName, title, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, x, y, 0, 0, GetModuleHandle(NULL), 0);
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
DispatchMessage(&msg);
};
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
32,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
24,
8,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
HDC deviceContextHandle = GetDC(this->s_Window);
int windowsChosen = ChoosePixelFormat(deviceContextHandle, &pfd);
SetPixelFormat(deviceContextHandle, windowsChosen, &pfd);
HGLRC ourOpenGLRenderingContext = wglCreateContext(deviceContextHandle);
wglMakeCurrent(deviceContextHandle, ourOpenGLRenderingContext);
glViewport(0, 0, 640, 480);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(0.1, 0.2, 0.3);
glVertex3f(0, 0, 0);
glVertex3f(1, 0, 0);
glVertex3f(0, 1, 0);
glEnd();
SwapBuffers(deviceContextHandle);
return 1;
};
You have to implement a message loop:
MSG msg;
while( ::GetMessage( &msg, 0, 0, 0 ) )
::DispatchMessage( &msg );
You have to draw the scene in the message loop, when the WM_PAINT message is sent:
LRESULT CALLBACK shadoeGE::s_WindowEvent( HWND hWnd, unsigned int msg, WPARAM wparam, LPARAM lparam )
{
switch(msg)
{
// [...]
case WM_PAINT:
wnd.Display();
break;
}
return DefWindowProc( hWnd, msg, wparam, lparam );
}
void shadoeGE::Display( void )
{
RECT clientRect;
::GetClientRect( hOGLWnd, &clientRect );
glViewport( 0, 0, clientRect.right-clientRect.left, clientRect.bottom-clientRect.top );
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// [...]
HDC hDC = ::GetDC( hOGLWnd );
::SwapBuffers( hDC );
::ReleaseDC( hOGLWnd, hDC );
}
See the example:
#include <windows.h>
#include <GL/gl.h>
#include <vector>
#include <stdexcept>
class shadoeGE
{
public:
static LRESULT CALLBACK s_WindowEvent( HWND hWnd, unsigned int msg, WPARAM wparam, LPARAM lparam );
HWND Init( int width, int height );
void DestroyWindow( void );
void MessageLoop( void );
void Display( void );
private:
HWND hOGLWnd = NULL;
HGLRC hOGLRenderContext = NULL;
};
std::wstring wnd_class( L"my_wnd_class" );
shadoeGE wnd;
int main()
{
int w = 800;
int h = 600;
HWND hWnd = wnd.Init( w, h );
if ( hWnd == 0 )
throw std::runtime_error( "error initializing window" );
wnd.MessageLoop();
wnd.DestroyWindow();
return 0;
}
HWND shadoeGE::Init( int width, int height )
{
// Get module handle
HMODULE hModule = ::GetModuleHandle( 0 );
if (!hModule)
return NULL;
// Create window class
WNDCLASSEX wndClassData;
memset( &wndClassData, 0, sizeof( WNDCLASSEX ) );
wndClassData.cbSize = sizeof( WNDCLASSEX );
wndClassData.style = CS_DBLCLKS;
wndClassData.lpfnWndProc = s_WindowEvent;
wndClassData.cbClsExtra = 0;
wndClassData.cbWndExtra = 0;
wndClassData.hInstance = hModule;
wndClassData.hIcon = ::LoadIcon(0,IDI_APPLICATION);
wndClassData.hCursor = ::LoadCursor(0,IDC_ARROW);
wndClassData.hbrBackground = ::CreateSolidBrush(COLOR_WINDOW+1);
wndClassData.lpszMenuName = 0;
wndClassData.lpszClassName = wnd_class.c_str();
wndClassData.hIconSm = 0;
if ( !::RegisterClassEx( &wndClassData ) )
return false;
// Creaate Window
hOGLWnd = ::CreateWindow( wnd_class.c_str(), NULL, WS_OVERLAPPEDWINDOW, 0, 0, width, height, NULL, NULL, hModule, NULL);
if ( hOGLWnd == NULL )
return NULL;
// Get device context
HDC hDC = ::GetDC( hOGLWnd );
// Create OpenGL context
DWORD pixelFormatFlags = PFD_SUPPORT_OPENGL | PFD_SUPPORT_COMPOSITION | PFD_GENERIC_ACCELERATED | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
pixelFormatFlags, //Flags
PFD_TYPE_RGBA, //The kind of framebuffer. RGBA or palette.
32, //Colordepth of the framebuffer.
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
24, //Number of bits for the depthbuffer
8, //Number of bits for the stencilbuffer
0, //Number of Aux buffers in the framebuffer.
PFD_MAIN_PLANE,
0,
0, 0, 0
};
int pixelFormat = ::ChoosePixelFormat( hDC, &pfd );
::SetPixelFormat( hDC, pixelFormat, &pfd );
hOGLRenderContext = ::wglCreateContext( hDC );
// make OpenGL context the current context
::wglMakeCurrent( hDC, hOGLRenderContext );
// release device context
::ReleaseDC( hOGLWnd, hDC );
// show the window
::ShowWindow( hOGLWnd, SW_SHOWDEFAULT );
return hOGLWnd;
}
void shadoeGE::MessageLoop( void )
{
MSG msg;
while( ::GetMessage( &msg, 0, 0, 0 ) )
::DispatchMessage( &msg );
}
void shadoeGE::DestroyWindow(void)
{
::DestroyWindow( hOGLWnd );
::wglMakeCurrent( NULL, NULL );
::wglDeleteContext( hOGLRenderContext );
HMODULE hModule = ::GetModuleHandle( 0 );
if (!hModule)
return;
::UnregisterClass( wnd_class.c_str(), hModule );
}
LRESULT CALLBACK shadoeGE::s_WindowEvent( HWND hWnd, unsigned int msg, WPARAM wparam, LPARAM lparam )
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_PAINT:
wnd.Display();
break;
}
return DefWindowProc( hWnd, msg, wparam, lparam );
}
void shadoeGE::Display( void )
{
RECT clientRect;
::GetClientRect( hOGLWnd, &clientRect );
glViewport( 0, 0, clientRect.right-clientRect.left, clientRect.bottom-clientRect.top );
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glBegin(GL_TRIANGLES);
glColor3f(0.1, 0.2, 0.3);
glVertex3f(0, 0, 0);
glVertex3f(1, 0, 0);
glVertex3f(0, 1, 0);
glEnd();
HDC hDC = ::GetDC( hOGLWnd );
::SwapBuffers( hDC );
::ReleaseDC( hOGLWnd, hDC );
}
I'm need to draw border on top another application's window (the main purpose is to highlight window user chooses from running applications list). I'm trying to draw border on top of native window border, but the border isn't drawn. Here is the code:
HPEN framePen = ::CreatePen(PS_SOLID, 5, RGB(255, 0, 0));
HWND handle = FindWindow(L"ConsoleWindowClass", L"C:\\WINDOWS\\system32\\cmd.exe");
WINDOWPLACEMENT winPlacement;
GetWindowPlacement(handle, &winPlacement);
if (winPlacement.showCmd == SW_SHOWMINIMIZED)
{
ShowWindow(handle, SW_RESTORE);
}
SetWindowPos(handle, HWND_TOP, 0, 0, 0, 0, SWP_ASYNCWINDOWPOS | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
SetForegroundWindow(handle);
PAINTSTRUCT ps;
RECT rect = {};
::GetClientRect(handle, &rect);
HDC hdc = ::BeginPaint(handle, &ps);
::SelectObject(hdc, framePen);
::Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
::EndPaint(handle, &ps);
In this example used handle of cmd window, but in fact it doesn't matter.
Could you please tell why border isn't drawn and how to draw it?
Thanks.
You can't draw directly on another window, because the system may refresh the window at any time, overwriting your drawing.
To make your drawing persistent, create a layered window, positioned on top of the other window.
Create the window with WS_EX_LAYERED flag.
Pass a color key to SetLayeredWindowAttributes().
In your WM_PAINT handler, draw the inside of the rectangle with the color key (by using it for the brush). Everything you draw with the color key will become transparent. Draw the border of the rectangle with the desired color (by using it for the pen).
Here is a minimal example to get you started. The frame can be moved around by drag-n-drop.
Note there is no error handling to keep the sample code concise. You should check the return value of each Windows API call.
#include <windows.h>
const COLORREF MY_COLOR_KEY = RGB( 255, 0, 255 );
int APIENTRY wWinMain(
HINSTANCE hInstance, HINSTANCE /*hPrevInst*/, LPWSTR /*lpCmdLine*/, int nCmdShow )
{
WNDCLASSW wc{ sizeof( wc ) };
wc.hInstance = hInstance;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hCursor = LoadCursor( nullptr, IDC_ARROW );
wc.hbrBackground = reinterpret_cast<HBRUSH>( COLOR_BTNFACE + 1 );
wc.lpszClassName = L"MyTransparentFrame";
wc.lpfnWndProc = []( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) -> LRESULT
{
switch( msg )
{
case WM_PAINT:
{
PAINTSTRUCT ps{};
HDC hdc = BeginPaint( hwnd, &ps );
RECT rc{}; GetClientRect( hwnd, &rc );
HPEN hPen = CreatePen( PS_SOLID, 20, GetSysColor( COLOR_HIGHLIGHT ) );
HBRUSH hBrush = CreateSolidBrush( MY_COLOR_KEY );
HGDIOBJ hOldPen = SelectObject( hdc, hPen );
HGDIOBJ hOldBrush = SelectObject( hdc, hBrush );
Rectangle( hdc, rc.left, rc.top, rc.right, rc.bottom );
if( hOldPen )
SelectObject( hdc, hOldPen );
if( hOldBrush )
SelectObject( hdc, hOldBrush );
if( hPen )
DeleteObject( hPen );
if( hBrush )
DeleteObject( hBrush );
EndPaint( hwnd, &ps );
}
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
case WM_NCHITTEST:
return HTCAPTION; // to be able to drag the window around
break;
default:
return DefWindowProcW( hwnd, msg, wp, lp );
}
return 0;
};
RegisterClassW( &wc );
HWND hwnd = CreateWindowExW( WS_EX_LAYERED, wc.lpszClassName, L"", WS_POPUP,
200, 200, 800, 600, nullptr, nullptr, hInstance, nullptr );
SetLayeredWindowAttributes( hwnd, MY_COLOR_KEY, 255, LWA_COLORKEY );
ShowWindow( hwnd, nCmdShow );
MSG msg;
while( GetMessage( &msg, nullptr, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return (int) msg.wParam;
}
Finally I managed to solve the problem with the following code:
const COLORREF MY_COLOR_KEY = RGB(255, 128, 0);
HWND cmdHanlde = NULL;
constexpr unsigned int timerIdWindowUpdate = 1;
constexpr unsigned int timerIdFrameColor = 2;
bool tick = false;
bool minimized = false;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"MyTransparentFrame";
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps{};
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc{}; GetClientRect(hwnd, &rc);
HPEN hPen = CreatePen(PS_SOLID, 5, tick ? RGB(255, 128, 1) : RGB(255, 201, 14));
HBRUSH hBrush = CreateSolidBrush(MY_COLOR_KEY);
HGDIOBJ hOldPen = SelectObject(hdc, hPen);
HGDIOBJ hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
if (hOldPen)
SelectObject(hdc, hOldPen);
if (hOldBrush)
SelectObject(hdc, hOldBrush);
if (hPen)
DeleteObject(hPen);
if (hBrush)
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
}
break;
case WM_TIMER:
{
if (wp == timerIdWindowUpdate)
{
WINDOWPLACEMENT windowPlacement = { sizeof(WINDOWPLACEMENT), };
if (::GetWindowPlacement(cmdHanlde, &windowPlacement))
{
if (windowPlacement.showCmd == SW_SHOWMINIMIZED
|| !IsWindowVisible(cmdHanlde))
{
ShowWindow(hwnd, SW_HIDE);
minimized = true;
}
else
{
RECT rect = {};
DwmGetWindowAttribute(cmdHanlde, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
MONITORINFO monInfo;
monInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfoW(MonitorFromWindow(cmdHanlde, MONITOR_DEFAULTTONEAREST), &monInfo);
if (cmdHanlde != NULL && ::IsZoomed(cmdHanlde))
{
rect.left = monInfo.rcWork.left;
rect.top = monInfo.rcWork.top;
rect.bottom = monInfo.rcWork.bottom > rect.bottom ? rect.bottom : monInfo.rcWork.bottom;
rect.right = monInfo.rcWork.right > rect.right ? rect.right : monInfo.rcWork.right;
}
if (minimized)
{
::SetWindowPos(hwnd, cmdHanlde, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
minimized = false;
}
else
{
::SetWindowPos(cmdHanlde, hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
::SetWindowPos(hwnd, 0, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
SWP_SHOWWINDOW);
}
}
}
}
else if (wp == timerIdFrameColor)
{
tick = !tick;
::RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE);
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hwnd, msg, wp, lp);
}
return 0;
};
RegisterClassEx(&wc);
HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TRANSPARENT, wc.lpszClassName, L"", WS_POPUP | WS_VISIBLE | WS_DISABLED,
0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
::SetTimer(hwnd, timerIdWindowUpdate, 50, NULL);
::SetTimer(hwnd, timerIdFrameColor, 500, NULL);
SetLayeredWindowAttributes(hwnd, MY_COLOR_KEY, 255, LWA_COLORKEY);
ShowWindow(hwnd, SW_SHOW);
cmdHanlde = FindWindow(L"ConsoleWindowClass", L"C:\\WINDOWS\\system32\\cmd.exe");
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
May be it is not the best solution, but it works for me. Could you please take a look on it and tell if there something to improve?
I was wondering if there is a way to get the font of the following windows font inside the red box.
Is there any way to receive this font as a HFONT via a Win32 API function?
Thanks in advance!
This is done with uxtheme.dll, class AeroWizard, part HeaderArea. Either state 0 or state 1 (NoMargins) will work. Here's a sample, both using DrawThemeText() and manually. (Note that being a demonstration program, it does no error checking.)
// 20 june 2016
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define _WIN32_WINDOWS 0x0600
#define _WIN32_IE 0x0700
#define NTDDI_VERSION 0x06000000
#include <windows.h>
#include <uxtheme.h>
#include <vsstyle.h>
#include <vssym32.h>
HWND mainwin;
HWND edit;
void redraw(void)
{
HDC dc;
PAINTSTRUCT ps;
WCHAR text[1024];
HTHEME theme;
LOGFONTW lf;
HFONT font, oldfont;
COLORREF color;
SIZE extent;
RECT r;
dc = BeginPaint(mainwin, &ps);
theme = OpenThemeData(mainwin, VSCLASS_AEROWIZARD);
GetThemeFont(theme, dc, AW_HEADERAREA, 0, TMT_FONT, &lf);
font = CreateFontIndirectW(&lf);
GetThemeColor(theme, AW_HEADERAREA, 0, TMT_TEXTCOLOR, &color);
GetWindowTextW(edit, text, 1024);
GetThemeTextExtent(theme, dc, AW_HEADERAREA, 0,
text, -1, 0, NULL, &r);
extent.cx = r.right - r.left;
extent.cy = r.bottom - r.top;
SetWindowPos(edit, NULL,
10, 10, 500, 20,
SWP_NOOWNERZORDER | SWP_NOZORDER);
r.left = 10;
r.top = 40;
r.right = 10 + extent.cx;
r.bottom = r.top + extent.cy;
DrawThemeText(theme, dc, AW_HEADERAREA, 0,
text, -1, 0, 0, &r);
oldfont = (HFONT) SelectObject(dc, font);
color = SetTextColor(dc, color);
r.top = r.bottom + 10;
r.bottom = r.top + extent.cy;
DrawTextW(dc, text, -1, &r, 0);
SetTextColor(dc, color);
SelectObject(dc, oldfont);
DeleteObject(font);
CloseThemeData(theme);
EndPaint(mainwin, &ps);
}
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_PAINT:
redraw();
return 0;
case WM_COMMAND:
InvalidateRect(hwnd, NULL, TRUE);
break;
case WM_CLOSE:
PostQuitMessage(0);
break;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
int main(void)
{
WNDCLASSW wc;
MSG msg;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndproc;
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
RegisterClassW(&wc);
mainwin = CreateWindowExW(0,
L"mainwin", L"Main Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
640, 200,
NULL, NULL, NULL, NULL);
edit = CreateWindowExW(WS_EX_CLIENTEDGE,
L"EDIT", L"Top is DrawThemeText(), bottom is manual",
WS_CHILD | WS_VISIBLE,
10, 10, 100, 100,
mainwin, (HMENU) 100, NULL, NULL);
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
while (GetMessageW(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
Well hello,
I have a problem and I didn't find any solution to this by searching; basically I want to make a simple login where you can switch through the controls ( username, password, login button ) with tabbing.
The only thing happening when pressing TAB is that it just completely selects the text from the current edit box.
Here's my code:
HWND g_hLogin1, g_hLogin2, g_hLogin3;
#define ID_BUTTON_LOGIN 201
LRESULT CALLBACK LoginProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch( uMsg )
{
case WM_CREATE:
{
g_hLogin1 = CreateWindow( "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER, 50, 40, 200, 20, hWnd, 0, 0, 0 );
g_hLogin2 = CreateWindow( "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_PASSWORD, 50, 80, 200, 20, hWnd, 0, 0, 0 );
g_hLogin3 = CreateWindow( "BUTTON", "Login", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 100, 120, 100, 22, hWnd, (HMENU)ID_BUTTON_LOGIN, 0, 0 );
HFONT font = CreateFont( 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial" );
SendMessage( g_hLogin1, WM_SETFONT, (WPARAM)font, 0 );
SendMessage( g_hLogin2, WM_SETFONT, (WPARAM)font, 0 );
SendMessage( g_hLogin3, WM_SETFONT, (WPARAM)font, 0 );
break;
}
case WM_DESTROY:
{
PostQuitMessage( 0 );
return 0;
}
case WM_KEYDOWN:
{
if( wParam == VK_TAB )
{
HWND hNext = GetWindow( hWnd, GW_HWNDNEXT );
if( !hNext )
hNext = GetWindow( hWnd, GW_HWNDLAST );
SendMessage( hNext, EM_SETSEL, (WPARAM)0, (LPARAM)-1 );
SetFocus( hNext );
}
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint( hWnd, &ps );
HFONT font = CreateFont( 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial" );
SelectObject( hDC, font );
RECT rc[] =
{
{ 50, 22, 0, 0 },
{ 50, 62, 0, 0 }
};
DrawText( hDC, "Username:", -1, &rc[ 0 ], DT_NOCLIP );
DrawText( hDC, "Password:", -1, &rc[ 1 ], DT_NOCLIP );
EndPaint( hWnd, &ps );
break;
}
case WM_COMMAND:
{
if( LOWORD( wParam ) == ID_BUTTON_LOGIN )
{
int len1 = GetWindowTextLength( g_hLogin1 );
int len2 = GetWindowTextLength( g_hLogin2 );
char* username = new char[ len1 + 1 ];
char* password = new char[ len2 + 1 ];
GetWindowText( g_hLogin1, username, len1 + 1 );
GetWindowText( g_hLogin2, password, len2 + 1 );
if( g_pServer->Login( username, password ) )
{
ShowWindow( g_hLogin, SW_HIDE );
ShowWindow( g_hMainWnd, SW_SHOW );
}
}
break;
}
default:
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
bool SetupClass( HINSTANCE hInstance, char* szClassName, WNDPROC wndProc )
{
WNDCLASSEX wc = { 0 };
wc.cbClsExtra = 0;
wc.cbSize = sizeof( WNDCLASSEX );
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
wc.hCursor = LoadCursor( 0, IDC_ARROW );
wc.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON1 ) );
wc.hIconSm = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON1 ) );
wc.hInstance = hInstance;
wc.lpfnWndProc = wndProc;
wc.lpszClassName = szClassName;
wc.lpszMenuName = "";
wc.style = CS_HREDRAW | CS_VREDRAW;
return RegisterClassEx( &wc );
}
int APIENTRY WinMain( HINSTANCE hThis, HINSTANCE hPrev, LPSTR lpCmd, int iCmd )
{
if( !SetupClass( hThis, "zcfw001", LoginProc ) )
return 1;
g_hLogin = CreateWindowEx( WS_EX_CONTROLPARENT, "zcfw001", "Login", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, 0, 0, hThis, 0 );
if( !g_hLogin )
return 1;
ShowWindow( g_hLogin, SW_SHOW );
MSG msg;
while( 1 )
{
if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
break;
if( !IsDialogMessage( g_hLogin, &msg ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
}
return msg.wParam;
}
You should use WS_TABSTOP style to make tab stops working.