Remove the System Menu of a QDialog on Windows

From Qt Wiki
Jump to: navigation, search
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.

How To: Remove the System Menu from a QDialog on Windows

This How To illustrates a - admittedly quite hacky - way to achieve a really native appearance for title bars of dialogs on Windows systems.

What goes wrong?

When using Doc:QDialogs on current Windows systems like Windows 7, there is one little thing you just can't get right: It is near impossible to remove the system menu from the dialog title bar without removing the close button as well. In fact, trying to achieve a really native look for dialogs using the window flags provided by Qt is an exercise in futility.

Why does it go wrong?

The reason for this issue is a peculiarity in the Windows API: There is a dedicated window class for dialogs on windows. Window classes are - put simply - blueprints for window creation. The combination of window flags that enables the title bar without system menu and a close button does for some reason only work for windows created from the dialog window class. Unfortunately, Qt does not use the dialog window class for dialog creation, which is the reason for this shortcoming. See http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574(v=vs.85).aspx for more details on window classes.

How to fix it?

There is a back door in Qt that allows for a workaround: The create() method of Doc:QWidget can be used to replace the native window handle managed by Qt with a new handle created by the Windows API. This makes it possible to create a new native window with the correct window class and hand it over to the Doc:QWidget as a replacement for the existing window:

mydialog.h

#ifndef MYDIALOG_H
#define MYDIALOG_H

#include <QDialog>

class MyDialog : public QDialog
{

Q_OBJECT

private:
 void fixDialogStyle();

public:
 MyDialog(QWidget* parent = 0);

~MyDialog();

};

#endif // MYDIALOG_H

mydialog.cpp

#include "mydialog.h"

#ifdef Q_WS_WIN32

#define WINVER 0x0500

#include <windows.h>

#endif // Q_WS_WIN32

MyDialog::MyDialog(QWidget* parent) : QDialog(parent)
{
 setWindowTitle(tr("Dialog without System Menu"));

// Optional: Remove context help button from title bar.
 // This must happen BEFORE the call to fixDialogStyle(), otherwise
 // the system menu will be back immediately.
 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

fixDialogStyle();
}

MyDialog::~MyDialog()
{
}

void MyDialog::fixDialogStyle()
{
#ifdef Q_WS_WIN32

// Retrieve the native window handle
 HWND hwnd = winId();

// Copy the style of the existing widget
 DWORD exStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE);
 DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);

// Keep the correct parent
 HWND parent = NULL;
 if (parentWidget()) {
 parent = parentWidget()->winId();
 }

// Set relevant style flags
 exStyle |= WS_EX_DLGMODALFRAME | WS_EX_TOPMOST;

// Create a new window handle using the dialog window class (#32770)
 HWND newHwnd = ::CreateWindowEx(exStyle, L"#32770", NULL, style,
 CW_USEDEFAULT, CW_USEDEFAULT,
 CW_USEDEFAULT, CW_USEDEFAULT,
 parent, NULL, qWinAppInst(), NULL);

// Swap the native handles
 create(newHwnd, true, true);

#endif // Q_WS_WIN32
}

The code above depends on functions available in user32.dll so an additional import library is required. Add the following to your .pro file:

win32:LIBS += -luser32<code>

Issues and Shortcomings

  • After applying the trick above, Doc:QWidget::setWindowFlags() cannot be used without bringing back the system menu immediately. Extra flags must be set before applying the hack or by using the appropriate Windows API functions directly.
  • Using this workaround involves a certain overhead, since two native window handles are created for every new dialog instance.