My original goal was to place an X button inside an edit control (it later turned out that this edit control by itself must be a part of the combo box.) I've been suggested to use this article as a guidance.
To make it look a little bit "prettier" I decided to use a toolbar for that X button instead. So I came up with the following code. Here's how the control is initialized inside the ComboBox:
//Initialization
#define EDIT_X_TOOLBAR_BTN_W 10
#define EDIT_X_TOOLBAR_BTN_H 11
//Find edit control handle using GetComboBoxInfo() API
COMBOBOXINFO cbi2 = {0};
cbi2.cbSize = sizeof(cbi2);
GetComboBoxInfo(hComboWnd, &cbi2);
HWND hEditCtrl = cbi2.hwndItem;
HINSTANCE hInst = ::GetModuleHandle(NULL);
//Create toolbar as a child of the edit control
hWndToolbar = ::CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE |
CCS_NODIVIDER | CCS_NOPARENTALIGN |
CCS_NORESIZE | CCS_NOMOVEY | TBSTYLE_FLAT | TBSTYLE_TRANSPARENT,
0, 0, 0, 0,
hEditCtrl,
(HMENU)ID_CMD_EDIT_X_TOOLBAR, hInst, 0);
::SendMessage(hWndToolbar, TB_BUTTONSTRUCTSIZE, (WPARAM) sizeof(TBBUTTON), 0);
::SendMessage(hWndToolbar, TB_SETBITMAPSIZE, 0, MAKELONG(EDIT_X_TOOLBAR_BTN_W, EDIT_X_TOOLBAR_BTN_H));
::SendMessage(hWndToolbar, TB_SETBUTTONSIZE, 0, MAKELONG(EDIT_X_TOOLBAR_BTN_W, EDIT_X_TOOLBAR_BTN_H));
//Load bitmap
TBADDBITMAP tbab = {0};
tbab.hInst = hInst;
tbab.nID = IDB_BITMAP_TOOLBAR_X;
VERIFY(::SendMessage(hWndToolbar, TB_ADDBITMAP, 1, (LPARAM)&tbab) != -1);
TBBUTTON tbbs[1] = {0};
tbbs[0].iBitmap = 0;
tbbs[0].idCommand = CMD_EDIT_X_TOOLBAR_CLOSE;
tbbs[0].fsState = TBSTATE_ENABLED;
tbbs[0].fsStyle = TBSTYLE_BUTTON;
tbbs[0].iString = -1;
VERIFY(::SendMessage(hWndToolbar, TB_ADDBUTTONS, SIZEOF(tbbs), (LPARAM)tbbs));
//Subclass edit control
oldProc = (WNDPROC)::SetWindowLong(hEditCtrl, GWL_WNDPROC, (LONG)wndProcEditCtrl);
//And redraw the edit control
::SetWindowPos(hEditCtrl, NULL, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER);
And this is the subclassed window proc for the edit control:
CRect rcToolbar;
LRESULT CALLBACK wndProcEditCtrl(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NCCALCSIZE:
{
if(wParam)
{
//First let edit box process it
::CallWindowProc(oldProc, hWnd, msg, wParam, lParam);
RECT* pRc = (RECT*)lParam;
//Define toolbar size & move it
int nTB_w = pRc->bottom - pRc->top;
int nTB_h = nTB_w;
//Adjust the edit's client area
pRc->right -= nTB_w;
//Set toolbar rect
rcToolbar.SetRect(pRc->right, pRc->top, pRc->right + nTB_w, pRc->top + nTB_h);
//And move the toolbar
::MoveWindow(hWndToolbar, pRc->right, pRc->top, nTB_w, nTB_h, TRUE);
return 0;
}
}
break;
case WM_NCHITTEST:
{
POINT pnt;
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
if(::ScreenToClient(hWnd, &pnt))
{
if(rcToolbar.PtInRect(pnt))
{
return HTBORDER;
}
}
}
break;
}
return ::CallWindowProc(oldProc, hWnd, msg, wParam, lParam);
}
The issue is that I can see that the toolbar is created (via Spy++) but it remains inactive and I don't see my button (all I get is a gray rectangle on the right):
To make sure that I can create the toolbar itself, if I change its parent window from the edit control (i.e. hEditCtrl) to the main dialog window, it is displayed (in a wrong place) and responds to clicks just fine. So my conclusion is that I must be blocking some messages to it in my subclass. The question is which ones?
Any idea what am I missing here?
Related
Currently, I'm trying to make a simple Win32 application that switches back and forth between fullscreen and windowed mode. I'm using the Chromium way of doing this as a reference. I created a class to handle windows to make things simpler.
I expected this to work by toggling fullscreen when the F4 key was pressed. Unfortunately, the styles appear to be applied correctly, but the window doesn't resize or move to the correct area. It also snaps to the top left corner of the screen for a moment before it returns to its original position. For some reason, input for the window passes to the one below it when I toggle fullscreen. Then I have to go to Task Manager to kill the program because I can't close the window or application.
I've tried storing the styles of the HWND in a class variable at creation (it starts out in windowed mode) and using the value to create the necessary style for the fullscreen window and restoring the windowed mode window. I have also tried immediately getting the window styles with GetWindowLongPtr when the ToggleFullscreen function is called. Both of these do not work.
Here is my code:
WindowHandler.h
#include <Windows.h> // Win32 API
#ifndef WINDOWHANDLER
#define WINDOWHANDLER
class WindowHandler // WindowHandler
{
public:
WindowHandler(); // Constructor
void Destroy() { DestroyWindow(hwnd); } // Destroy the handler
void ToggleFullscreen(); // Toggle fullscreen
protected:
static LRESULT CALLBACK WindowProc // Window procedure
(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam
);
HWND hwnd; // The window
// Everything involved in fullscreen
bool fullscreen = false; // Whether the window is fullscreen or not
RECT windowRect = {}; // The restored window size
long int windowStyles = 0; // The restored window styles
long int extendedWindowStyles = 0; // The restored window extended styles
};
#endif
WindowHandler.cpp
#include "WindowHandler.h" // Header file
WindowHandler::WindowHandler() // Constructor
{
WNDCLASS wndClass = {}; // The window information
wndClass.lpfnWndProc = WindowProc;
wndClass.hInstance = GetModuleHandle(nullptr);
wndClass.lpszClassName = L"FullscreenTest";
RegisterClass(&wndClass); // Register the window
hwnd = CreateWindowEx // Create the window and store a pointer to the handler for the procedure to use
(
0,
L"FullscreenTest",
L"Stack Overflow Repro",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
nullptr,
nullptr,
GetModuleHandle(nullptr),
this
);
if (hwnd == nullptr) Destroy(); // Destroy the handler if the window is invalid
else // Otherwise...
{
GetWindowRect(hwnd, &windowRect); // Store the window size
windowStyles = GetWindowLongPtr(hwnd, GWL_STYLE); // Store the window styles
extendedWindowStyles = GetWindowLongPtr(hwnd, GWL_EXSTYLE); // Store the extended window styles
ShowWindow(hwnd, SW_SHOW); // Show the window
}
}
void WindowHandler::ToggleFullscreen() // Toggle fullscreen
{
if (!fullscreen) // If fullscreen is not enabled
{
MONITORINFO monitorInfo; // Get the monitor info
monitorInfo.cbSize = sizeof(monitorInfo);
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), &monitorInfo);
SetWindowLongPtr(hwnd, GWL_STYLE, windowStyles & ~(WS_CAPTION | WS_THICKFRAME)); // Set the window styles
SetWindowLongPtr // Set the extended window styles
(
hwnd,
GWL_EXSTYLE,
extendedWindowStyles & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)
);
SetWindowPos // Resize, move, and refresh the window
(
hwnd,
nullptr,
monitorInfo.rcMonitor.left,
monitorInfo.rcMonitor.top,
monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED
);
fullscreen = true; // Indicate that fullscreen is on
}
else // Otherwise...
{
SetWindowLongPtr(hwnd, GWL_STYLE, windowStyles); // Set the window styles
SetWindowLongPtr(hwnd, GWL_EXSTYLE, extendedWindowStyles); // Set the extended window styles
SetWindowPos // Resize, move, and refresh the window
(
hwnd,
nullptr,
windowRect.left,
windowRect.top,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED
);
fullscreen = false; // Indicate that fullscreen is off
}
}
LRESULT CALLBACK WindowHandler::WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) // Window procedure
{
WindowHandler* handlerPtr; // Pointer to the window handler
if (message == WM_CREATE) // If the window is being created...
{
CREATESTRUCT* createStruct = reinterpret_cast<CREATESTRUCT*>(lParam); // Get the pointer's container
handlerPtr = reinterpret_cast<WindowHandler*>(createStruct->lpCreateParams); // Get the pointer
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)handlerPtr); // Store the pointer
}
else handlerPtr = reinterpret_cast<WindowHandler*>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); // Otherwise, get the pointer
if (handlerPtr) { // If the pointer is valid...
switch (message)
{
case WM_PAINT: // Paint the window
{
PAINTSTRUCT paintStruct;
HDC hdc = BeginPaint(hwnd, &paintStruct);
FillRect(hdc, &paintStruct.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &paintStruct);
}
return 0;
case WM_DESTROY: // Destroy the window
PostQuitMessage(0);
return 0;
case WM_KEYDOWN: // Process input
switch ((int)wParam)
{
case VK_ESCAPE: // Quit if the escape key is pressed
handlerPtr->Destroy();
break;
case VK_F4: // Toggle fullscreen if F4 is pressed
handlerPtr->ToggleFullscreen();
break;
}
return 0;
default: // Do the default action if the message was not processed
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
else return DefWindowProc(hwnd, message, wParam, lParam); // Do the default action if the pointer is not valid
}
main.cpp
#include "WindowHandler.h" // Window handler
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) // Win32 main function
{
WindowHandler repro; // Create a window handler
MSG msg = {}; // Message structure
while (GetMessage(&msg, nullptr, 0, 0)) // Message loop
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Replace:
windowStyles = GetWindowLongPtr(hwnd, GWL_STYLE);
extendedWindowStyles = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
ShowWindow(hwnd, SW_SHOW); // Show the window
With:
ShowWindow(hwnd, SW_SHOW); // Show the window
windowStyles = GetWindowLongPtr(hwnd, GWL_STYLE);
extendedWindowStyles = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
The WS_VISIBLE style bit does not get set until after that first ShowWindow(SW_SHOW).
I made a little window with the win32 api and want to open a popup menu when I click on the window. When I click on it again I want to close the menu if it's still open.
I open the menu on WM_LBUTTONUP, because I want to drag the window on WM_LBUTTONDOWN.
I know when the menu appears or disappears by WM_ENTERMENULOOP and WM_EXITMENULOOP.
I know how to close the menu programmatically, but unfortunately I don't know how to decide whether the menu is open or closed on WM_LBUTTONUP. The problem is that the menu gets automatically closed on WM_LBUTTONDOWN and that's why I can't save the current state of the menu.
It would be great if someone has a hint how to solve this problem.
Additional information:
the whole window is a borderless client-area with a bitmap painted on it
hWnd = CreateWindowExW(WS_EX_TOPMOST, L"MyWindow", 0, WS_POPUP, wndPosX, wndPosY, m_WndWidth, m_WndHeight, 0, 0, m_hInst, 0);
the bitmap gets loaded on WM_CREATE in the WindowProcedure of the window and is painted on WM_PAINT
m_hBitmap = (HBITMAP)LoadImageA(NULL, "MyBitmap.bmp", IMAGE_BITMAP, m_WndWidth, m_WndHeight, LR_LOADFROMFILE);
the window isn't draggable by default, because I don't have a non-client area, that's why I move the window manually on WM_MOUSEMOVE according to the current cursor position
On WM_LBUTTONUP I create the popup menu on top of the window
HMENU hPopupMenu = CreatePopupMenu();
InsertMenuW(hPopupMenu, 0, MF_BYPOSITION | MF_STRING, ID_ITEM_A, L"ItemA");
SetForegroundWindow(hWnd);
RECT wndRect;
GetWindowRect(hWnd, &wndRect);
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, wndRect.left, wndRect.top, 0, hWnd, NULL);
Here is the solution using WindowsHooks for mouse event
You have to catch mouse down event inside the window client area and skip the next mouse up event
in global scope
static HHOOK hMouseHook = 0;
static HWND hMainWindow = 0;
static int nSkipClick = 0;
LRESULT CALLBACK MouseProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
if (nCode < 0)
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
if (wParam == WM_LBUTTONDOWN) {
MOUSEHOOKSTRUCT* p = (MOUSEHOOKSTRUCT*)lParam;
if (WindowFromPoint(p->pt) == hMainWindow) {
POINT pt = p->pt;
ScreenToClient(hMainWindow, &pt);
RECT rct;
GetClientRect(hMainWindow, &rct);
if (PtInRect(&rct, pt))nSkipClick = 1;
}
}
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
in WndProc
case WM_LBUTTONUP:
if (nSkipClick == 0) { // menu not shown before
hMouseHook = SetWindowsHookEx(WH_MOUSE, &MouseProc, NULL, GetCurrentThreadId());
hMainWindow = hWnd;
HMENU hPopupMenu = CreatePopupMenu();
InsertMenuW(hPopupMenu, 0, MF_BYPOSITION | MF_STRING, ID_ITEM_A, L"ItemA");
SetForegroundWindow(hWnd);
RECT wndRect;
GetWindowRect(hWnd, &wndRect);
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, wndRect.left, wndRect.top, 0, hWnd, NULL);
UnhookWindowsHookEx(hMouseHook);
hMouseHook = 0;
}
else {
nSkipClick = 0;
}
break;
I hope this is what you want to do.
I'm new to Win32 and I am trying to have the window frame extended in the hopes of having a custom menu over there in the non-client area while also adding a rebar. Unfortunately, the extended frame causes the rebar to be placed at the top of the window like you see in this pic instead of below the title bar. The rebar seems to be one of those controls with fixed position like the menu bar. I tried moving the position programmatically but I had no succes. I tried creating the rebar before actually extending the frame but the result is the same.
Here's my code taken mostly from this site and MSDN:
// Called from the window procedure (see below) and adds the rebar
VOID MainWindow::addControls(HWND hwnd) {
INITCOMMONCONTROLSEX iccx;
iccx.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccx.dwICC = ICC_BAR_CLASSES | ICC_COOL_CLASSES;
InitCommonControlsEx(&iccx);
// ...
wRebar = CreateWindowEx(0, REBARCLASSNAME, L"",
WS_VISIBLE | WS_CHILD | RBS_AUTOSIZE,
0, 0, 0, 0, // modifying these does nothing
hwnd, (HMENU)9000, GetModuleHandle(NULL), NULL);
hr = GetLastError();
REBARINFO ri = { 0 };
ri.cbSize = sizeof(REBARINFO);
SendMessage(wRebar, RB_SETBARINFO, 0, (LPARAM)&ri);
// Insert a image
HICON hImg = (HICON)LoadImage(0, IDI_QUESTION, IMAGE_ICON, 0, 0, LR_SHARED);
HWND hwndImg = CreateWindow(L"STATIC", NULL,
WS_CHILD | WS_VISIBLE | SS_ICON | SS_REALSIZEIMAGE | SS_NOTIFY,
NULL, NULL, NULL, NULL, wRebar, (HMENU)NULL, GetModuleHandle(NULL), NULL);
// Set static control image
SendMessage(hwndImg, STM_SETICON, (WPARAM)hImg, NULL);
REBARBANDINFO rbBand;
rbBand.cbSize = sizeof(REBARBANDINFO);
rbBand.fMask = RBBIM_STYLE | RBBIM_CHILDSIZE | RBBIM_CHILD | RBBIM_SIZE;
rbBand.fStyle = RBBS_CHILDEDGE | RBBS_NOGRIPPER;
rbBand.hwndChild = hwndImg;
rbBand.cxMinChild = 0;
rbBand.cyMinChild = 20;
rbBand.cx = 20;
// Insert the img into the rebar
SendMessage(wRebar, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&rbBand);
// Insert a blank band
rbBand.cbSize = sizeof(REBARBANDINFO);
rbBand.fMask = RBBIM_STYLE | RBBIM_SIZE;
rbBand.fStyle = RBBS_CHILDEDGE | RBBS_HIDETITLE | RBBS_NOGRIPPER;
rbBand.cx = 1;
// Insert the blank band into the rebar
SendMessage(wRebar, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&rbBand);
}
// Window Procedure
LRESULT CALLBACK MainWindow::wProcedure(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE:
{
addControls(hwnd);
break;
}
case WM_ACTIVATE:
DwmExtendFrameIntoClientArea(hwnd, &(m.realNonclientInsets)); // <-- a RECT that holds the metrics of the non-client area
break;
case WM_NCCALCSIZE:
if (wParam != (WPARAM)FALSE) {
NCCALCSIZE_PARAMS *op = (NCCALCSIZE_PARAMS *)lParam;
NCCALCSIZE_PARAMS np;
if (!defWindowProcFirst)
return 0;
np = *op;
DefWindowProcW(hwnd, uMsg, wParam, (LPARAM)(&np));
op->rgrc[0].left = np.rgrc[0].left;
op->rgrc[0].right = np.rgrc[0].right;
op->rgrc[0].bottom = np.rgrc[0].bottom;
return 0;
}
break;
// ...
}
Is there any way to force the rebar into it's normal position while also having the window frame extended?
I'm trying to achieve something like Visual Studio installer does with borderless window and drop shadow:
I tried various options like CS_DROPSHADOW and DWM API, but as soon as I apply the WS_THICKFRAME style the shadow disappears.
This is my code for creating and centering a window:
RECT R = {0, 0, _clientWidth, _clientHeight};
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
_mainWnd = CreateWindow(L"D3DWndClassName", _mainWndCaption.c_str(), WS_OVERLAPPEDWINDOW, 100, 100, R.right, R.bottom, nullptr, nullptr, _appInst, nullptr);
if(!_mainWnd){
MessageBox(nullptr, L"CreateWindow FAILED", nullptr, 0);
PostQuitMessage(0);
}
RECT rc;
GetWindowRect(_mainWnd, &rc);
LONG lStyle = GetWindowLong(_mainWnd, GWL_STYLE);
lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU );
SetWindowLong(_mainWnd, GWL_STYLE, lStyle);
int xPos = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2;
int yPos = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2;
SetWindowPos(_mainWnd, 0, xPos, yPos, _clientWidth, _clientHeight, SWP_NOZORDER);
ShowWindow(_mainWnd, SW_SHOW);
UpdateWindow(_mainWnd);
You can create this effect by using a combination of DwmExtendFrameIntoClientArea() and setting 0 as the message result of WM_NCCALCSIZE if wParam is TRUE. Detailed steps below.
Window style should be such that normally the whole frame would be shown (WS_CAPTION|WS_POPUP works well for me), but don't include any of WS_MINIMIZE, WS_MAXIMIZE, WS_SYSMENU.
Call DwmExtendFrameIntoClientArea() with MARGINS{0,0,0,1}. We don't really want a transparent frame, so setting the bottom margin only is enough.
Call SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED) to let the system 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, but excluding the shadow. See remarks section of the documentation.
In WM_PAINT draw your frame and content area as you like but make sure to use an opaque alpha channel (value of 255) for the margin area 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. BitBlt() with a 32bpp source bitmap containing an opaque alpha channel also works.
You may handle WM_NCHITTEST if you want a resizable window.
The effect of all this is, that you paint "over" the regular window frame which is now inside the client area due to the DWM calls, but keep the regular window shadow. Don't worry the "paint over" doesn't create any flickering, even if you make the window resizable.
You can put any standard or user-defined controls into this window. Just make sure child controls don't overlap the margin defined by the DwmExtendFrameIntoClientArea() call because most GDI-based controls ignore the alpha channel.
Here is a minimal, self-contained example application:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dwmapi.h>
#include <unknwn.h>
#include <gdiplus.h>
#pragma comment( lib, "dwmapi" )
#pragma comment( lib, "gdiplus" )
namespace gdip = Gdiplus;
INT_PTR CALLBACK MyDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam );
int APIENTRY wWinMain( _In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow )
{
// Initialize GDI+
gdip::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdipToken = 0;
gdip::GdiplusStartup( &gdipToken, &gdiplusStartupInput, nullptr );
struct MyDialog : DLGTEMPLATE {
WORD dummy[ 3 ] = { 0 }; // unused menu, class and title
}
dlg;
dlg.style = WS_POPUP | WS_CAPTION | DS_CENTER;
dlg.dwExtendedStyle = 0;
dlg.cdit = 0; // no controls in template
dlg.x = 0;
dlg.y = 0;
dlg.cx = 300; // width in dialog units
dlg.cy = 200; // height in dialog units
DialogBoxIndirectW( hInstance, &dlg, nullptr, MyDialogProc );
gdip::GdiplusShutdown( gdipToken );
return 0;
}
INT_PTR CALLBACK MyDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_INITDIALOG:
{
SetWindowTextW( hDlg, L"Borderless Window with Shadow" );
// This plays together with WM_NCALCSIZE.
MARGINS m{ 0, 0, 0, 1 };
DwmExtendFrameIntoClientArea( hDlg, &m );
// Force the system to recalculate NC area (making it send WM_NCCALCSIZE).
SetWindowPos( hDlg, nullptr, 0, 0, 0, 0,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED );
return TRUE;
}
case WM_NCCALCSIZE:
{
// Setting 0 as the message result when wParam is TRUE removes the
// standard frame, but keeps the window shadow.
if( wParam == TRUE )
{
SetWindowLong( hDlg, DWLP_MSGRESULT, 0 );
return TRUE;
}
return FALSE;
}
case WM_PAINT:
{
PAINTSTRUCT ps{ 0 };
HDC hdc = BeginPaint( hDlg, &ps );
// Draw with GDI+ to make sure the alpha channel is opaque.
gdip::Graphics gfx{ hdc };
gdip::SolidBrush brush{ gdip::Color{ 255, 255, 255 } };
gfx.FillRectangle( &brush,
static_cast<INT>( ps.rcPaint.left ), static_cast<INT>( ps.rcPaint.top ),
static_cast<INT>( ps.rcPaint.right - ps.rcPaint.left ), static_cast<INT>( ps.rcPaint.bottom - ps.rcPaint.top ) );
EndPaint( hDlg, &ps );
return TRUE;
}
case WM_NCHITTEST:
{
// Setting HTCAPTION as the message result allows the user to move
// the window around by clicking anywhere within the window.
// Depending on the mouse coordinates passed in LPARAM, you may
// set other values to enable resizing.
SetWindowLong( hDlg, DWLP_MSGRESULT, HTCAPTION );
return TRUE;
}
case WM_COMMAND:
{
WORD id = LOWORD( wParam );
if( id == IDOK || id == IDCANCEL )
{
EndDialog( hDlg, id );
return TRUE;
}
return FALSE;
}
}
return FALSE; // return FALSE to let DefDialogProc handle the message
}
I am trying to preserve content aspect ratio in fullscreen mode in Windows. I'd like to hide the rest of the desktop behind black borders if the display aspect ratio differs from the content aspect ratio. Is it possible to create fullscreen window with centered content and black borders with Win32 api?
In OS X this can be achieved quite easily with the following code:
CGSize ar;
ar.width = 800;
ar.height = 600;
[self.window setContentAspectRatio:ar];
[self.window center];
[self.window toggleFullScreen:nil];
If I run the above code in 16:9 display, my app goes to fullscreen mode, the content is centered (since it is 4:3) and I have black borders on both sides of the screen.
I have tried to implement the same functionality in Windows but I begin to wonder if it is even possible. My current fullscreen code maintains the aspect ratio and
centers the content, but shows the desktop on the both sides of the window if the fullscreenWidth and fullscreenHeight are not equal to displayWidth and displayHeight:
bool enterFullscreen(int fullscreenWidth, int fullscreenHeight)
{
DEVMODE fullscreenSettings;
bool isChangeSuccessful;
int displayWidth = GetDeviceCaps(m_hDC, HORZRES);
int displayHeight = GetDeviceCaps(m_hDC, VERTRES);
int colourBits = GetDeviceCaps(m_hDC, BITSPIXEL);
int refreshRate = GetDeviceCaps(m_hDC, VREFRESH);
EnumDisplaySettings(NULL, 0, &fullscreenSettings);
fullscreenSettings.dmPelsWidth = fullscreenWidth;
fullscreenSettings.dmPelsHeight = fullscreenHeight;
fullscreenSettings.dmBitsPerPel = colourBits;
fullscreenSettings.dmDisplayFrequency = refreshRate;
fullscreenSettings.dmFields = DM_PELSWIDTH |
DM_PELSHEIGHT |
DM_BITSPERPEL |
DM_DISPLAYFREQUENCY;
SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_TOPMOST);
SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, displayWidth, displayHeight, SWP_SHOWWINDOW);
isChangeSuccessful = ChangeDisplaySettings(&fullscreenSettings, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;
ShowWindow(m_hWnd, SW_MAXIMIZE);
RECT rcWindow;
GetWindowRect(m_hWnd, &rcWindow);
// calculate content position
POINT ptDiff;
ptDiff.x = ((rcWindow.right - rcWindow.left) - fullscreenWidth) / 2;
ptDiff.y = ((rcWindow.bottom - rcWindow.top) - fullscreenHeight) / 2;
AdjustWindowRectEx(&rcWindow, GetWindowLong(m_hWnd, GWL_STYLE), FALSE, GetWindowLong(m_hWnd, GWL_EXSTYLE));
SetWindowPos(m_hWnd, 0, ptDiff.x, ptDiff.y, displayWidth, displayHeight, NULL);
return isChangeSuccessful;
}
The easiest way to accomplish what you are looking for is to create a child window (C) to render your content, leaving any excess space to the parent (P).
P should be created using a black brush for its background. Specify (HBRUSH)GetStockObject(BLACK_BRUSH) for the hbrBackground member of the WNDCLASS structure when registering the window class (RegisterClass). To prevent flicker while erasing the background, P should have the WS_CLIPCHILDREN Window Style.
Whenever P changes its size, a WM_SIZE message is sent to P's window procedure. The handler can then adjust C's position and size to maintain the aspect ratio.
To create a borderless child window C, use the WS_CHILD | WS_VISIBLE window styles in the call to CreateWindow. If you want to handle mouse input in the parent P instead, add the WS_DISABLED window style.
Sample code (error checking elided for brevity):
#define STRICT 1
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Globals
HWND g_hWndContent = NULL;
// Forward declarations
LRESULT CALLBACK WndProcMain( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK WndProcContent( HWND, UINT, WPARAM, LPARAM );
int APIENTRY wWinMain( HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/,
LPWSTR /*lpCmdLine*/,
int nCmdShow ) {
Both main and content window classes need to be registered. The registration is almost identical, with the exception of the background brush. The content window uses a white brush so that it's visible without any additional code:
// Register main window class
const wchar_t classNameMain[] = L"MainWindow";
WNDCLASSEXW wcexMain = { sizeof( wcexMain ) };
wcexMain.style = CS_HREDRAW | CS_VREDRAW;
wcexMain.lpfnWndProc = WndProcMain;
wcexMain.hCursor = ::LoadCursorW( NULL, IDC_ARROW );
wcexMain.hbrBackground = (HBRUSH)::GetStockObject( BLACK_BRUSH );
wcexMain.lpszClassName = classNameMain;
::RegisterClassExW( &wcexMain );
// Register content window class
const wchar_t classNameContent[] = L"ContentWindow";
WNDCLASSEXW wcexContent = { sizeof( wcexContent ) };
wcexContent.style = CS_HREDRAW | CS_VREDRAW;
wcexContent.lpfnWndProc = WndProcContent;
wcexContent.hCursor = ::LoadCursorW( NULL, IDC_ARROW );
wcexContent.hbrBackground = (HBRUSH)::GetStockObject( WHITE_BRUSH );
wcexContent.lpszClassName = classNameContent;
::RegisterClassExW( &wcexContent );
With the window classes registered we can move on and create an instance of each. Note that the content window is initially zero-sized. The actual size is calculated in the parent's WM_SIZE handler further down.
// Create main window
HWND hWndMain = ::CreateWindowW( classNameMain,
L"Constant AR",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 800,
NULL,
NULL,
hInstance,
NULL );
// Create content window
g_hWndContent = ::CreateWindowW( classNameContent,
NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hWndMain,
NULL,
hInstance,
NULL );
The remainder is boilerplate Windows application code:
// Show application
::ShowWindow( hWndMain, nCmdShow );
::UpdateWindow( hWndMain );
// Main message loop
MSG msg = { 0 };
while ( ::GetMessageW( &msg, NULL, 0, 0 ) > 0 )
{
::TranslateMessage( &msg );
::DispatchMessageW( &msg );
}
return (int)msg.wParam;
}
The behavior for a window class is implemented inside it's Window Procedure:
LRESULT CALLBACK WndProcMain( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch ( message ) {
case WM_CLOSE:
::DestroyWindow( hWnd );
return 0;
case WM_DESTROY:
::PostQuitMessage( 0 );
return 0;
default:
break;
In addition to standard message handling, the main window's window procedure resizes the content to fit whenever the main window's size changes:
case WM_SIZE: {
const SIZE ar = { 800, 600 };
// Query new client area size
int clientWidth = LOWORD( lParam );
int clientHeight = HIWORD( lParam );
// Calculate new content size
int contentWidth = ::MulDiv( clientHeight, ar.cx, ar.cy );
int contentHeight = ::MulDiv( clientWidth, ar.cy, ar.cx );
// Adjust dimensions to fit inside client area
if ( contentWidth > clientWidth ) {
contentWidth = clientWidth;
contentHeight = ::MulDiv( contentWidth, ar.cy, ar.cx );
} else {
contentHeight = clientHeight;
contentWidth = ::MulDiv( contentHeight, ar.cx, ar.cy );
}
// Calculate offsets to center content
int offsetX = ( clientWidth - contentWidth ) / 2;
int offsetY = ( clientHeight - contentHeight ) / 2;
// Adjust content window position
::SetWindowPos( g_hWndContent,
NULL,
offsetX, offsetY,
contentWidth, contentHeight,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER );
return 0;
}
}
return ::DefWindowProcW( hWnd, message, wParam, lParam );
}
The content window's window procedure doesn't implement any custom behavior, and simply forwards all messages to the default implementation:
LRESULT CALLBACK WndProcContent( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
return ::DefWindowProcW( hWnd, message, wParam, lParam );
}