Search code examples
qtwindows-8githubstylesheet

Qt working Windows 8 style frameless custom window


I recently installed Github for Windows on my Windows 7 machine and loved the custom frame it had, it fit really well with the overall application theme and had it's own titlebar buttons which were really well layed out, very fluent, and seemed very natural to work with.

I did a bit of digging and found 2 flags that would clear out the border completely and after a bit of customization I got my app to also have a nicely customized look which was intuitive yet different from all the apps with the old Windows border.

The thing was it wasn't fluent and naturally responsive like the other windows, it was glitchy as heck, I easily got the window to move around with the mouse but it often glitched and was able to be moved on areas it shouldn't like clicking and dragging on a disabled button.

The maximize button which was linked to showMaximize method just enlarged the whole window to take up the entire desktop, you could still move it (wasnt really trully maximized).

The window responded to none of the system signals like clicking the taskbar to minimize it and such.

After a lot of fixing around I just finally gave up which was ashame cause I really liekd how it looked and it was very intuitive, much like github for Windows is very intuitive.

Is there any way I can accomplish this, I'm really not ready to give up yet.

I know that when making a raw Windows API application you have to link it to the XP built-in style because it inherits the Windows 95 style by default, maybe theres a Windows 8 style that Qt'S not connected to, I do't know didn'T go that far in research yet.


Solution

  • Minimize window by clicking on task bar

    It seems that Qt::FramelessWindowHint's implementation is limited. When this flag is set, Windows thinks that this window cannot be minimized or maximized. I've tried this solution implemented in pure winapi. Minimizing and restoring frameless window by clicking on taskbar works fine. Apparently Qt sets some bad flags that block this functionality. May be there is a good reason for that, I don't know.

    We can use winapi and Qt together but it is troublesome. Firstly, winapi code should be executed after you set window flags and show the window using Qt. Otherwise Qt will overwrite window flags.

    Another problem is when we remove border using winapi, window geometry suddently changes, and Qt doesn't know about that. Rendering and event mapping (including mouse click positions) become invalid. I didn't find any documented way to update mapping. I've found that we can tell Qt that screen orientation has changed, and it forces it to recalculate window geometry. But this looks like a dirty hack. Also the QWidget::windowHandle function is missing in Qt 4 and "is subject to change" in Qt 5. So this method is not reliable. But anyway, it works now. Here is complete code (tested in Windows 8) that should be placed in the top window class constructor:

    #include "windows.h"
    #include <QWindow>
    //...
    show();
    HWND hwnd = reinterpret_cast<HWND>(effectiveWinId());
    LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
    lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU);
    SetWindowLong(hwnd, GWL_STYLE, lStyle);
    setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
    windowHandle()->reportContentOrientationChange(Qt::PrimaryOrientation);
    

    The true way to solve this problem is to modify the Window Qt platform plugin (see QWindowsWindow class in Qt sources). May be there is a way to inherit from the default implementation, modify it and use in your app. Also you can ask Qt developers is this behavior reasonable or is it a bug. I think that this issue can be fixed with a patch.

    If you still intend to use this code and other OSs should be also supported, don't forget to wrap windows-specific implementation in #ifdef Q_OS_WIN.

    Enable window dragging only when title bar is clicked and window is not maximized

    Other problems can be fixed more easily. When you process mouse events to implement window dragging, check window state and event position and disable moving when it is unwanted.

    void MainWindow::mousePressEvent(QMouseEvent *e) {
      if (!isMaximized() && 
          e->button() == Qt::LeftButton && 
          ui->title->geometry().contains(e->pos())) {
        window_drag_start_pos = e->pos();
      }
    }
    
    void MainWindow::mouseReleaseEvent(QMouseEvent *e) {
      window_drag_start_pos = QPoint(0, 0);
    }
    
    void MainWindow::mouseMoveEvent(QMouseEvent *e) {
      if (!window_drag_start_pos.isNull()) {
        move(pos() + e->pos() - window_drag_start_pos);
      }
    }
    
    void MainWindow::on_minimize_clicked() {
      showMinimized();
    }
    
    void MainWindow::on_maximize_clicked() {
      if (isMaximized()) {
        showNormal();
      } else {
        showMaximized();
      }
    }
    

    Here ui->title is a label used for displaying fake title bar, and QPoint window_drag_start_pos is a class variable.