扁平窗口实现之无边框可缩放控件
开发步骤
- 计算控件可变范围区域
- 判断鼠标在区域内改变鼠标对应形状
- 根据扩展方向改变大小
计算可变区域范围
一般来讲窗口都是在鼠标处于窗口的8个方向上时才能对窗体进行大小改变,因此,需要给控件设置一个虚拟的部区域,在内部区域外是改变控件的操作,在内部区域中则是移动窗口,示意图如下:
因此,只需要使用adjusted()
基于控件自身获取一个小一号的区域然后判断鼠标是否在该区域内,示例代码如下:
bool JQFlatBase::judgeOnEdge(const QPoint & _current_pos) {
/* 获取当前窗口大小,获取一个根据检测边界宽度改变的区域 */
QRect inner_rect = rect().adjusted(edge_width_, edge_width_, -edge_width_, -edge_width_);
if (inner_rect.contains(_current_pos)) {
return false;
}
// ......
}
这个edge_width_
是一个边界的宽度,鼠标在这个区域内按下会视为要进行控件大小的改变,这个值越大
响应的范围就越大,也就是示意图中的蓝色区域部分越小
,而橘黄色部分越宽
。
这个目前只能识别出鼠标点击时是要改变大小还是移动,在改变大小时还不能区分到底是往8个方向的哪个方向变化。
目前,我想到的方法是将窗体划分成8个小的区域
,然后依次判断鼠标是否在该区域之内,由此来确定是朝着哪个方向扩展,示意图如下:
根据示意图可以得到区域计算代码
QMap<QString, QRect> JQFlatBase::divideAreas() {
QMap<QString, QRect> divide_area_results;
/* 分割扩展方向区域 */
/* 左上角区域 */
QRect top_left_rect = QRect(rect().topLeft(), QSize(edge_width_, edge_width_));
divide_area_results[TOP_LEFT_EDGE] = top_left_rect;
/* 上边区域 */
QRect top_rect = QRect(top_left_rect.topRight(),
QSize(rect().width() - edge_width_ * 2, edge_width_));
divide_area_results[TOP_EDGE] = top_rect;
/* 右上角区域 */
QRect top_right_rect = QRect(top_rect.topRight(),
QSize(edge_width_, edge_width_));
divide_area_results[TOP_RIGHT_EDGE] = top_right_rect;
/* 右侧边区域 */
QRect right_rect = QRect(top_right_rect.bottomLeft(),
QSize(edge_width_, rect().height() - edge_width_ * 2));
divide_area_results[RIGHT_EDGE] = right_rect;
/* 右下边区域 */
QRect bottom_right_rect = QRect(right_rect.bottomLeft(),
QSize(edge_width_, edge_width_));
divide_area_results[RIGHT_BOTTOM_EDGE] = bottom_right_rect;
/* 左边区域 */
QRect left_rect = QRect(top_left_rect.bottomLeft(),
QSize(edge_width_, rect().height() - edge_width_ * 2));
divide_area_results[LEFT_EDGE] = left_rect;
/* 左下边区域 */
QRect bottom_left_rect = QRect(left_rect.bottomLeft(),
QSize(edge_width_, edge_width_));
divide_area_results[LEFT_BOTTOM_EDGE] = bottom_left_rect;
/* 下边区域 */
QRect bottom_rect = QRect(bottom_left_rect.topRight(),
QSize(rect().width() - edge_width_ * 2, edge_width_));
divide_area_results[BOTTOM_EDGE] = bottom_rect;
return divide_area_results;
}
改变鼠标的形状
区域分割完成后就是根据区域在鼠标移动到该区域之内时,改变对应方向的形状,形状改变是成对出现,分别是左右[水平]
、上下[垂直]
、左上右下[斜左右]
和右上左下[斜右左]
。
void JQFlatBase::changedMouseShape() {
if (edge_flags_[TOP_LEFT_EDGE] || edge_flags_[RIGHT_BOTTOM_EDGE]) {
setCursor(Qt::SizeFDiagCursor);
} else if (edge_flags_[TOP_EDGE] || edge_flags_[BOTTOM_EDGE]) {
setCursor(Qt::SizeVerCursor);
} else if (edge_flags_[TOP_RIGHT_EDGE] || edge_flags_[LEFT_BOTTOM_EDGE]) {
setCursor(Qt::SizeBDiagCursor);
} else if (edge_flags_[RIGHT_EDGE] || edge_flags_[LEFT_EDGE]) {
setCursor(Qt::SizeHorCursor);
}
}
至此,鼠标判断悬浮区域以及改变鼠标形状的功能基本完成,后边就是点击鼠标进行大小和位置的改变。
控件大小改变计算
先判断当前点击鼠标的功能到底是移动还是改变大小,因此,定义两个变量is_changed_size_
和is_move_
在鼠标点击事件函数中设置这两个标志,代码如下:
void JQFlatBase::mousePressEvent(QMouseEvent *event) {
if (event->button() != Qt::LeftButton) {
/* 不是鼠标左键则退出 */
return;
}
is_mouse_press_ = true;
/* 判断是否在是改变大小 */
is_changed_size_ = judgeOnEdge(event->pos());
if (!is_changed_size_)
is_move_ = true;
/* 记录当前状态 */
last_mouse_point_ = event->pos();
last_press_size_ = size();
}
调用judgeOnEdge()
函数,并将当前鼠标位置传入,判断是否点击在改变区域内
。
bool JQFlatBase::judgeOnEdge(const QPoint & _current_pos) {
/* 将全部边界检测标志进行置位 */
restEdgeFlags();
/* 获取当前窗口大小,获取一个根据检测边界宽度改变的区域 */
QRect inner_rect = rect().adjusted(edge_width_, edge_width_, -edge_width_, -edge_width_);
if (inner_rect.contains(_current_pos)) {
setCursor(Qt::ArrowCursor);
return false;
}
QMap<QString, QRect> divide_areas = divideAreas();
for (const QString & _area_name : divide_areas.keys()) {
QRect current_area = divide_areas[_area_name];
if (current_area.contains(_current_pos)) {
edge_flags_[_area_name] = true;
changedMouseShape();
return true;
// qInfo() << _area_name << current_area;
}
}
return false;
}
在该函数还会更新edge_flags_
的变量,该变量是QMap<QString, bool>
用于给当前类提供鼠标是否在某个可变区域内。
点击了某个区域后就会到鼠标移动事件函数中,根据不同的移动方向更新控件大小,代码如下所示:
void JQFlatBase::mouseMoveEvent(QMouseEvent * event) {
/* 在点击鼠标后不能再次判断鼠标位置 */
if (!is_mouse_press_) {
judgeOnEdge(event->pos());
}
if (is_move_ && is_mouse_press_) {
move(event->pos() - last_mouse_point_ + pos());
} else if (is_changed_size_ && is_mouse_press_) {
updateWidget(event->pos());
}
}
调用udateWidegt()
函数将当前鼠标点击后相对控件的坐标传入,该函数内部算法如下:
void JQFlatBase::updateWidget(const QPoint & _update_pos) {
QRect changed_rect = geometry();
if (edge_flags_[RIGHT_EDGE]) {
/* 向右拖动边框时,坐标点不动,更新 x轴方向 大小 */
changed_rect.setRight(_update_pos.x() + pos().x());
} else if (edge_flags_[LEFT_EDGE]) {
/* 向左拖动边框时,坐标 x轴 方向移动,更新 x轴方向 大小 */
changed_rect.setLeft(_update_pos.x() + pos().x());
} else if (edge_flags_[TOP_EDGE]) {
/* 向上拖动边框时,坐标 y轴 方向移动,更新 y轴方向 大小 */
changed_rect.setTop(_update_pos.y() + pos().y());
} else if (edge_flags_[BOTTOM_EDGE]) {
/* 向下拖动边框时,坐标不动,更新 y轴方向 大小 */
changed_rect.setBottom(_update_pos.y() + pos().y());
} else if (edge_flags_[TOP_LEFT_EDGE]) {
/* 向左上拖动边框时,坐标 x,y轴 方向移动,更新 -x,-y轴方向 大小 */
changed_rect.setTopLeft(QPoint(_update_pos.x() + pos().x(), _update_pos.y() + pos().y()));
} else if (edge_flags_[TOP_RIGHT_EDGE]) {
/* 向右上拖动边框时,坐标 -y轴 方向移动,更新 x, -y轴方向 大小 */
changed_rect.setTopRight(QPoint(_update_pos.x() + pos().x(), _update_pos.y() + pos().y()));
} else if (edge_flags_[RIGHT_BOTTOM_EDGE]) {
/* 向右下拖动边框时,坐标不动,更新 x, y轴方向 大小 */
changed_rect.setBottomRight(QPoint(_update_pos.x() + pos().x(), _update_pos.y() + pos().y()));
} else if (edge_flags_[LEFT_BOTTOM_EDGE]) {
/* 向左下拖动边框时,坐标 x 轴 方向移动,更新 -x, y轴方向 大小 */
changed_rect.setBottomLeft(QPoint(_update_pos.x() + pos().x(), _update_pos.y() + pos().y()));
}
setGeometry(changed_rect);
}
计算比较简单基本就是对应QRect
的8个函数setRight()
、setLeft()
、setTop()
、setBottom()
、setTopLeft()
、setTopLeft()
、setTopRight()
和setBottomRight()
。
其中对于需要更新x轴方向
的计算都是_update_pos.x() + pos().x()
,而涉及更新y轴方向的计算都是_update_pos.y() + pos().y()
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biBDcZ0C-1686904253794)(自定义可拖拽改变大小控件.gif)]
tom()、
setTopLeft()、
setTopLeft()、
setTopRight()和
setBottomRight()`。
其中对于需要更新x轴方向
的计算都是_update_pos.x() + pos().x()
,而涉及更新y轴方向的计算都是_update_pos.y() + pos().y()
。