2

I'm aware that have some similar questions about this topic, but I couldn't find one relative to a window being created using Qt.

I tried all suggestions in this answer: How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?

  • Modifying WM_WINDOWPOSCHANGING flag to SWP_NOCOPYBITS
       wp = reinterpret_cast<tagWINDOWPOS*>(msg->lParam);
       wp->flags |= SWP_NOCOPYBITS;
  • Returning WVR_VALIDRECTS in WM_NCCALCSIZE
        if (msg->wParam == 1)
            return WVR_VALIDRECTS;
  • Applying CS_HREDRAW | CS_VREDRAW styles to the window
SetWindowLongPtr(HWND(winId()), GWL_STYLE, CS_HREDRAW | CS_VREDRAW | WS_POPUPWINDOW | WS_CAPTION | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN);
  • Apply the exstyle WS_EX_COMPOSITED
  • WM_ERASEBKGND to return 1;

But the flicker persists. What else I could try?

The goal is to create a window without a caption, that can be resized/minimized.

The code below is working the problem is when the window is being resized from left/top it causes flickering.

//.h
#include <QtWidgets/QMainWindow>
#include <Windows.h>

class MainWindow : public QMainWindow
{
    Q_OBJECT


public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void Frameless()
    {
        // set flags that 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);
    }

private:
    Ui::MainWindowClass ui;

protected:
    virtual bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;

};
//.cpp
#include "MainWindow.h"


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    Frameless();
    return;
}

MainWindow::~MainWindow()
{}

bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result)
{

    MSG* msg = static_cast<MSG*>(message);

    switch (msg->message)
    {
    case WM_WINDOWPOSCHANGING:
    {
        tagWINDOWPOS* wp;
        wp = reinterpret_cast<tagWINDOWPOS*>(msg->lParam);
        wp->flags |= SWP_NOCOPYBITS;
    }
    break;

    case WM_NCCALCSIZE:
    {
        if (msg->wParam == 1)
            return WVR_VALIDRECTS;

        // 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;
    }
    break;

    case WM_NCHITTEST:
    {
        if (isMaximized())
        {
            return false;
        }

        *result = 0;
        const LONG borderWidth = 8;
        RECT winrect;
        GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);

        // must be short to correctly work with multiple monitors (negative coordinates)
        short x = msg->lParam & 0x0000FFFF;
        short y = (msg->lParam & 0xFFFF0000) >> 16;

        bool resizeWidth = minimumWidth() != maximumWidth();
        bool resizeHeight = minimumHeight() != maximumHeight();
        if (resizeWidth)
        {
            //left border
            if (x >= winrect.left && x < winrect.left + borderWidth)
            {
                *result = HTLEFT;
            }
            //right border
            if (x < winrect.right && x >= winrect.right - borderWidth)
            {
                *result = HTRIGHT;
            }
        }
        if (resizeHeight)
        {
            //bottom border
            if (y < winrect.bottom && y >= winrect.bottom - borderWidth)
            {
                *result = HTBOTTOM;
            }
            //top border
            if (y >= winrect.top && y < winrect.top + borderWidth)
            {
                *result = HTTOP;
            }
        }
        if (resizeWidth && resizeHeight)
        {
            //bottom left corner
            if (x >= winrect.left && x < winrect.left + borderWidth &&
                y < winrect.bottom && y >= winrect.bottom - borderWidth)
            {
                *result = HTBOTTOMLEFT;
            }
            //bottom right corner
            if (x < winrect.right && x >= winrect.right - borderWidth &&
                y < winrect.bottom && y >= winrect.bottom - borderWidth)
            {
                *result = HTBOTTOMRIGHT;
            }
            //top left corner
            if (x >= winrect.left && x < winrect.left + borderWidth &&
                y >= winrect.top && y < winrect.top + borderWidth)
            {
                *result = HTTOPLEFT;
            }
            //top right corner
            if (x < winrect.right && x >= winrect.right - borderWidth &&
                y >= winrect.top && y < winrect.top + borderWidth)
            {
                *result = HTTOPRIGHT;
            }
        }

        if (*result != 0)
            return true;

        QWidget *action = QApplication::widgetAt(QCursor::pos());
        if (action == this){
            *result = HTCAPTION;
            return true;
        }

        return false;
    }
    break;

    default:
        return QWidget::nativeEvent(eventType, message, result);
    }


    return QWidget::nativeEvent(eventType, message, result);
}

EDIT

I was testing the GUI compiling the project as debug when I changed to release the flicker reduced a lot but still persists, and now it flickers from the right instead of the left.

Testing with QT 6.3.1 static debug:

https://www.youtube.com/watch?v=a5fmXKsKDaY&feature=youtu.be

Testing with QT 6.3.1 static release:

https://www.youtube.com/watch?v=OwpxmCsLLRQ

Computer settings, os version: https://i.stack.imgur.com/3DXmd.png

I tested on two different machines using win10, and the flicker happens on both.

The same issue happens with the frameless.h from jdfa answer, the difference is that my method the GUI resizing is way more 'smoothly'/fast.

Cesar
  • 41
  • 2
  • 5
  • 16
  • What content do you have inside your MainWindow? I want to reproduce your issue – jdfa Oct 10 '22 at 21:40
  • @jdfa I'm testing without any content, just a picture in the background see: https://www.youtube.com/watch?v=a5fmXKsKDaY&feature=youtu.be – Cesar Oct 10 '22 at 22:01
  • Do you have the source code of the project somewhere? – boggy Oct 13 '22 at 06:25
  • I was very curious, and I've done an app in Delphi 10 with an image, and it works just fine, it doesn't have the jitter effect. I use windows 10. I am installing now Qt 6.4 - I will give it a try tomorrow morning. – boggy Oct 13 '22 at 06:55
  • I created a 6.4 project with Qt Creator, a form with a QLabel, inside a grid layout to make it resize automatically when the window resizes, I set the pixmap property to an image and set the scaledContent to true and sizePolicy to ignored for both horiz & vert. When I resize the window from the top or left there is no jittery effect. – boggy Oct 13 '22 at 17:21
  • @costa Sorry I had not seen your message, the source code is only this that I posted in the question, I upload it here: https://www25.zippyshare.com/v/LXfyyQj5/file.html, I'm building the project using `Qt 6.3.1 Static`, `Visual Studio 2022`. – Cesar Oct 13 '22 at 23:22
  • The jittery effect only happens when you remove the window title bar, did you remove it? – Cesar Oct 13 '22 at 23:22
  • Yes, I did remove it - please see my answer - I set the style of the window to Qt::CustomizeWindowHint. It removed the title, and you can resize the window from any edge. I have to say that the top border is a bit too high. It has some extra white padding compared to the other edges. – boggy Oct 14 '22 at 00:09

3 Answers3

3

I would suggest to avoid this kind of mix of Qt and WinAPI. Not only it is not portable, but it is also produce your issue.

This problem can be avoided by solving original task (frameless resizeble window) in Qt way by using Qt::FramelessWindowHint. Unfortunately, resizing functionality will be lost together with frame in that case. So you need to reimplement it by overriding mouse event handlers (mousePressEvent, mouseMoveEvent). You are basically doing similar thing already, just using WinAPI.

That problem is already solved here: https://gist.github.com/Nico-Duduf/b8c799f1f2a694732abd1238843b1d70

Here is my result with it: https://i.imgur.com/ovbG2hI.mp4

Edit: here is how I tested it:

#include <QApplication>

#include <QtWidgets/QMainWindow>

#include "FrameLess.h"

class MainWindow : public QMainWindow {
public:
    MainWindow(QWidget* parent = nullptr)
        : QMainWindow(parent)
    {
        setStyleSheet("border-image:url(\"bg.png\");");
    }
};

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);

    MainWindow main;
    FrameLess f(&main);
    main.show();

    return app.exec();
}
jdfa
  • 659
  • 4
  • 11
  • Does your gui also minimizes? I tested the code in your github link but it only work for widgets not the main window, and the frameless widget is flickering like the mainwindow from my code. Could you share the code you used in your result please? – Cesar Oct 11 '22 at 18:44
  • @Katia added example with MainWindow – jdfa Oct 11 '22 at 19:58
  • What os you are testing? I'm testing on win10, i created a new project and included only the `frameless.h cpp` and a picture like on your example, the window still flickers from left and top. – Cesar Oct 11 '22 at 21:18
  • I am using Windows 10. So you don't like result on my video? Is it different from what you had before? – jdfa Oct 11 '22 at 21:34
  • I'm getting a result completely different than your video, see: https://www.youtube.com/watch?v=r6bmKqHuwiU – Cesar Oct 12 '22 at 00:54
  • Could you test with the picture I'm using in the background: https://i.imgur.com/QHrn34Y.jpeg – Cesar Oct 12 '22 at 01:23
  • Can you test compiling the GUI using `debug` instead of release? I'm testing using qt 6.4 static/debug i think this is what causing the jitter, I'm downloading the release version to test again. – Cesar Oct 13 '22 at 18:28
0

I created a project in 6.4 that doesn't have the jittery effect you describe and showed in the video. I tested it on windows 10.

Please create a folder, let's call it TestImage, and save the following files:

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
 Qt::WindowFlags flags;
 flags = Qt::Window;
 //flags |= Qt::FramelessWindowHint;
 flags |= Qt::CustomizeWindowHint;
 this->setWindowFlags(flags);
}

MainWindow::~MainWindow()
{
    delete ui;
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

TestImage.pro

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

RESOURCES += \
    resources.qrc

earth.jpeg (save the image in the same folder)

resources.qrc

<RCC>
    <qresource prefix="/image">
        <file>earth.jpeg</file>
    </qresource>
</RCC>

mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>2068</width>
    <height>1599</height>
   </rect>
  </property>
  <property name="sizePolicy">
   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
    <horstretch>0</horstretch>
    <verstretch>0</verstretch>
   </sizepolicy>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout_2">
    <item row="0" column="0">
     <layout class="QGridLayout" name="gridLayout">
      <property name="sizeConstraint">
       <enum>QLayout::SetNoConstraint</enum>
      </property>
      <item row="0" column="0">
       <widget class="QLabel" name="label">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Ignored" vsizetype="Ignored">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
        <property name="text">
         <string/>
        </property>
        <property name="pixmap">
         <pixmap resource="resources.qrc">:/image/earth.jpeg</pixmap>
        </property>
        <property name="scaledContents">
         <bool>true</bool>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>2068</width>
     <height>22</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources>
  <include location="resources.qrc"/>
 </resources>
 <connections/>
</ui>
boggy
  • 3,674
  • 3
  • 33
  • 56
0

Probably you only need to modify processing the WM_NCCALCSIZE message as follows. Perhaps it looks strange, but give it a try. It seems that when you return HTLEFT, HTTOP etc from WM_NCHITTEST, then the system expects that the first rect in NCCALCSIZE_PARAMS.rgrc contains the client area, as if there were borders. I've used this solution several times and it worked on all Windows versions I've tried so far.

case WM_NCCALCSIZE:
{
    if (msg->wParam == 1)
    {
        NCCALCSIZE_PARAMS& pms = *(NCCALCSIZE_PARAMS*)msg->lParam;
        RECT& r = pms.rgrc[0];

        int borderWidth = GetSystemMetrics(SM_CXSIZEFRAME);
        int borderHeight = GetSystemMetrics(SM_CYSIZEFRAME);
        r.left += borderWidth;
        r.top += borderHeight;
        r.right -= borderWidth;
        r.bottom -= borderHeight;

        *result = WVR_VALIDRECTS;
        return true;
    }

    // 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;
}
break;
Jiri Volejnik
  • 1,034
  • 6
  • 9