扁平窗口实现之标题栏窗口
开发步骤
- 标题栏的实现
- 带标题栏窗口的思路
- 实现整个窗口可放大缩小
- 实现鼠标点住后随标题栏移动
标题栏开发
在前面两篇文章中实现了可以拖拽和放大缩小的空白控件,但一般widget
独立出现时都是带标题栏的,这篇文章就从标题栏开始,实现一个完整的扁平窗口。
先来看看标题栏的开发,标题栏一般包含就是三个部分,分别由logo
、title
和一组按钮
组成。因此,只要创建对应的控件,使用水平布局分别放置即可,标题栏控件可以继承QWidget
。我这里直接继承我之前的自定义空白控件,代码如下:
//.h
namespace jq {
class JQFlatTitleBar
: public JQFlatBase {
public:
explicit JQFlatTitleBar(QWidget * _parent = nullptr);
~JQFlatTitleBar() override;
private:
QSize bar_size_;
QMap<QString, QLabel*> title_all_label_;
QMap<QString, QPushButton*> title_all_button_;
};
} // jq
//.cpp
namespace jq {
JQFlatTitleBar::~JQFlatTitleBar() {
}
JQFlatTitleBar::JQFlatTitleBar(QWidget *_parent)
: JQFlatBase(_parent)
, bar_size_(60, 30)
{
setMinimumSize(bar_size_);
title_all_label_["logo"] = new QLabel("");
title_all_label_["窗口标题"] = new QLabel("");
title_all_button_["最大化"] = new QPushButton("最大化");
title_all_button_["最小化"] = new QPushButton("最小化");
title_all_button_["关闭"] = new QPushButton("关闭");
auto main_layout = new QHBoxLayout();
setLayout(main_layout);
main_layout->addWidget(title_all_label_["logo"]);
main_layout->addWidget(title_all_label_["软件名称"]);
main_layout->addWidget(title_all_button_["最大化"]);
main_layout->addWidget(title_all_button_["最小化"]);
main_layout->addWidget(title_all_button_["关闭"]);
title_all_label_["软件名称"]->setText(qAppName());
QIcon icon((":/logo.jpg"));
title_all_label_["logo"]->setPixmap(icon.pixmap(18, 18));
}
}
上面这些代码就可以组合成一个简单标题栏控件,效果如下:
这里先记录一个题外话,如果不是使用VisualStudio
进行开发,那么,在调试阶段需要复制一下plugins\imageformats\qjpegd.dll
到当前.exe
的目录下,且保持目录层级不变,这样才能加载出图片。
回到正题,虽然效果很拉跨但基本像是一个标题栏了,后边在继续优化和调整。对于标题栏来说,现代风格的扁平化标题栏其实包含很多的东西,例如有的可能是在标题栏的中间显示当前打开的文档名称、有的可能是在标题栏嵌入一个搜索栏还有的在标题栏中显示当前软件使用软件的用户头像和名称,总之这个东西是随心而变的,这里只是提供一个思路,需要根据具体需求进行控件和布局方面的调整。
扁平化窗口实现思路
有了简单的标题栏控件后可以开始实现一个扁平风格的widget控件了
,先说一下思路,一般的网上的自定义窗体都是采用在一个大的widget
上进行布局,将标题栏放在最上边,这里也不例外,采用这种方式,首先实现一个继承QWidget
的窗体类,同样的这里继承了我之前开发的那个空白的自定义个窗口控件
,代码如下:
// .h
namespace jq {
class JQFlatTitleBar;
class JQFlatWidget
: public JQFlatBase {
public:
explicit JQFlatWidget(QWidget * _parent = nullptr);
~JQFlatWidget() override;
private:
void initUI();
void initData();
private:
JQFlatBase* center_;
JQFlatTitleBar* flat_title_bar_;
};
} // jq
// .cpp
namespace jq {
JQFlatWidget::~JQFlatWidget() {
}
JQFlatWidget::JQFlatWidget(QWidget *_parent)
: JQFlatBase(_parent)
, center_(nullptr)
, flat_title_bar_(nullptr)
{
initUI();
initData();
}
void JQFlatWidget::initUI() {
center_ = new JQFlatBase();
flat_title_bar_ = new JQFlatTitleBar();
auto main_layout = new QVBoxLayout();
main_layout->setContentsMargins(0,0,0,0);
main_layout->setSpacing(0);
setLayout(main_layout);
main_layout->addWidget(flat_title_bar_);
main_layout->addWidget(center_, 1);
}
void JQFlatWidget::initData() {
flat_title_bar_->setBackgroundColor(QColor(199, 125, 187));
center_->setBackgroundColor(QColor(35, 94, 197));
}
} // jq
在initUI()
函数中就是布局的思路,为了看出效果特意在基类里实现了一个设置背景颜色的接口setBackgroundColor()
思路就是重写paintEvent()
函数,这个不是本文重点暂且不表,如果是直接继承QWidget
的话可以自行百度,或者不设置也可以这步只是为了看效果,整体效果如下:
目前看是象那么回事,但这个还是不行鼠标点击标题栏开始移动时,就会发现三个控件放是放到一起了但是都是各自为战的,如下所示:
接下来就是改造这个控件,让这三位可以在一起干活。
整个窗体放大缩小
由于我是使用的自己开发的控件因此为了解决问题,我需要给这个控件做两个接口就是不能改变大小和不能拖拽,如果是直接继承QWidget
的话就可以省去此步。设置状态的思路就是设置变量在移动和改变大小的关键位置进行屏蔽,核心代码如下:
// .h
namespace jq {
class JQFlatBase
: public QWidget {
Q_OBJECT
public:
explicit JQFlatBase(QWidget* _parent = nullptr);
~JQFlatBase() override;
protected:
/* 设置禁用全部方向的拖拽 */
void dragSizeEnable(bool _is_enable);
/* 设置禁用控件移动 */
void dragMoveEnable(bool _is_enable);
private:
bool enable_drag_size_;
bool enable_drag_move_;
};
} // jq
// .cpp
namespace jq {
void JQFlatBase::dragSizeEnable(bool _is_enable) {
enable_drag_size_ = _is_enable;
}
void JQFlatBase::dragMoveEnable(bool _is_enable) {
enable_drag_move_ = _is_enable;
}
void JQFlatBase::updateWidget(const QPoint & _update_pos) {
// ......
if (enable_drag_size_) {
setGeometry(changed_rect);
}
}
void JQFlatBase::changedMouseShape() {
if (!enable_drag_size_) {
return;
}
// ......
}
void JQFlatBase::mouseMoveEvent(QMouseEvent * event) {
// ......
if (is_move_ && is_mouse_press_ && enable_drag_move_) {
move(event->pos() - last_mouse_point_ + pos());
}
// ......
}
} // jq
这样一来标题栏和中心区域控件都不会有鼠标事件响应了,当然这只是第一步,真正要解决问题从下面开始。
首先来解决如何让窗体放大缩小,由于我用两个控件覆盖在一个控件上
,因此,覆盖住的才是主要窗体,它需要响应鼠标事件,但由于被覆盖导致鼠标的事件也没有传递到最下层控件。
经过仔细观察windows的窗口发现,并不是当鼠标进入到控件的边上时才会触发拖拽,而是在快进入到控件周围时就已经改变了。这个和我之前实现扁平控件的原理不太一样,而且鼠标要获取控件之外的鼠标移动事件,这可能会涉及到一些底层API,这会不会牵扯出跨平台问题。总之,我选择比较邪教的方法,O(∩_∩)O,核心代码如下:
void JQFlatWidget::initData() {
dragSizeEnable(true);
dragMoveEnable(true);
setBackgroundColor(QColor(255, 255, 255, 1));
flat_title_bar_->setBackgroundColor(QColor(199, 125, 187));
center_->setBackgroundColor(QColor(35, 94, 197));
layout()->setContentsMargins(dragSizeWidth(),dragSizeWidth(),
dragSizeWidth(),dragSizeWidth());
}
原理就是让在布局对象使用setContentsMargins()
,不设置成四个0,而是根据可退拽改变窗体事件的响应宽度进行设置(PS:最开始不设置间距的话就没这个问题了-.-!!),同时,将底层控件的背景色的透明度改为1,使其基本透明,绝对不能完全透明全透的话也响应不了事件,具体效果如下:
整个窗体移动
接下来解决整个窗体的移动功能,其原理也是一样的就是让上层的标题栏控件不阻止事件的传递,只不过放大缩小可以通过缩小核心区域
将可控制区域
露出来,这个整体移动就没有办法了。
因此只能在JQFlatTitleBar
这个类中重写mouseMoveEvent()
、mousePressEvent()
和mouseReleaseEvent()
三个函数,并且在其中调用QMouseEvent::ignore()
这个函数,这个函数的意思我个人理解是这个事件我不要,给其控件吧,核心代码如下:
void JQFlatTitleBar::mouseMoveEvent(QMouseEvent *event) {
JQFlatBase::mouseMoveEvent(event);
event->ignore();
}
void JQFlatTitleBar::mousePressEvent(QMouseEvent *event) {
JQFlatBase::mousePressEvent(event);
event->ignore();
}
void JQFlatTitleBar::mouseReleaseEvent(QMouseEvent *event) {
JQFlatBase::mouseReleaseEvent(event);
event->ignore();
}
至此,整个控件的点住标题栏并整体移动功能就完成了,效果如下: