简体   繁体   中英

How to integrate the Qt frameless window in Windows composer? (system shortcut doesnt works)

I worked on Windows platform. If I used native frameless window flags like:

::SetWindowLongPtr((HWND)winId(), GWL_STYLE, WS_POPUP | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);

I recieve frameless window that correctly works with default Windows windows composer - it correctly change the state when I press "WIN" key + arrows.

When I try to use Qt library with following frameless window flags:

setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::CustomizeWindowHint);

I recieve the window that doesnt responce on the "WIN" key + arrows. So it doesnt works with default Windows window composer.

Which compbination of Qt window flags would have similar behavior like native flags above?

So, indeed this has little to do with Qt, it seems. Having anything but a full frame on a Windows window seems to disable the "snap" shortcuts, even if the window can still be resized or moved with keyboard arrows (from the Alt+Space system menu).

The workaround is actually pretty simple. Basically to implement the QWidget::nativeEvent() virtual method and ignore the WM_NCCALCSIZE message.

I ran into some painting issues when snapping from one screen to another, but worked around that with a mask (notes in code comments). Would be nice to find a cleaner solution (it may qualify as a Qt bug actually).

As a bonus this also allows for rounded corners. The painting/styling is based on a rounded message box I made for another answer , and the painting code is documented more fully over there.

The system menu and all interactions with keys (move/resize/snap/etc) work. Mouse handling could be added by implementing the WM_NCHITTEST message in nativeEvent() (see references below).

Only tested on Win7, would be curious how it acts on Win10.

FramelessWidget

#include <QPainter>
#include <QPalette>
#include <QStyle>
#include <QStyleOption>
#include <QWidget>
#include <qt_windows.h>

class FramelessWidget : public QWidget
{
    Q_OBJECT
  public:
    explicit FramelessWidget(QWidget *p = nullptr, Qt::WindowFlags f = Qt::Window) :
      // the flags set here should "match" the GWL_STYLE flags set below
      QWidget(p, f | Qt::WindowMinMaxButtonsHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::FramelessWindowHint)
    {
      setAttribute(Qt::WA_TranslucentBackground);  // for rounded corners
      // set flags which will override what Qt does, especially with the Qt::FramelessWindowHint which essentially disables WS_SIZEBOX/WS_THICKFRAME
      SetWindowLongPtr(HWND(winId()), GWL_STYLE, WS_POPUPWINDOW | WS_CAPTION | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN);
    }

    // these settings are only used when no styleSheet is set
    qreal radius = 0.0;        // desired radius in absolute pixels
    qreal borderWidth = -1.0;  // -1: use style hint frame width; 0: no border; > 0: use this width.

  protected:
    bool nativeEvent(const QByteArray &eventType, void *message, long *result) override
    {
      MSG *msg = static_cast<MSG*>(message);
      if (msg && msg->message == WM_NCCALCSIZE) {
        // Just return 0 and mark event as handled. This will draw the widget contents
        // into the full size of the frame, instead of leaving room for it.
        *result = 0;
        return true;
      }
      return QWidget::nativeEvent(eventType, message, result);
    }

    // Override paint event because of transparent background. 
    // Can be styled using CSS or QPalette with backgroundRole()/foregroundRole().
    void paintEvent(QPaintEvent *) override
    {
      QPainter p(this);
      p.setRenderHints(QPainter::Antialiasing);
      QStyleOption opt;
      opt.initFrom(this);
      // be sure to use the full frame size, not the default rect() which is inside frame.
      opt.rect.setSize(frameSize());  

      if (testAttribute(Qt::WA_StyleSheetTarget)) {
        style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
        setMask(QRegion(opt.rect));  // see notes below
        return;
      }

      QRectF rect(opt.rect);
      qreal penWidth = borderWidth;
      if (penWidth < 0.0)
        penWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
      if (penWidth > 0.0) {
        p.setPen(QPen(palette().brush(foregroundRole()), penWidth));
        const qreal dlta = (penWidth * 0.5);
        rect.adjust(dlta, dlta, -dlta, -dlta);
      }
      else {
        p.setPen(Qt::NoPen);
      }
      p.setBrush(palette().brush(backgroundRole()));
      if (radius > 0.0)
        p.drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
      else
        p.drawRect(rect);

      // Setting a mask works around an issue with artifacts when switching screens with Win+arrow
      // keys. I don't think it's the actual mask which does it, rather it triggers the region 
      // around the widget to be polished but I'm not sure. As support for my theory, the mask 
      // doesn't even have to follow the border radius.
      setMask(QRegion(opt.rect));
    }

};  // FramelessWidget

Test implementation


int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  //QLoggingCategory::setFilterRules(QStringLiteral("qt.qpa.windows = true\n"));
  FramelessWidget msgBox;
  msgBox.setWindowTitle("Frameless window test");
  msgBox.setLayout(new QVBoxLayout);
  msgBox.layout()->addWidget(new QLabel(QStringLiteral("<h3>Frameless rounded widget.</h3>"), &msgBox));
  QLabel *lbl = new QLabel(QStringLiteral(
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean fermentum erat rhoncus, "
    "scelerisque eros ac, hendrerit metus. Nunc ac lorem id tortor porttitor mollis. Nunc "
    "tristique orci vel risus convallis, non hendrerit sapien condimentum. Phasellus lorem tortor, "
    "mollis luctus efficitur id, consequat eget nulla. Nam ac magna quis elit tristique hendrerit id "
    "at erat. Integer id tortor elementum, dictum urna sed, tincidunt metus. Proin ultrices tempus "
    "lacinia. Integer sit amet fringilla nunc."
  ), &msgBox);
  lbl->setWordWrap(true);
  msgBox.layout()->addWidget(lbl);
  QPushButton *pb = new QPushButton(QStringLiteral("Close"), &msgBox);
  QObject::connect(pb, &QPushButton::clicked, qApp, &QApplication::quit);
  msgBox.layout()->addItem(new QSpacerItem(1,1, QSizePolicy::Expanding, QSizePolicy::Expanding));
  msgBox.layout()->addWidget(pb);

  msgBox.setStyleSheet(QStringLiteral(
    "FramelessWidget { "
      "border-radius: 12px; "
      "border: 3px solid palette(shadow); "
      "background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #ffeb7f, stop: 1 #d09d1e); "
    "}"
  ));

  msgBox.show();
  return a.exec();
}

在此处输入图片说明

References:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM