前言
前两天登录github看到一个网友的留言,这两天比较闲就研究了下。
实现效果
实现思路
实现一个继承自QWidget的类,我们就叫MuWinWindow,然后设置无边框setWindowFlags(Qt::FramelessWindowHint);
然后实现bool MuWinWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
此时我们拖动窗体的任何部分都能实现上面的效果。我们想要的是只能通过拖动标题栏来实现上述的效果,在MuWinWindow添加QVBoxLayou布局来添加Titlebar和ClientWidget,此时点击ClientWidget区域就不会移动窗体,在Titlebar中通过setMask来设置我们需要Titlebar接收鼠标事件的地方(最小化、放大还原和关闭等按钮),其余区域将不会显示,鼠标事件直接由MuWinWindow接收。但是这里面有一个问题,如果我们在Titlebar中显示窗口图标和标题的话,需要通过setMask设置他们,但是此时拖动窗口图标和标题的区域是不起作用的,因此我们只能将图标和标题信息绘制到了MuWinWindow上面。
这样一来,设置MuWinWindow的背景颜色其实就是设置所谓的标题栏的颜色,窗口图标和标题都是在MuWinWindow中完成的。
代码
MuWinWindow.h
#ifndef MUWINWINDOW_H
#define MUWINWINDOW_H
#include <QWidget>
#include <Windows.h>
namespace Ui {
class MuWinWindow;
}
class QHBoxLayout;
class QVBoxLayout;
class MuWinWindow : public QWidget
{
Q_OBJECT
public:
explicit MuWinWindow(QWidget *parent = 0);
~MuWinWindow();
void paintEvent(QPaintEvent *e);
QString iconFileName() const;
void setWindowIcon(const QString &fileName);
void setWindowTitle(const QString &title);
bool isMaximized() const
{ return isMaximized_; }
protected:
virtual bool nativeEvent(const QByteArray &eventType,
void *message,
long *result);
private:
LRESULT calculateBorder(const QPoint &pt);
private:
Ui::MuWinWindow *ui;
QString iconFileName_;
QString title_;
QVBoxLayout *mainLayout_;
QWidget *clientWidget_;
bool isMaximized_;
};
#endif // MUWINWINDOW_H
MuWinWindow.cpp
#include "MuWinWindow.h"
#include "ui_muwinwindow.h"
#include <QLayout>
#include <QPainter>
#include <QDesktopWidget>
#include <QDebug>
#include <QtWinExtras/QtWin>
#include <dwmapi.h>
#include "MuWinTitlebar.h"
#pragma comment (lib, "user32.lib")
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam))
#endif
#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam))
#endif
MuWinWindow::MuWinWindow(QWidget *parent) :
QWidget(parent),
ui(new Ui::MuWinWindow),
clientWidget_(new QWidget(this))
{
setWindowFlags(Qt::FramelessWindowHint);
HWND hwnd = reinterpret_cast<HWND>(this->winId());
DWORD style = GetWindowLong(hwnd, GWL_STYLE);
SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
bool enabled = QtWin::isCompositionEnabled();
if (enabled) {
HWND hwnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
::SetWindowLong(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION | WS_BORDER);
QtWin::extendFrameIntoClientArea(this, 1, 1, 1, 1);
}
// 其实是设置的标题栏的颜色
setStyleSheet("background-color: #52baff");
clientWidget_->setStyleSheet("background-color: #FFFFFF");
MuWinTitlebar *titleBar = new MuWinTitlebar(this);
titleBar->setFixedHeight(50);
mainLayout_ = new QVBoxLayout(this);
mainLayout_->addWidget(titleBar);
mainLayout_->addWidget(clientWidget_);
mainLayout_->setContentsMargins(0, 0, 0, 0);
installEventFilter(titleBar);
setWindowIcon(":/1.jpg");
setWindowTitle("WinWindow");
connect(titleBar, &MuWinTitlebar::ShowMinimized, this, &MuWinWindow::showMinimized);
connect(titleBar, &MuWinTitlebar::ShowMaximized, this, &MuWinWindow::showMaximized);
connect(titleBar, &MuWinTitlebar::ShowRestoreSize, this, &MuWinWindow::showNormal);
connect(titleBar, &MuWinTitlebar::Close, this, &MuWinWindow::close);
}
MuWinWindow::~MuWinWindow()
{
}
///
/// \brief MuWinWindow::paintEvent 通过paintEvent绘制图标和标题,如果用MuTitleBar来显示的图标和标题的话,
/// 当鼠标点击图标和标题时无法移动窗体
/// \param e
///
void MuWinWindow::paintEvent(QPaintEvent *e)
{
QPainter p(this);
// 绘制窗口图标
QRect imgTarget(10, 10, 30, 30);
QImage img(iconFileName_);
p.drawImage(imgTarget, img.scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
// 绘制标题
QFont font;
font.setPixelSize(14);
QFontMetrics fm(font);
QRect titleTarget(45, 10, fm.width(title_), 30);
p.setFont(font);
p.setPen(Qt::white);
p.drawText(titleTarget, title_, QTextOption(Qt::AlignCenter));
QWidget::paintEvent(e);
}
QString MuWinWindow::iconFileName() const
{
return iconFileName_;
}
void MuWinWindow::setWindowIcon(const QString &fileName)
{
iconFileName_ = fileName;
this->QWidget::setWindowIcon(QIcon(fileName));
}
void MuWinWindow::setWindowTitle(const QString &title)
{
title_ = title;
this->QWidget::setWindowTitle(title);
}
bool MuWinWindow::nativeEvent(const QByteArray &eventType,
void *message,
long *result)
{
#ifdef Q_OS_WIN
if (eventType != "windows_generic_MSG")
return false;
MSG* msg = static_cast<MSG*>(message);
QWidget* widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
if (!widget)
return false;
switch (msg->message) {
case WM_NCCALCSIZE: {
*result = 0;
return true;
}
case WM_NCHITTEST: {
int x = GET_X_LPARAM(msg->lParam);
int y = GET_Y_LPARAM(msg->lParam);
QPoint pt = mapFromGlobal(QPoint(x, y));
*result = calculateBorder(pt);
if (*result == HTCLIENT) {
QWidget* tempWidget = this->childAt(pt.x(), pt.y());
if (tempWidget == NULL) {
*result = HTCAPTION;
}
}
return true;
}
case WM_GETMINMAXINFO: {
if (::IsZoomed(msg->hwnd)) {
isMaximized_ = true;
RECT frame = { 0, 0, 0, 0 };
AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
frame.left = abs(frame.left);
frame.top = abs(frame.bottom);
widget->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
}
else {
widget->setContentsMargins(0, 0, 0, 0);
isMaximized_ = false;
}
*result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
return true;
}
break;
default:
break;
}
#endif
return QWidget::nativeEvent(eventType, message, result);
}
LRESULT MuWinWindow::calculateBorder(const QPoint &pt)
{
if (::IsZoomed((HWND)this->winId())) {
return HTCLIENT;
}
int borderSize = 4;
int cx = this->size().width();
int cy = this->size().height();
QRect rectTopLeft(0, 0, borderSize, borderSize);
if (rectTopLeft.contains(pt)) {
return HTTOPLEFT;
}
QRect rectLeft(0, borderSize, borderSize, cy - borderSize * 2);
if (rectLeft.contains(pt)) {
return HTLEFT;
}
QRect rectTopRight(cx - borderSize, 0, borderSize, borderSize);
if (rectTopRight.contains(pt)) {
return HTTOPRIGHT;
}
QRect rectRight(cx - borderSize, borderSize, borderSize, cy - borderSize * 2);
if (rectRight.contains(pt)) {
return HTRIGHT;
}
QRect rectTop(borderSize, 0, cx - borderSize * 2, borderSize);
if (rectTop.contains(pt)) {
return HTTOP;
}
QRect rectBottomLeft(0, cy - borderSize, borderSize, borderSize);
if (rectBottomLeft.contains(pt)) {
return HTBOTTOMLEFT;
}
QRect rectBottomRight(cx - borderSize, cy - borderSize, borderSize, borderSize);
if (rectBottomRight.contains(pt)) {
return HTBOTTOMRIGHT;
}
QRect rectBottom(borderSize, cy - borderSize, cx - borderSize * 2, borderSize);
if (rectBottom.contains(pt)) {
return HTBOTTOM;
}
return HTCLIENT;
}
MuWinTitlebar.h
#ifndef MUTITLEBAR_H
#define MUTITLEBAR_H
#include <QWidget>
class QHBoxLayout;
class QPushButton;
class MuWinTitlebar : public QWidget
{
Q_OBJECT
public:
explicit MuWinTitlebar(QWidget *parent = nullptr);
bool eventFilter(QObject *watched, QEvent *event);
void resizeEvent(QResizeEvent *e);
signals:
void ShowMinimized();
void ShowMaximized();
void ShowRestoreSize();
void Close();
public slots:
private:
QPushButton *minButton_;
QPushButton *maxRestoreButton_;
QPushButton *closeButton_;
QHBoxLayout *mainLayout_;
};
#endif // MUTITLEBAR_H
MuWinTitlebar.cpp
#include "MuWinTitlebar.h"
#include <QEvent>
#include <QLabel>
#include <QHBoxLayout>
#include <QIcon>
#include <QPushButton>
#include <QApplication>
#include <QPainter>
#include <QDebug>
#include "muwinwindow.h"
MuWinTitlebar::MuWinTitlebar(QWidget *parent)
: QWidget(parent),
minButton_(new QPushButton()),
maxRestoreButton_(new QPushButton(this)),
closeButton_(new QPushButton(this)),
mainLayout_(new QHBoxLayout(this))
{
// setAttribute(Qt::WA_TransparentForMouseEvents);
maxRestoreButton_->setCheckable(true);
mainLayout_->addStretch();
mainLayout_->addWidget(minButton_);
mainLayout_->addWidget(maxRestoreButton_);
mainLayout_->addWidget(closeButton_);
minButton_->setFixedSize(35, 35);
maxRestoreButton_->setFixedSize(35, 35);
closeButton_->setFixedSize(35, 35);
minButton_->setObjectName("minButton");
minButton_->setStyleSheet("QPushButton { "
" border: none;"
" padding: 3px;"
" image: url(:/minimize.png);"
"}"
"QPushButton:hover { "
" border: none;"
" background-color: #4baeeb;"
"}");
maxRestoreButton_->setStyleSheet("QPushButton { "
" border: none;"
" padding: 8px;"
" image: url(:/maximize.png);"
"}"
"QPushButton:hover { "
" border: none;"
" background-color: #4baeeb;"
"}"
"QPushButton::checked { "
" border: none;"
" padding: 8px;"
" image: url(:/restore.png);"
"}"
"QPushButton::checked:hover { "
" border: none;"
" background-color: #4baeeb;"
"}"
);
closeButton_->setStyleSheet("QPushButton { "
" border: none;"
" padding: 8px;"
" image: url(:/close.png);"
"}"
"QPushButton:hover { "
" border: none;"
" background-color: #4baeeb;"
"}");
connect(minButton_, &QPushButton::clicked, this, &MuWinTitlebar::ShowMinimized);
connect(closeButton_, &QPushButton::clicked, this, &MuWinTitlebar::Close);
connect(maxRestoreButton_, &QPushButton::clicked, [this](bool checked) {
if (checked)
emit ShowMaximized();
else
emit ShowRestoreSize();
});
}
bool MuWinTitlebar::eventFilter(QObject *watched, QEvent *event)
{
MuWinWindow *window = qobject_cast<MuWinWindow *>(watched);
if (window == nullptr)
return false;
switch (event->type()) {
case QEvent::Resize: {
if (window->isMaximized()) {
maxRestoreButton_->setChecked(true);
} else {
maxRestoreButton_->setChecked(false);
}
return true;
}
default:
break;
}
return QWidget::eventFilter(watched, event);
}
void MuWinTitlebar::resizeEvent(QResizeEvent *e)
{
QRegion reg(frameGeometry());
reg -= QRegion(geometry());
reg += childrenRegion();
setMask(reg);
QWidget::resizeEvent(e);
}