How can I remove a window's non-client area completely? - c++

I need a window featuring no title bar, none of the control boxes, no system menu, and no frames (all that functionality is provided with separate controls).
I suspect that this should be possible to do with CreateWindowExA's window styles argument dwStyle and possibly lpWindowName, as described here: https://learn.microsoft.com/en-us/windows/desktop/winmsg/window-styles
This is how the arguments look like originally:
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class.
L"", // No window name (title text).
WS_OVERLAPPEDWINDOW, // Window style.
// Size and position.
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window.
NULL, // Menu.
hInstance, // Instance handle.
NULL // Additional application data.
);
However, in dwStyle, the normal window style WS_OVERLAPPEDWINDOW is defined as
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
with WS_OVERLAPPED being 0x00000000L.
Simply providing 0 and omitting the rest doesn't work, as also the documentation implies: "The window is an overlapped window. An overlapped window has a title bar and a border."
(The funny thing is, I am perfectly able to do this task in VB.NET (and even in VB6) by setting the ControlBox property to False and then by removing the titlebar using Text = "", so I strongly suspect that when possible in VB...)
How would I do my task in C++?
Just in case the WindowProc is needed in order to process a different message, here it is in its minimalistic version:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
(Compiling with VS 2017.)

The non-client area of a top-level window can be removed by using only the WS_POPUP style:
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class.
L"", // No window name (title text).
WS_POPUP, // Window style.
// Size and position.
100, 100, 400, 300,
NULL, // Parent window.
NULL, // Menu.
hInstance, // Instance handle.
NULL // Additional application data.
);
Note that CW_USEDEFAULT for size and position is only valid for overlapped windows. For popup windows you have to be explicit.
Depending on your use case, the technique described by this answer might be better suitable. Using the DWM API, it allows you to remove the non-client area, but keep the drop shadow to make the window stand out better from the background.

Related

Why isn't SetWindowRgn working as it should? [duplicate]

I have some problems with making static controls rounded. I can't understand why SetWindowRgn doesn't work for a static control here. Also I've tried SelectClipRgn and it works. But nevertheless why SetWindowRgn doesn't? The Microsoft documentation states that
The system does not display any portion of a window that lies outside
of the window region.
The control must be rounded and clipped according to the documentation. But it is not. Here's my example of the problem:
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
HRGN hrgnMain = CreateRoundRectRgn(0, 0, 200, 200, 5, 5);
int res = SetWindowRgn(hwnd, hrgnMain, TRUE);
ShowWindow(hwnd, nCmdShow);
HWND hControl = CreateWindow(L"Static", L"hello, world", WS_VISIBLE | WS_CHILD | SS_NOTIFY | SS_LEFTNOWORDWRAP,
20, 20, 40, 40, hwnd, NULL, hInstance, NULL);
HRGN hrgnControl = CreateRoundRectRgn(0, 0, 10, 10, 5, 5);
res = SetWindowRgn(hControl, hrgnControl, TRUE);
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Some builtin controls use CS_PARENTDC class style.
CS_PARENTDC sets the clipping region of the child window to that of the parent window. This conflicts with SetWindowRgn which wants to install custom region. Depending how your window is refreshed you could get different combinations of custom region working or not. For example if you resize parent window you can get your control refreshed partially with region set and partially without region.
Parent Display Device Contexts states that:
The system ignores the CS_PARENTDC style if the parent window uses a
private or class device context, if the parent window clips its child
windows, or if the child window clips its child windows or sibling
windows.
But it looks that setting only WS_CLIPCHILDREN for parent windows is not enough. Adding WS_CLIPSIBLINGS or WS_CLIPCHILDREN flags in control styles (even if you have only one child) triggers desired behavior.
HWND hControl = CreateWindow(
L"Static",
L"hello, world",
WS_CLIPSIBLINGS | WS_VISIBLE | WS_CHILD | SS_NOTIFY | SS_LEFTNOWORDWRAP,
20, 20, 40, 40,
hwnd,
NULL,
hInstance,
NULL);
Your code without WS_CLIPSIBLINGS
And with WS_CLIPSIBLINGS
Alternatively the CS_PARENT style could be removed by using GetClassLongPtr and SetClassLongPtr. Because CS_PARENT is used only to reuse clipping region it shouldn't have any other unexpected effects.
SetClassLongPtr(
hControl,
GCL_STYLE,
GetClassLongPtr( hControl, GCL_STYLE ) & ~CS_PARENTDC );

Drawing a picture using GDI+ in WinAPI [duplicate]

I need a window featuring no title bar, none of the control boxes, no system menu, and no frames (all that functionality is provided with separate controls).
I suspect that this should be possible to do with CreateWindowExA's window styles argument dwStyle and possibly lpWindowName, as described here: https://learn.microsoft.com/en-us/windows/desktop/winmsg/window-styles
This is how the arguments look like originally:
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class.
L"", // No window name (title text).
WS_OVERLAPPEDWINDOW, // Window style.
// Size and position.
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window.
NULL, // Menu.
hInstance, // Instance handle.
NULL // Additional application data.
);
However, in dwStyle, the normal window style WS_OVERLAPPEDWINDOW is defined as
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
with WS_OVERLAPPED being 0x00000000L.
Simply providing 0 and omitting the rest doesn't work, as also the documentation implies: "The window is an overlapped window. An overlapped window has a title bar and a border."
(The funny thing is, I am perfectly able to do this task in VB.NET (and even in VB6) by setting the ControlBox property to False and then by removing the titlebar using Text = "", so I strongly suspect that when possible in VB...)
How would I do my task in C++?
Just in case the WindowProc is needed in order to process a different message, here it is in its minimalistic version:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
(Compiling with VS 2017.)
The non-client area of a top-level window can be removed by using only the WS_POPUP style:
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class.
L"", // No window name (title text).
WS_POPUP, // Window style.
// Size and position.
100, 100, 400, 300,
NULL, // Parent window.
NULL, // Menu.
hInstance, // Instance handle.
NULL // Additional application data.
);
Note that CW_USEDEFAULT for size and position is only valid for overlapped windows. For popup windows you have to be explicit.
Depending on your use case, the technique described by this answer might be better suitable. Using the DWM API, it allows you to remove the non-client area, but keep the drop shadow to make the window stand out better from the background.

Why does SetWindowRgn not work for static controls?

I have some problems with making static controls rounded. I can't understand why SetWindowRgn doesn't work for a static control here. Also I've tried SelectClipRgn and it works. But nevertheless why SetWindowRgn doesn't? The Microsoft documentation states that
The system does not display any portion of a window that lies outside
of the window region.
The control must be rounded and clipped according to the documentation. But it is not. Here's my example of the problem:
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
HRGN hrgnMain = CreateRoundRectRgn(0, 0, 200, 200, 5, 5);
int res = SetWindowRgn(hwnd, hrgnMain, TRUE);
ShowWindow(hwnd, nCmdShow);
HWND hControl = CreateWindow(L"Static", L"hello, world", WS_VISIBLE | WS_CHILD | SS_NOTIFY | SS_LEFTNOWORDWRAP,
20, 20, 40, 40, hwnd, NULL, hInstance, NULL);
HRGN hrgnControl = CreateRoundRectRgn(0, 0, 10, 10, 5, 5);
res = SetWindowRgn(hControl, hrgnControl, TRUE);
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Some builtin controls use CS_PARENTDC class style.
CS_PARENTDC sets the clipping region of the child window to that of the parent window. This conflicts with SetWindowRgn which wants to install custom region. Depending how your window is refreshed you could get different combinations of custom region working or not. For example if you resize parent window you can get your control refreshed partially with region set and partially without region.
Parent Display Device Contexts states that:
The system ignores the CS_PARENTDC style if the parent window uses a
private or class device context, if the parent window clips its child
windows, or if the child window clips its child windows or sibling
windows.
But it looks that setting only WS_CLIPCHILDREN for parent windows is not enough. Adding WS_CLIPSIBLINGS or WS_CLIPCHILDREN flags in control styles (even if you have only one child) triggers desired behavior.
HWND hControl = CreateWindow(
L"Static",
L"hello, world",
WS_CLIPSIBLINGS | WS_VISIBLE | WS_CHILD | SS_NOTIFY | SS_LEFTNOWORDWRAP,
20, 20, 40, 40,
hwnd,
NULL,
hInstance,
NULL);
Your code without WS_CLIPSIBLINGS
And with WS_CLIPSIBLINGS
Alternatively the CS_PARENT style could be removed by using GetClassLongPtr and SetClassLongPtr. Because CS_PARENT is used only to reuse clipping region it shouldn't have any other unexpected effects.
SetClassLongPtr(
hControl,
GCL_STYLE,
GetClassLongPtr( hControl, GCL_STYLE ) & ~CS_PARENTDC );

Setting text color of child window

I'm trying to change text/background color for the static control. I can do this just fine the following way:
// This is the 'main' window
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance;
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW &~WS_MAXIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
CW_USEDEFAULT, 0, 1035, 764, nullptr, nullptr, hInstance, nullptr);
...
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
case WM_CTLCOLORSTATIC:
{
MessageBox( NULL, "CTLCOLORSTATIC called", "", MB_OK );
HDC hdcStatic = (HDC)wParam;
SetTextColor(hdcStatic, RGB(200, 200, 20));
SetBkColor(hdcStatic, RGB(10, 10, 10));
return (INT_PTR)CreateSolidBrush(RGB(30, 30, 30));
}
default:
return DefWindowProc( hWnd, message, wParam, lParam );
}
But if I place the window inside another window, the child control text/background color stays default:
// This is the 'parent' window, which resides in the 'main' window
HWND parent = CreateWindowEx
(
0,
_TEXT("STATIC"),
"",
WS_TABSTOP | WS_VISIBLE | BS_SOLID | WS_CLIPCHILDREN,
10, 10, 500, 500,
hwnd,
NULL,
(HINSTANCE)GetWindowLong(hwnd, GWLP_HINSTANCE),
NULL
);
// This is the 'child' window which resides in the 'parent' window
HWND child = CreateWindowEx
(
0,
_TEXT("STATIC"),
"SubItem",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_SOLID,
10, 10, 100, 100,
parent,
NULL,
(HINSTANCE)GetWindowLong(parent, GWLP_HINSTANCE),
NULL
);
To conclude, I have 3 windows:
HWND hwnd; // the 'main' application window (color changes fine)
HWND parent; // the 'parent/container' window which is inside the 'main' window (color changes fine)
HWND child; // the 'child' window which is inside the 'parent' window (color DOES NOT change)
Even though if I put MessageBox inside the WM_CTLCOLORSTATIC, I see it triggering every time the children is drawn, yet the color is not being changed for the child, only for the parent.
As far as I understood I need to handle the message in the main window procedure, but I'm not entirelly clear how to do this. if I compare the (HWND)lParam to the childrens HWND, they're the same (within the default switch case), so I can get the reference in the 'default' section, but I'm not sure how I should handle it from there..
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
case WM_CTLCOLORSTATIC:
{
if ((HWND)lParam == child )
MessageBox( NULL, "Reference Match for CTLCOLORSTATIC", "", MB_OK ); // <-- THIS NEVER TRIGGERS
}
....
default:
if ((HWND)lParam == child )
MessageBox( NULL, "Reference Match for DEFAULT", "", MB_OK ); // <-- THIS DOES TRIGGER
return DefWindowProc( hWnd, message, wParam, lParam );
}
return 0;
}
The reason I'm placing one window inside another is so that I can use WS_CLIPCHILDREN property in the parent (becase the child will move/scroll).
WM_CTLCOLORSTATIC is sent by a static control to its parent window, not the top level window.
hwndMain: WM_CTLCOLORSTATIC for hwnd1
|
\--hwnd1: WM_CTLCOLORSTATIC for hwnd2 (You might have to subclass hwnd1)
|
\--hwnd2
You are also leaking brushes, store the brush from CreateSolidBrush somewhere when you create the window and delete it when the window is destroyed.
By reading the MSDN document, I know a function, SetWindowsLongPtr, whose parameter GWLP_WNDPROC can set a new address for the window procedure.
This function can change the text color and background color of the child window, that is to say, it can trigger WM_CTLCOLORSTATIC.
But after testing, I found that it is invalid to the secondary window, that is to say, it can not change the text color of the parent window.
I also consulted a lot of information, and very few documents related to three-tier windows.
So, I think to solve this problem and make all three windows change the color of the text, you may have to rewrite WndProc by yourself, but this is very complicated and involves a lot of things.
Edit: If you just need to change the text color and background color
of the static control, you can customize a control so that you can
handle all its operations.
Hope to help you.

Win32 C++ Resizing Window Leaves Artifacts in RDP

I have a simple Win32 program that does the following
1) Creates a Window
HWND hWnd = CreateWindowEx(
WS_EX_LEFT |
WS_EX_LTRREADING |
WS_EX_RIGHTSCROLLBAR |
WS_EX_WINDOWEDGE |
WS_EX_APPWINDOW,
wc.lpszClassName,
L"App Name",
WS_CAPTION |
WS_VISIBLE |
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN |
WS_SYSMENU |
WS_OVERLAPPED |
WS_MINIMIZEBOX, 0, 0, 540, 180, 0, 0, hInstance, NULL);
Then
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
HDC hdc;
hdc = BeginPaint(hWnd, &ps);
// code here (not shown) calculates pixel size of text with GetTextExtentPoint32
// resizes window to fit text with MoveWindow
MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, height + borderSize, false);
// writes text to window with DrawText
EndPaint(hWnd, &ps);
}
break;
case WM_CLOSE:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
This works fine on desktop without an issue, Window looks fine and displays as expected.
However on RDP the following artifacts are present:
The pre-resized versions of Window remains i.e. you can see both the old border and the new border
An image from the background of where Window was created is blended into the Window
Moving the Window around the screen does not remove the artifacts
Minimizing the Window and opening it again does remove the artifacts
If I minimize the Remote Desktop client, and switch back to it, the Window is displayed as expected.
Other programs don't show this behavior during the RDP session.
RDP client is on Win10 x64, target machines are Server 2008 R2 or Windows 7 SP1 x64.
Tried with repaint set to TRUE in MoveWindow but this had no affect.
Fixed this by removing WS_VISIBLE from my CreateWindowEx command, resized the Window first, then after resizing Window called
ShowWindow(hWnd,SW_SHOW);
I also removed the window resize from WM_PAINT, however this did not fix the issue.