I've made a program with a custom non-client area. Instead of handling WM_NCPAINT, i set the client area size to zero in WM_NCCALCSIZE and then simulate the non-client area by handling WM_NCHITTEST. All painting to the window is handled in WM_PAINT. WM_PAINT works fine when i start the program and whenever the program needs repainted. However, once i minimize the program with a call to ShowWindow(hwnd, SW_MINIMIZE), and then restore the program by clicking on it in the task bar, WM_PAINT no longer works. I've checked every function call in WM_PAINT and they all return success, but the window just shows up as black. Anybody have any ideas as to why?
Here's the code for my WM_PAINT handler:
http://pastebin.com/MXmWzb4M
Here's the output to the console window:
BeginPaint successful
hdcBuffer is valid
MainWindow->hbmBuffer is valid
hdcMem is valid
So, seeing as every function call in my WM_PAINT handler is returning success, why is my entire window showing up as black?
Turns out the problem was with handling the WM_NCCALCSIZE message to remove the non-client area. Here's the alternative that i used:
DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
dwStyle &= ~WS_OVERLAPPEDWINDOW;
SetWindowLong(hwnd, GWL_STYLE, dwStyle);
Related
I'm writing a program which needs a DPI aware, borderless fullscreen top-level OpenGL window.
The program has only this main window for 99% of the time.
The problem I'm having is the client area of the window is erased by windows when the window becomes inactive.
This only occurs when DPI aware, fullscreen and borderless(i.e. zero non-client area).
I have found a workaround to this, by catching WM_NCACTIVATE and returning 0 which prevents windows from processing the NC redraw. It seems that the windows non-client redraw procedure erases the client area if the non-client area is zero.
But, when I create a DialogBox (child of main hWnd), it causes the main window to become inactive (obviously).
If I return 0 from WM_NCACTIVATE, mouse and keyboard events do not get passed to the dialog box proc so the program becomes locked.
Using a global flag I can deactivate the workaround before creating the dialog, then redraw my main GL screen on WM_INITDIALOG but this causes a rather ugly screen flash that I'm trying to prevent.
So my question is: How do I prevent windows from erasing my client are of main wnd, without suppressing WM_NCACTIVATE?
Alternately: How to avoid the window erase, delay, then redraw flash that occurs when creating the dialog?
FYI,
The code I'm using to create the main window, run the message loop and draw a GL scene is essentially pinched from the HeNe examples.
I know these are very dated, but I find them easy to build from.
I've changed the HeNe code to WinMain() and perform my OpenGL drawing only as the program requires (outside of the main loop and outside of WM_PAINT).
Redrawing the scene and SwapBuffers() inside of WM_PAINT and WM_NCPAINT does not fix the problem.
To make the window fullscreen, I use Raymond Chen's code:
https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
I have also tried c-smile's code to make fullscreen, which behaves identically to Raymond Chen's:
Windows 10, DPI scaling and fullscreen
For the record, I'm using PerMonitor V2 but it dosent seem to matter which mode I use. Ive tried setting using both API and manifest file.
VS2019 v142; SDK 10.0.18362.0
I resolved this for a Qt application on Win10. My program is DPI aware and borderless, but is not fullscreen. Nevertheless, I had the same issue where:
I would get an unpleasant flicker when activating/deactivating the window
Unconditionally returning true for WM_NCACTIVATE WndProc calls would fix the flicker but would prevent dialogs capturing input
The flicker occurs without WM_NCPAINT having been called; there is no opportunity to prevent the redraw.
To resolve this, I had to change how I remove the non-client area. Previously I was using a Qt-specific function on window creation like so:
setWindowFlag(Qt::FramelessWindowHint);
I removed that and added a handler for WM_NCCALCSIZE calls which unconditionally returns 0. That removed the non-client area and, for whatever reason, doesn't cause the redraw on activation despite not having a handler for WM_NCACTIVATE.
In short, the only thing I do to remove the non-client rect is handle WM_NCCALCSIZE. I don't call into any WINAPI functions. I don't override WM_NCACTIVATE because doing so prevents dialogs from working correctly.
Here's my WndProc in full. This is for a Qt application, but nativeEvent is just a thin wrapper around the native WndProc.
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
MSG* msg = static_cast<MSG*>(message);
LRESULT hitTestResult = 0;
auto handled = DwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, result);
if (handled)
return true;
static bool resizing = false;
switch (msg->message)
{
case WM_NCHITTEST:
*result = HitTestNCA(msg->hwnd, msg->wParam, msg->lParam, this);
if (*result != HTNOWHERE)
return true;
return false;
case WM_NCCALCSIZE:
// Setting the result to zero removes the non-client rect. This is
// a hardcoded Microsoft thing.
*result = 0;
return true;
default:
return false;
}
// Unreachable
}
I tried to create button (child window) inside WM_NCCREATE message, and its position seemed to be created respected to screen coordinates, rather than client coordinates. At first, I thought WM_CREATE and WM_NCCREATE provide us the same handle to window, but this seem to be untrue. Therefore, can anyone explain me the differences between WM_CREATE and WM_NCCREATE messages? Also what are the differences between handle to window in WM_CREATE and in WM_NCCREATE?
WM_NCCREATE is an example of an arms race in progress. It seems to have been introduced to serve a need where DefWindowProc (or the base window proc of a commonly subclassed window) needed to perform some initialization perhaps before WM_CREATE was processed (or to make up for the fact that many window implementations handle WM_CREATE directly and return TRUE rather than passing it on to DefWindowProc).
WM_NCCREATE therefore is the message you should respond to if you are implementing a default window procedure, that needs to perform initialization before the users window proc handles the WM_CREATE message. WM_NCCREATE also MUST be passed on to the appropriate DefWindowProc, probably before you do your own processing as some lower level aspects of the window are clearly in an uninitialized state before WM_NCCREATE is processed.
If trying to guarantee first-look processing is NOT your consideration, then WM_CREATE is the appropriate place to perform your window initialization: All other layers that might have jist-in-time setup via WM_NCCREATE have been done, and the window is in a stable state wrt things like its non client metrics, screen position etc.
Or: If you don't know why you should use WM_NCCREATE over WM_CREATE, then you should not be using WM_NCCREATE.
The WM_NC messages are for the non-client area, i.e. the window border and caption. For your needs you are not interested in these non-client messages.
Per MSDN:
WM_NCCREATE:
Sent prior to the WM_CREATE message
when a window is first created.
Return Value:
If an application processes this
message, it should return TRUE to
continue creation of the window. If
the application returns FALSE, the
CreateWindow or CreateWindowEx
function will return a NULL handle.
WM_CREATE:
Sent when an application requests that
a window be created by calling the
CreateWindowEx or CreateWindow
function. (The message is sent before
the function returns.) The window
procedure of the new window receives
this message after the window is
created, but before the window becomes
visible.
Return Value:
If an application processes this
message, it should return zero to
continue creation of the window. If
the application returns –1, the window
is destroyed and the CreateWindowEx or
CreateWindow function returns a NULL
handle.
This device context includes the window title bar, menu, scroll bars, and frame in addition to the client
area. Applications programs rarely use the GetWindowDC function. If you want to experiment with it,
you should also trap the WM_NCPAINT ("nonclient paint") message, which is the message Windows
uses to draw on the nonclient areas of the window.
from:《Programming Windows Fifth Edition》 -Charles Petzold
So I think it's believable, although MSDN didn't say it。
Not sure why you're creating a button in the WM_NCCREATE -- because the window onto which the button will appear doesn't exist yet, hence (I believe) the destop coords. WM_NCCREATE gets sent to you when the 'non-client' areas of the window are about to be created (non-client areas such as the window's border, title bar, etc.)
Are you needing to put a button on the non-client area? If the answer is no, then why not do the button create inside
WM_CREATE.
If you have to create the button for some reason inside WM_NCCREATE, then why not store the window handle returned by your Createwindow() call. Then, inside your WM_CREATE message handler, grab that button's window handle and do a 'MoveWindow(...)' on it using the app window which you should now have coordinates to when you're in the WM_CREATE message handler.
I believe one of the parameters you can pass to your CreateWindow(...) call to create the button allows you to specify an 'SW_...'
flag, such as 'SW_HIDE' if memory serves me correct. So create but don't show the button in WM_NCCREATE handling if you must, then when WM_CREATE comes quickly afterward, do a 'MoveWindow(....window coords,......SW_SHOW,......) etc.
to position and make visible the button.
Attempting to simulate client area based window dragging by returning HTCAPTION under a WM_NCHITTEST (excluding HTCLIENT & appropriate areas) works flawlessly when used with a parent window - however presence of child windows such as tabs placeholder windows, even when set to the extended style WS_EX_TRANSPARENT, cause clicks to fail to pass WM_NCHITTEST messages to the parent window (and attempting to process local WM_NCHITEST messages in a similar fashion produces the expected effect of dragging the child window around the parent rather than the parent itself).
Given that every area in that tab child window appears to be considered to be client area, processing WM_LBUTTONDOWN instead appears to produce the desired effect (see below):
case WM_LBUTTONDOWN: {
SendMessage(mainWnd.hWnd, WM_NCLBUTTONDOWN, HTCAPTION, lParam);
break;
}
Where mainWnd.hWnd is the parent window handle (hWnd is a member of a designed window properties helper class)
Although this produces the desired effect, I'm confused at whether WS_EX_TRANSPARENT is actually meant to allow clicks to pass through to underlying windows, and whether there is a more appropriate solution?
Have you tried returning HTTRANSPARENT from WM_NCHITTEST for the tab control? I think that should propagate the message to the parent window.
WS_EX_TRANSPARENT has to do with how the window is painted afaik.
I am not using a dialog, I'm using my own custom class which I have registered and then used the CreateWindow call to create it, I have preset the background color to red when registering:
WNDCLASSEX wc;
wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
But now I want to change the background color at runtime, by e.g. clicking a button to change it to blue.
I have tried to use SetBkColor() call in the WM_PAINT, and tried returning a brush from the WM_CTLCOLORDLG message, they don't work.
Any help?
From Window Background comes:
...The system paints the background for a
window or gives the window the
opportunity to do so by sending it a
WM_ERASEBKGND message when the
application calls BeginPaint. If an
application does not process the
message but passes it to
DefWindowProc, the system erases the
background by filling it with the
pattern in the background brush
specified by the window's class.....
...... An application can process the
WM_ERASEBKGND message even though a
class background brush is defined.
This is typical in applications that
enable the user to change the window
background color or pattern for a
specified window without affecting
other windows in the class. In such
cases, the application must not pass
the message to DefWindowProc. .....
So, use the WM_ERASEBKGND message's wParam to get the DC and paint the background.
You may try the following:
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 255));
SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)brush);
Short answer: Handle WM_ERASEBKGND.
Longer answer:
When you register the WNDCLASS, you're providing information about all windows of that class. So if you want to change the color of just one instance of the window, you'll need to handle it yourself.
When it's time to repaint your window, the system will send your wndproc a WM_ERASEBKGND message. If you don't handle it, the DefWindowProc will erase the client area with the color from the window class. But you can handle the message directly, painting whatever color (or background pattern) you like.
I am trying to create a control that implements the per-pixel alpha blend while painting a 32-bit bitmap.
I extended a CWnd and use static control in the resource editor. I managed to paint the alpha channel correctly but still the static control keep painting the gray background.
I overwrote the OnEraseBkgnd to prevent the control from painting the background but it didn't worked. I finally managed to do it by using WS_EX_TRANSPARENT.
My problem now is that my control is placed over other control. The first time the dialog is painted all works fine...but if I click over the "parent" control (ie the one beneath my control) my control doesn't received the WM_PAINT message. So it is not painted anymore.
If I minimize the aplication and maximized it again the controls are painted again.
Please, can anybody give a hint? I am getting crazy with this control!!!
Thanks.
I would have though that if you are processing both the WM_ERASEBKGND and the WM_PAINT messages then you should have cover all the painting options, without having to resort to using the WS_EX_TRANSPARENT style.
Are you sure your code is not passing these messages on to the default processing?
One other option might be to subclass the static control, just to make sure your code is the only one handling these two messages.
BEGIN_MESSAGE_MAP(CTransparentStatic, CStatic)
ON_WM_ERASEBKGND()
ON_WM_CTLCOLOR_REFLECT()
END_MESSAGE_MAP()
BOOL CTransparentStatic::OnEraseBkgnd(CDC* /*pDC*/)
{
// Prevent from default background erasing.
return FALSE;
}
BOOL CTransparentStatic::PreCreateWindow(CREATESTRUCT& cs)
{
cs.dwExStyle |= WS_EX_TRANSPARENT;
return CStatic::PreCreateWindow(cs);
}
HBRUSH CTransparentStatic::CtlColor(CDC* pDC, UINT /*nCtlColor*/)
{
pDC->SetBkMode(TRANSPARENT);
return reinterpret_cast<HBRUSH>(GetStockObject(NULL_BRUSH));
}
void CTransparentStatic::PreSubclassWindow()
{
CStatic::PreSubclassWindow();
const LONG_PTR exStyle = GetWindowLongPtr(m_hWnd, GWL_EXSTYLE);
SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, exStyle | WS_EX_TRANSPARENT);
}