Reputation: 1281
I'm working on an Qt 6-based application where I'd like to be able to extend the window content into the titlebar area, but keep the window buttons, something like this on Windows 10/11:
I have a solution implemented on macOS, but I'm struggling on Windows, where I'm not very comfortable with the various native APIs.
I've attempted to use DwmExtendFrameIntoClientArea in a simple dummy app, which I believe should do what I want, according to this Microsoft guide, but it's not working for me. I've implemented the code in that guide in a QAbstractNativeEventFilter
subclass which I've installed to my application, and the events are being received but the window still has a normal titlebar with the menubar below:
It also generates some warnings when I run it:
exiting ctor
WM_CREATE, hwnd: 0xca04cc
QWindowsContext::windowsProc: No Qt Window found for event 0x1c (WM_ACTIVATEAPP), hwnd=0x0xca04cc.
WM_ACTIVATE
succeeded, will return true.
QWindowsContext::windowsProc: No Qt Window found for event 0x7 (WM_SETFOCUS), hwnd=0x0xca04cc.
Subsequent WM_ACTIVATE
are received and don't generate any warnings, so I assume these first few messages are just received before the Qt window is allocated, but I suspect that these are relevant to the fact that my titlebar isn't going away.
main.cpp:
#include <QApplication>
#include "mainWindow.h"
#include "winEventFilter.h"
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
WinEventFilter eventFilter;
app.installNativeEventFilter( &eventFilter );
MainWindow mainWin;
mainWin.show();
return app.exec();
}
winEventFilter.h:
#pragma once
#include <QAbstractNativeEventFilter>
class WinEventFilter : public QAbstractNativeEventFilter
{
public:
bool nativeEventFilter( const QByteArray& eventType, void* message, qintptr* result ) override;
};
winEventFilter.cpp:
#include <dwmapi.h>
#include <QDebug>
#include "winEventFilter.h"
#define LEFTEXTENDWIDTH 8
#define RIGHTEXTENDWIDTH 8
#define BOTTOMEXTENDWIDTH 20
#define TOPEXTENDWIDTH 27
#define RECTWIDTH( R ) ( R.right - R.left )
#define RECTHEIGHT( R ) R.bottom - R.top
bool WinEventFilter::nativeEventFilter( const QByteArray& eventType, void* message, qintptr* result )
{
auto msg = reinterpret_cast<MSG*>( message );
if( msg == nullptr )
{ return false; }
BOOL fDwmEnabled = FALSE;
HRESULT hr = S_OK;
hr = DwmIsCompositionEnabled(&fDwmEnabled);
if( !SUCCEEDED( hr ) )
{
qDebug() << "composition is not enabled.";
return false;
}
auto hWnd = msg->hwnd;
switch( msg->message )
{
case WM_CREATE:
qDebug() << "WM_CREATE, hwnd:" << msg->hwnd;
RECT rcClient;
GetWindowRect( hWnd, &rcClient );
// Inform application of the frame change.
SetWindowPos( hWnd,
NULL,
rcClient.left, rcClient.top,
RECTWIDTH( rcClient ), RECTHEIGHT( rcClient ),
SWP_FRAMECHANGED );
if( result != nullptr )
{ *result = 0; }
return true;
case WM_ACTIVATE:
qDebug() << "WM_ACTIVATE";
MARGINS margins;
margins.cxLeftWidth = LEFTEXTENDWIDTH; // 8
margins.cxRightWidth = RIGHTEXTENDWIDTH; // 8
margins.cyBottomHeight = BOTTOMEXTENDWIDTH; // 20
margins.cyTopHeight = TOPEXTENDWIDTH; // 27
// only if content window is hWnd?
hr = DwmExtendFrameIntoClientArea( hWnd, &margins );
if( !SUCCEEDED( hr ) )
{
qDebug() << "extending frame into client area failed";
return false;
}
qDebug() << "succeeded, will return true.";
if( result != nullptr )
{ *result = 0; }
return true;
default:
return false;
}
}
mainWindow.h:
#pragma once
#include <QMainWindow>
class MainWindow : public QMainWindow
{
public:
MainWindow();
private:
void createMenus();
void createCentralWidget();
};
mainWindow.cpp:
#include <memory>
#include <QLabel>
#include <QMenuBar>
#include <QVBoxLayout>
#include "mainWindow.h"
MainWindow::MainWindow()
{
createMenus();
createCentralWidget();
qDebug() << "exiting ctor";
}
void MainWindow::createMenus( )
{
auto fileMenu = menuBar()->addMenu( tr( "&File" ) );
fileMenu->addAction( tr( "Placeholder" ) );
}
void MainWindow::createCentralWidget()
{
auto widget = std::make_unique<QWidget>();
auto pal = palette();
pal.setColor( QPalette::Window, Qt::darkRed );
pal.setColor( QPalette::WindowText, Qt::white );
widget->setPalette( pal );
widget->setAutoFillBackground( true );
auto layout = new QVBoxLayout( widget.get() );
layout->addWidget( new QLabel( "Hello" ) );
setCentralWidget( widget.release() );
}
Native event filters aren't very well-documented, and I'm not sure I'm doing this right at all, or even if this is a valid approach to move window content into the titlebar area. I've also come across more recent documentation with other solutions based on WinUI 2/WinUI 3/Windows App SDK, but I have no idea how to implement them in Qt.
Is it possible to get the effect I want with the approach I'm using? Failing that, is it possible to use one of the more recent WinUI APIs to do so? I don't really mind if it's only possible to get this working in Windows 11 or more recent versions of Windows 10. Ideally I'd like to do this without resorting to setting the Qt::FramelessWindowHint
flag, because I'd like to maintain native button behavior, including snap layouts on Windows 11.
Upvotes: 2
Views: 944