Qt在win10自定义标题栏,应用主题颜色到标题栏
前言
Qt自定义标题栏的资料很多了,但是百度来github去,没找到应用win10的主题颜色到标题栏,就自己折腾了一下。弄了个Demo。没什么技术含量,献丑了。
先看效果
截图中实现了标题栏颜色和拖动改变窗口位置。
关键点
QtWin
在.pro文件中加入
QT += winextras
就可以使用QtWin命名空间中的API。这里我们使用
QColor QtWin::realColorizationColor()
根据文档描述:
这玩意儿返回的是带有Alpha的标题栏颜色。OK这样就是真实的颜色了。另外一个函数:
QColor QtWin::colorizationColor(bool *opaqueBlend = Q_NULLPTR)
返回的颜色有色差。
注册表获取是否应用了颜色到标题栏
这个东西好像不是公开的,经过我一番折腾,发现了在注册表的这个位置:
HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\DWM
的这个key对应了上面我们开启的“应用颜色到标题栏和窗口边框”
有了这两个东西就好办了,就算是热更改,只需要在需要显示颜色的窗口的绘制事件中绘制上这个颜色就搞定了。
代码
头文件:captionwidget.h
// captionwidget.h
#ifndef CAPTIONWIDGET_H
#define CAPTIONWIDGET_H
#include <QWidget>
#ifdef Q_OS_WIN
class CaptionWidget : public QWidget
{
Q_OBJECT
public:
explicit CaptionWidget(QWidget *parent = 0);
bool event(QEvent *e) override;
signals:
void btnMinClicked();
void btnMaxNormalClicked();
void btnCloseClicked();
void DwmCompsitionChanged();
protected:
// QWidget interface
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
private:
enum RectType{
Rect_Other = -1,
Rect_Mini,
Rect_NormalMax,
Rect_Close,
};
enum IconType{
Icon_Mini,
Icon_Max,
Icon_Restore,
Icon_Close,
};
bool mini_hover_;
bool max_hover_;
bool close_hover_;
void setTopLevelWidget();
QRectF getRect(RectType type);
QPixmap getPixmap(IconType type,const QColor &color);
QColor getIconColor(bool isClose = false);
int itemAt(const QPoint &pos);
static QColor getForgroundColor(const QColor &backgroundColor);
/// \brief windows8以上版本,获取是否将主题颜色应用到标题栏和边框
/// \return
///
static bool dwmColorPrevalence();
};
#else
class CaptionWidget : public QWidget
{
Q_OBJECT
public:
CaptionWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags())
: QWidget(parent, f)
{
}
};
#endif // Q_OS_WIN
#endif // CAPTIONWIDGET_H
源文件:captionwidget.cpp
// captionwidget.cpp
#include "captionwidget.h"
#include <QSettings>
#include <QtGlobal>
#ifdef Q_OS_WIN
#include <QPaintEvent>
#include <QMouseEvent>
#include <QHelpEvent>
#include <QPainter>
#include <QtDebug>
#include <QToolTip>
#include <QtWin>
#include <QToolTip>
#include <QStyle>
#include <Windows.h>
#pragma comment(lib, "User32.lib")
CaptionWidget::CaptionWidget(QWidget *parent)
: QWidget(parent)
, mini_hover_(false)
, max_hover_(false)
, close_hover_(false)
{
setMouseTracking(true);
setTopLevelWidget();
}
void CaptionWidget::setTopLevelWidget()
{
auto w = this->window();
connect(this, &CaptionWidget::btnMinClicked, w, &QWidget::showMinimized);
connect(this, &CaptionWidget::btnMaxNormalClicked, w, [=](){
auto state = w->windowState();
if(state.testFlag(Qt::WindowMaximized)){
w->showNormal();
}else if(state.testFlag(Qt::WindowNoState)){
w->showMaximized();
}
});
connect(this, &CaptionWidget::btnCloseClicked, w, &QWidget::close);
}
/// QWidget在鼠标移动后停留的短暂时间内,会触发一次QEvent::ToolTip
/// 如果自绘制的按钮,可以在event中显示tooltip
bool CaptionWidget::event(QEvent *e)
{
if (e->type() == QEvent::ToolTip) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(e);
int index = itemAt(helpEvent->pos());
if (index != -1) {
QString str;
switch (index) {
case Rect_Mini:
str = tr("minimize");
break;
case Rect_NormalMax:
str = this->window()->isMaximized()
? tr("restore")
: tr("maximize");
break;
case Rect_Close:
str = tr("close");
break;
default:
Q_UNREACHABLE();
}
QToolTip::showText(helpEvent->globalPos(), str);
} else {
QToolTip::hideText();
e->ignore();
}
return true;
}
return QWidget::event(e);
}
bool CaptionWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
//Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
MSG* msg = *reinterpret_cast<MSG**>(message);
#else
MSG* msg = reinterpret_cast<MSG*>(message);
#endif
switch (msg->message)
{
case WM_DWMCOLORIZATIONCOLORCHANGED:
{
// notify dwm composition changed
emit DwmCompsitionChanged();
return false;
}
default:
return QWidget::nativeEvent(eventType, message, result);
}
}
void CaptionWidget::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if( this->window()->isFullScreen()){
return QWidget::paintEvent(event);
}
QRectF rectMini = getRect(Rect_Mini);
QRectF rectMaxNormal = getRect(Rect_NormalMax);
QRectF rectClose = getRect(Rect_Close);
QPainter p(this);
p.save();
// draw background
int height = this->style()->pixelMetric(QStyle::PM_TitleBarHeight);
height += 8;
QRect titleBarRect(0,
0,
width(),
height);
QColor color{Qt::white};
if(dwmColorPrevalence())
{
color = QtWin::realColorizationColor();
}
if(this->isActiveWindow()){
color.setAlphaF(1);
}else{
color.setAlphaF(0.7);
}
p.fillRect(titleBarRect, color);
p.restore();
// draw icons
p.save();
if(mini_hover_ ){
QColor color("#B9B9B9");
color.setAlphaF(0.5);
p.fillRect(rectMini, color);
}
if(max_hover_ ){
QColor color("#B9B9B9");
color.setAlphaF(0.3);
p.fillRect(rectMaxNormal, color);
}
if(close_hover_){
p.fillRect(rectClose, QColor("#E81123"));
}
// draw minimum icon
auto pix = getPixmap(Icon_Mini, this->getIconColor());
p.drawPixmap(QPointF(rectMini.x() + (rectMini.width() - pix.width()) / 2,
rectMini.y() + (rectMini.height() - pix.height()) / 2),
pix);
// draw maximum and resore icon
IconType type = this->window()->isMaximized()
? Icon_Restore
: Icon_Max;
pix = getPixmap(type, this->getIconColor());
p.drawPixmap(QPointF(rectMaxNormal.x() + (rectMaxNormal.width() - pix.width()) / 2,
rectMaxNormal.y() + (rectMaxNormal.height() - pix.height()) / 2),
pix);
// draw close icon
if(close_hover_){
pix = getPixmap(Icon_Close, Qt::white);
}else{
pix = getPixmap(Icon_Close, this->getIconColor());
}
p.drawPixmap(QPointF(rectClose.x() + (rectClose.width() - pix.width()) / 2,
rectClose.y() + (rectClose.height() - pix.height()) / 2),
pix);
p.restore();
}
void CaptionWidget::mouseMoveEvent(QMouseEvent *event)
{
QWidget::mouseMoveEvent(event);
QPoint p = event->pos();
mini_hover_ = getRect(Rect_Mini).contains(p);
max_hover_ = getRect(Rect_NormalMax).contains(p);
close_hover_ = getRect(Rect_Close).contains(p);
update();
}
void CaptionWidget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
QPoint p = event->pos();
if(event->button() == Qt::LeftButton)
{
if(getRect(Rect_Other).contains(p)){
if(::ReleaseCapture()){
SendMessage(HWND(this->window()->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
event->ignore();
}
}
}
}
void CaptionWidget::mouseReleaseEvent(QMouseEvent *event)
{
QWidget::mouseReleaseEvent(event);
QPoint p = event->pos();
if(event->button() == Qt::LeftButton)
{
if(getRect(Rect_Mini).contains(p) ){
emit btnMinClicked();
mini_hover_ = false;
update();
}
if(getRect(Rect_NormalMax).contains(p)){
emit btnMaxNormalClicked();
max_hover_ = false;
update();
const QSignalBlocker blocker(this);
}
if(getRect(Rect_Close).contains(p)){
emit btnCloseClicked();
}
}
}
void CaptionWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QWidget::mouseDoubleClickEvent(event);
auto w = this->window();
auto p = event->pos();
if(getRect(Rect_Other).contains(p)){
if(event->button() == Qt::LeftButton){
if(w->isMaximized()){
w->showNormal();
}else{
w->showMaximized();
}
}
}
}
void CaptionWidget::leaveEvent(QEvent *event)
{
mini_hover_ = false;
max_hover_ = false;
close_hover_ = false;
update();
QWidget::leaveEvent(event);
}
QRectF CaptionWidget::getRect(CaptionWidget::RectType type)
{
//96 45,28
// 120 58,42
//144 70,45
// 168 80,54
int BtnWidth = 45;
int BtnHeight = 28;
int BtnSpacing = 1;
int dpi = this->window()->logicalDpiX();
switch (dpi) {
case 96:
BtnWidth = 45;
BtnHeight = 30;
break;
case 120:
BtnWidth = 56;
BtnHeight = 40;
break;
case 144:
BtnWidth = 68;
BtnHeight = 42;
break;
case 168:
BtnWidth = 78;
BtnHeight = 52;
break;
default:
BtnWidth = 45;
BtnHeight = 28;
break;
}
int x = 0;
int y = 1;
int w = this->width();
QRectF rectClose(x + w - BtnWidth,
y,
BtnWidth,
BtnHeight);
QRectF rectMax(rectClose.x() - BtnSpacing - BtnWidth,
y,
BtnWidth,
BtnHeight);
QRectF rectMini(rectMax.x() - BtnSpacing - BtnWidth,
y,
BtnWidth,
BtnHeight);
QRectF rectOther(x,
y,
x + rectMini.x(),
42);
switch (type) {
case Rect_Close:
return rectClose;
case Rect_NormalMax:
return rectMax;
case Rect_Mini:
return rectMini;
case Rect_Other:
return rectOther;
default:
return QRectF();
}
}
QPixmap CaptionWidget::getPixmap(CaptionWidget::IconType type, const QColor &color)
{
QSize size(10, 10);
int rr;
int dpi = this->window()->logicalDpiX();
switch (dpi) {
case 96:
rr = 30 / 3;
break;
case 120:
rr = 40 / 3;
break;
case 144:
rr = 42 /3;
break;
case 168:
rr = 52 / 3;
break;
default:
rr = 28 /3;
break;
}
size.setWidth(rr);
size.setHeight(rr);
QPixmap pix(size);
pix.fill(Qt::transparent);
QSizeF sizeF(size.width() *1.0, size.height() * 1.0);
QPainter p(&pix);
switch (type) {
case Icon_Mini:
{
p.save();
QPen pen(QBrush(color), 1.0);
p.setPen(pen);
p.drawLine(QPointF(0, sizeF.height() / 2),
QPointF(sizeF.width(), sizeF.height() / 2));
p.restore();
}
break;
case Icon_Max:
{
p.save();
QPen pen(QBrush(color), 1.0);
p.setPen(pen);
p.drawRect(QRectF(0.0, 0.0, sizeF.width() - 1.0, sizeF.height() - 1.0));
p.restore();
}
break;
case Icon_Restore:
{
p.save();
p.setRenderHint(QPainter::Antialiasing);
QPen pen(QBrush(color), 1.0);
p.setPen(pen);
QRectF rect(0,0,size.width(), size.height());
QRectF r1(rect.x(),
rect.y() + rect.height() / 4,
rect.width() * 3 / 4,
rect.width() * 3 / 4);
p.drawRect(r1);
QVector<QLineF> lines;
QLineF line1(QPointF(rect.x() + rect.width() * 1 / 4, rect.y() + rect.width() * 1 / 4),
QPointF(rect.x() + rect.width() * 1 / 4, rect.y())
);
QLineF line2(QPointF(rect.x() + rect.width() * 1 / 4, rect.y()),
QPointF(rect.x() + rect.width(), rect.y())
);
QLineF line3(QPointF(rect.x() + rect.width(), rect.y()),
QPointF(rect.x() + rect.width(), rect.y() + rect.width() * 3 / 4)
);
QLineF line4(QPointF(rect.x() + rect.width(), rect.y() + rect.width() * 3 / 4),
QPointF(rect.x() + rect.width() * 3 / 4, rect.y() + rect.width() * 3 / 4)
);
lines.append(line1);
lines.append(line2);
lines.append(line3);
lines.append(line4);
p.drawLines(lines);
p.restore();
}
break;
case Icon_Close:
{
p.save();
p.setRenderHint(QPainter::Antialiasing);
QPen pen(QBrush(color), 1.0);
p.setPen(pen);
QRectF rect(0,0,size.width(), size.height());
p.drawLine(QPointF(rect.x(), rect.y()),
QPointF(rect.x() + rect.width(), rect.y() + rect.height())
);
p.drawLine(QPointF(rect.x() + rect.width(), rect.y()),
QPointF(rect.x(), rect.y() + rect.height())
);
p.restore();
}
break;
default:
break;
}
return pix;
}
QColor CaptionWidget::getIconColor(bool isClose)
{
if(!isClose){
QColor color(Qt::black);
if(dwmColorPrevalence() && this->isActiveWindow()){
color = getForgroundColor(QtWin::colorizationColor());
}
return color;
}else{
return Qt::white;
}
}
int CaptionWidget::itemAt(const QPoint &pos)
{
if(getRect(Rect_Mini).contains(pos)){
return Rect_Mini;
}else if(getRect(Rect_NormalMax).contains(pos)){
return Rect_NormalMax;
}
else if(getRect(Rect_Close).contains(pos)){
return Rect_Close;
}
return -1;
}
QColor CaptionWidget::getForgroundColor(const QColor &backgroundColor)
{
auto color = backgroundColor;
double gray = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
return gray > 0.5 ? Qt::black : Qt::white;
}
bool CaptionWidget::dwmColorPrevalence()
{
#ifdef Q_OS_WIN
const QString path = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\DWM";
const QString key = "ColorPrevalence";
QSettings settings(path, QSettings::NativeFormat);
auto value = settings.value(key, false).toBool();
return value;
#else
return false;
#endif
}
#endif // Q_OS_WIN
main.cpp
// main.cpp
#include "captionwidget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CaptionWidget w;
w.show();
return a.exec();
}
其它功能
最小化、最大化/还原、关闭按钮
1.顺便绘制了一下这个几个按钮,效果一般,要用的朋友自己重绘。
2.这三个按钮支持tooltip提示。
3.按钮会根据背景色深浅自动变为灰色或者白色。
窗口拖动
这个很简单,在mousePressEvent事件中发个win32消息:
void CaptionWidget::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
QPoint p = event->pos();
if(event->button() == Qt::LeftButton)
{
if(getRect(Rect_Other).contains(p)){
if(::ReleaseCapture()){
SendMessage(HWND(this->window()->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
event->ignore();
}
}
}
}
参考资料
最后
大冬天的打字手冷jio冷的,如果对你有帮助,点个赞不过分撒。😀