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 );
}
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'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 am able to create a window with a Tile. How can I add now new text line inside the Window ?
All what I succeed to did was only to change the title of the window which is not what I want . I want to add some text line in the window box.
SendMessage function was not working for me.
Please if somebody has some tip for this to tell me !
#include <windows.h>
const char g_szClassName[] = "myWindowClass";
//The Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
// Registering the Window Class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"Title of window",
WS_OVERLAPPEDWINDOW,
1390, 540, 240, 120,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// The Message Loop
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
To draw text in the client area, your wndProc would normally use something like DrawText or TextOut. You typically do that in response to WM_PAINT.
To be able to respond to an external message, you'd typically send a message containing the text. The window would receive that, store (a copy of) the text it received, and (usually) invalidate the window's rectangle. Since the window is now invalidated, the next chance it gets, Windows will send your window a WM_PAINT message (and then you'll draw out the text).
Handling the WM_PAINT message and drawing the text directly on the window's HDC is one option.
Another option is to create a child STATIC control in your window, and then you can assign the desired text to that child using SetWindowText() or the WM_SETTEXT message. No manual drawing needed.
In the end I figure out how to finish this:
win32 app picture
#ifndef UNICODE
#define UNICODE
#endif
using namespace std;
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
int X_Coordinate = 215;
int Y_Coordinate = 415;
int Width = 700;
int Height = 500;
char Text[] = {"abc123"};
char Window_Title[] = "My title";
char Window_Image[] = "D:\\bitmap1.bmp";
const char* csWindow_Title = Window_Title;
const char* csWindow_Image = Window_Image;
HBITMAP bitmap; // Creates bitmap object based on a handle to a Windows Windows Graphics Device Interface (GDI) bitmap and a handle to a GDI palette.
// Utilities
bool ConvertConstChartoLPWSTR (const char* as , wchar_t* wString )
{
memset(wString,0,sizeof(wString));
MultiByteToWideChar(CP_ACP, 0, as, -1, wString, 4096);
return wString;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
//Registering the Window Class
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255)); // set window background color ( RGB ) - white
RegisterClass(&wc); // register the window class with the operating system
wchar_t* wWindow_Title=new wchar_t[4096];
memset(wWindow_Title,0,sizeof(wWindow_Title)); // init variable
ConvertConstChartoLPWSTR(csWindow_Title,wWindow_Title); // convert
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
wWindow_Title, // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
X_Coordinate, Y_Coordinate, Width, Height,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// ----------------------------- START -> SWITCH case -----------------------------------------------------
switch (uMsg)
{
// ----------------------------- START -> case WM_DESTROY -------------------------------------------------
case WM_DESTROY:
PostQuitMessage(0);
return 0;
// ----------------------------- END -> case WM_DESTROY ---------------------------------------------------
// ----------------------------- START -> case WM_PAINT ---------------------------------------------------
case WM_PAINT:
{
wchar_t* wWindow_Image=new wchar_t[4096];
memset(wWindow_Image,0,sizeof(wWindow_Image));
ConvertConstChartoLPWSTR(csWindow_Image,wWindow_Image); // convert
bitmap=(HBITMAP)LoadImage(NULL, // A handle to the module that contains the image to be loaded. To load a stand-alone resource (icon, cursor, or bitmap file)—for example, c:\myimage.bmp — set this parameter to NULL
wWindow_Image, // The image to be loaded.
IMAGE_BITMAP, // The type of image to be loaded.
690, // The width, in pixels, of the icon or cursor.
540, // he height, in pixels, of the icon or cursor.
LR_LOADFROMFILE); //Loads the stand-alone image from the file specified by lpszName (icon, cursor, or bitmap file).
PAINTSTRUCT ps; // declare structure with information for an application
HDC hdc = BeginPaint(hwnd, &ps); // prepare the specified window for painting
int index = sizeof(Text);
HDC hMemDC=CreateCompatibleDC(hdc); // create a compatible DC ( hMemDC ) o be the same like another one ( hdc )
::SelectObject(hMemDC,bitmap); // Selects an object into the specified device context (DC). The new object replaces the previous object of the same type.
long retval=SetTextAlign(hdc,TA_TOP); // alignment of written area
const char* theval;
int u = 5;
for(int b = 0; b < sizeof(Text); b++)
{
string sym(1, Text[b]); // convert char to const char*
theval = sym.c_str();
cout<<b<<theval;
wchar_t wtext[sizeof(Text)];
memset(wtext,0,sizeof(wtext));
ConvertConstChartoLPWSTR(theval,wtext); // convert
// Here application is laid out.
TextOut (hdc, 5, u, wtext, sizeof(Text));
u = u + 15;
}
index = index + u; // claculate the size of written area
BitBlt( hdc, // handler
0, // The x-coordinate, in logical units, of the upper-left corner of the destination rectangle.
index, // The y-coordinate, in logical units, of the upper-left corner of the destination rectangle.
700, // The width, in logical units, of the source and destination rectangles.
980, // The height, in logical units, of the source and the destination rectangles.
hMemDC, // handler for source ( image ).
0, // The x-coordinate, in logical units, of the upper-left corner of the source rectangle.
0, // The y-coordinate, in logical units, of the upper-left corner of the source rectangle.
SRCCOPY ); // A raster-operation code. These codes define how the color data for the source rectangle is to be combined with the color data for the destination rectangle to achieve the final color.
// SRCCOPY - Copies the source rectangle directly to the destination rectangle.
EndPaint(hwnd, &ps); // function marks the end of painting in the specified window
}
return 0;
// ----------------------------- END -> case WM_PAINT ---------------------------------------------------
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
// ----------------------------- END -> SWITCH case -----------------------------------------------------
} // END -> LRESULT CALLBACK WindowProc
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
}
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?