qt 拖拽 修改大小(二)
最近项目需要实现windows下橡皮筋的效果,所以对此做了一些了解,特此记录。
首先windows系统是支持橡皮筋效果的,需要使用win32方 法:SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, showFullWindow, NULL, 0);showFullWindow是一个变量,如果需要windows默认支持橡皮筋则需要传递参数false,否则传递参数true,如果使用 windows默认的橡皮筋缩放,效果如图1所示,会产生一个矩形框,不管是窗口移动还是放大缩小,都会对该矩形框作用,然后当鼠标弹起时,真实窗口才会 移动或者放大缩小。如果不使用橡皮筋拖拽的方式,那么窗口就是实时的拖拽。

图1 windows橡皮筋
在使用Qt窗口时,如果需要支持windows系统这种方式的拖拽,不能够使用setGeometry该函数来移动或者放大缩小窗口,而需要 重写QWidget::nativeEvent这个方法,该方法是在消息进入qt事件循环之前调用的,也就是说该方法会在mouseEvent等方法之前 调用,nativeEvent方法的实现请看另一篇文章qt 拖拽 修改大小,不过在我使用的过程中,使用了HTCAPTION这个属性后,原始窗口的双击放大事件被屏蔽掉了,到现在原因未搞清。在qt 拖拽 修改大小这篇文字中提到的bug,我用迂回的方式解决了,那就是使用Qt::WindowSystemMenuHint属性,但是窗口的放大和缩小使用另一种方式解决。
下面就是我使用代理的方式来支持窗口拖拽,由于该代理代码行数过多,我只写下重点的部分,该代理代码我也是从别人那儿拷贝的,后来根据我自己的理解和项目需求添加了一些东西。
代理头文件
#ifndef NC_FRAMELESS_HELPER_H
#define NC_FRAMELESS_HELPER_H
#include
#include
#include
#include "commonControls/include/commoncontrols_global.h"
class WidgetResizeHandlerImpl;
class CRubberBand : public QRubberBand
{
public:
CRubberBand(QRubberBand::Shape s, QWidget * parent = nullptr);
~CRubberBand();
protected:
virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
void changeEvent(QEvent *) Q_DECL_OVERRIDE;
void showEvent(QShowEvent *) Q_DECL_OVERRIDE;
void moveEvent(QMoveEvent *) Q_DECL_OVERRIDE;
private:
};
//鼠标状态,可以获取鼠标当前和目标窗口的关系
class CursorPosCalculator
{
public:
CursorPosCalculator(){ reset(); }
void reset();
void recalculate(const QPoint& globalMousePos, const QRect& frameRect);
public:
bool onEdges;
bool onLeftEdge;
bool onRightEdge;
bool onTopEdge;
bool onBottomEdge;
bool onTopLeftEdge;
bool onBottomLeftEdge;
bool onTopRightEdge;
bool onBottomRightEdge;
static int mBorderWidth;
};
//真正的处理操作类
class WidgetData
{
public:
WidgetData(WidgetResizeHandlerImpl * d, QWidget* topLevelWidget);
~WidgetData();
QWidget * widget();
void handleWidgetEvent(QEvent * event);//处理指定窗口事件入口函数
void updateRubberBandStatus();
private:
void updateCursorShape(const QPoint& globalMousePos);
void resizeWidget(const QPoint& globalMousePos);
void moveWidget(const QPoint& globalMousePos);
void handleMousePressEvent(QMouseEvent* event);
void handleMouseReleaseEvent(QMouseEvent* event);
void handleMouseMoveEvent(QMouseEvent* event);
void handleLeaveEvent(QEvent* event);
void handleHoverMoveEvent(QHoverEvent* event);
private:
bool mLeftButtonPressed = false;
bool mCursorShapeChanged = false;
Qt::WindowFlags mWindowFlags;
QPoint mDragPos;//拖拽位置起点
QWidget * mWidget = nullptr;//被代理的窗口指针
CRubberBand * mRubberBand = nullptr;//橡胶类,支持橡胶操作
CursorPosCalculator mPressedMousePos;//鼠标按下时光标信息
CursorPosCalculator mMoveMousePos;//鼠标移动时光标信息
WidgetResizeHandlerImpl * d_ptr;
};
///说明:当QWidget设置了Qt::FramelessWindowHint属性时,可以借助该类完成:拖拽+窗口大小更改
class COMMONCONTROLS_EXPORT WidgetResizeHandler : public QObject
{
public:
explicit WidgetResizeHandler(QObject* parent = );
~WidgetResizeHandler();
public:
void activateOn(QWidget * topLevelWidget);//添加topLevelWidget事件代理
void removeFrom(QWidget * topLevelWidget);//移除topLevelWidget事件代理
Qt::CursorShape CursorShape(QWidget * widget);
//窗口移动 default:true
void setWidgetMovable(bool movable);
bool isWidgetMovable();
//大小可变 default:true
void setWidgetResizable(bool resizable);
bool isWidgetResizable();
// 橡胶式窗口移动 default:false
void useRubberBandOnMove(bool use);
bool isUsingRubberBandOnMove();
//橡胶式修改大小 default:false
void useRubberBandOnResize(bool use);
bool isUsingRubberBandOnResisze();
void setBorderWidth(int newBorderWidth);
int borderWidth();
//局部可移动
void useLocalMoveabled(bool use);
void addLocalWidget(QWidget *);
protected:
virtual bool eventFilter(QObject * obj, QEvent * event) Q_DECL_OVERRIDE;//?????????????????????????????????????
private:
WidgetResizeHandlerImpl * d_ptr;
};
#endif // NC_FRAMELESS_HELPER_H
上边头文件中都有基本的注释,我就不过多解释了,下边我主要说下原理
在需要代理的类中声明WidgetResizeHandler对象,然后使用activateOn方法把需要代理的窗口添加到代理,注意被代 理的窗口需要含有Qt::Window属性明也就是需要时顶层窗口,如果对于一个复杂的窗口进行代理时,可能会出现一些意向不到的问题,比如:1、 QLabel接受富文本时,代理拿不到鼠标弹起事件,QToolButton对象不放到布局时,代理也拿不到鼠标弹起事件,这会导致代理不能正常使用,因 此我对该代理进行了修改,添加了useLocalMoveabled接口,允许代理只对局部窗口进行移动,这样是解决了我前边提到的两个问题。如果仔细看 应该也能看到我的代理也是使用setGeometry方法来拖拽窗口的,那么和我之前谈论的橡皮筋方式就有出入了,这个时候重点才来了,哈哈哈,继续往下 看,头文件中有个类CRubberBand,他是继承自QRubberBand,该类就模拟了一个橡皮筋的过程,只是qt提供的类接口有限,有一些写过很 难达到,比如说我要实现一些复杂的代理界面,那么我们就只能自己绘制了,我通过重新实现paintEvent函数,对该类画了一个灰色的矩形框,代码如 下:
QPainter p(this);
p.setPen(QPen(QColor(102, 102, 102), 4));
QRect rect = this->rect().adjusted(2, 2, -3, -3);
p.drawRect(rect);
如果照着我我上边所说的流程走,就会发现除了一个矩形框之外还会有一个背景色填充,这个时候就奇怪了,我们paintEvent并没有画背景 啊,呵呵呵,只需要在构造函数里加上这句话即可setAttribute(Qt::WA_NoSystemBackground),效果如图2所示。

图2 定制橡皮筋
下边我添加一些代理部分代码
1、CRubberBand构造函数
CRubberBand::CRubberBand(QRubberBand::Shape s, QWidget * parent) :QRubberBand(QRubberBand::Rectangle, parent)
{
setAttribute(Qt::WA_TranslucentBackground);
#ifndef Q_DEAD_CODE_FROM_QT4_WIN
setAttribute(Qt::WA_NoSystemBackground);
#endif //Q_DEAD_CODE_FROM_QT4_WIN
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
}
2、使用activateOn后,窗口存储并验证
WidgetData::WidgetData(WidgetResizeHandlerImpl * d, QWidget * topLevelWidget)
{
this->d_ptr = d;
mWidget = topLevelWidget;
mWidget->setMouseTracking(true);
mWindowFlags = mWidget->windowFlags();
//mWindowFlags |= Qt::CustomizeWindowHint | Qt::FramelessWindowHint;
mWindowFlags |= Qt::FramelessWindowHint;
mWidget->setWindowFlags(mWindowFlags);
//mWidget->setWindowFlags( Qt::Popup | Qt::CustomizeWindowHint|Qt::FramelessWindowHint );
//Bug fix, mouse move events does not propagate from child widgets.
//so need the hover events.
mWidget->setAttribute(Qt::WA_Hover);
updateRubberBandStatus();
bool visible = mWidget->isVisible();//防止非widget被代理
mWidget->setVisible(visible);
}
3、当被代理的窗口有鼠标事件时,先有代理处理
void WidgetData::handleWidgetEvent(QEvent * event)
{
switch (event->type())
{
case QEvent::MouseButtonPress:
handleMousePressEvent(static_cast(event));
break;
case QEvent::MouseButtonRelease:
handleMouseReleaseEvent(static_cast(event));
break;
case QEvent::MouseMove:
handleMouseMoveEvent(static_cast(event));
break;
case QEvent::Leave:
handleLeaveEvent(event);
break;
case QEvent::HoverMove:
handleHoverMoveEvent(static_cast(event));
break;
}
}
4、下边5个函数分别是上边的具体实现
void WidgetData::handleMousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::LeftButton)
{
mLeftButtonPressed = true;
QRect frameRect = mWidget->frameGeometry();
mPressedMousePos.recalculate(event->globalPos(), frameRect);
mDragPos = event->globalPos() - frameRect.topLeft();
if (mPressedMousePos.onEdges)
{
if (d_ptr->mUseRubberBandOnResize)
{
mRubberBand->setGeometry(frameRect);
//mRubberBand->show();
}
}
else if (d_ptr->mUseRubberBandOnMove)
{
mRubberBand->setGeometry(frameRect);
//mRubberBand->show();
}
if (d_ptr->mLocalOnMove)//启用局部拖拽功能后 需要处理不在指定范围内的拖拽,并过滤掉
{
bool canMove = false;
for (int i = ; i < d_ptr->mLocalWidget.size(); ++i)
{
if (d_ptr->mLocalWidget[i]->rect().contains(d_ptr->mLocalWidget[i]->mapFromGlobal(event->globalPos())))
{
canMove = true;
break;
}
}
if (canMove == false && mPressedMousePos.onEdges == false)
{
mLeftButtonPressed = false;
}
}
}
}
void WidgetData::handleMouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
d_ptr->mCanMoveFlag = false;
mLeftButtonPressed = false;
mPressedMousePos.reset();
if (mRubberBand && mRubberBand->isVisible())
{
mRubberBand->hide();
mWidget->setGeometry(mRubberBand->geometry());
}
}
}
void WidgetData::handleMouseMoveEvent(QMouseEvent* event)
{
if (mLeftButtonPressed)
{
if (d_ptr->mWidgetResizable && mPressedMousePos.onEdges)
{
resizeWidget(event->globalPos());
}
else if (d_ptr->mWidgetMovable)
{
moveWidget(event->globalPos());
}
}
else if (d_ptr->mWidgetResizable)
{
updateCursorShape(event->globalPos());
}
}
void WidgetData::handleLeaveEvent(QEvent*)
{
if (!mLeftButtonPressed)
{
mWidget->unsetCursor();
}
}
void WidgetData::handleHoverMoveEvent(QHoverEvent* event)
{
if (mLeftButtonPressed)
{
return;
}
if (d_ptr->mWidgetResizable)
{
updateCursorShape(mWidget->mapToGlobal(event->pos()));
}
}
5、更新鼠标状态
void WidgetData::updateCursorShape(const QPoint & globalMousePos)
{
if (mWidget->isFullScreen() || mWidget->isMaximized())
{
if (mCursorShapeChanged)
{
mWidget->unsetCursor();
}
return;
}
mMoveMousePos.recalculate(globalMousePos, mWidget->frameGeometry());
if (mMoveMousePos.onTopLeftEdge || mMoveMousePos.onBottomRightEdge)
{
mWidget->setCursor(Qt::SizeFDiagCursor);
mCursorShapeChanged = true;
}
else if (mMoveMousePos.onTopRightEdge || mMoveMousePos.onBottomLeftEdge)
{
mWidget->setCursor(Qt::SizeBDiagCursor);
mCursorShapeChanged = true;
}
else if (mMoveMousePos.onLeftEdge || mMoveMousePos.onRightEdge)
{
mWidget->setCursor(Qt::SizeHorCursor);
mCursorShapeChanged = true;
}
else if (mMoveMousePos.onTopEdge || mMoveMousePos.onBottomEdge)
{
mWidget->setCursor(Qt::SizeVerCursor);
mCursorShapeChanged = true;
}
else
{
if (mCursorShapeChanged)//修改鼠标状态
{
mWidget->unsetCursor();
mCursorShapeChanged = false;
}
}
}
6、修改窗口大小和移动位置
void WidgetData::resizeWidget(const QPoint& globalMousePos)
{
QRect origRect;
if (d_ptr->mUseRubberBandOnResize)
{
origRect = mRubberBand->frameGeometry();
}
else
{
origRect = mWidget->frameGeometry();
}
int left = origRect.left();
int top = origRect.top();
int right = origRect.right();
int bottom = origRect.bottom();
origRect.getCoords(&left, &top, &right, &bottom);
int minWidth = mWidget->minimumWidth();
int minHeight = mWidget->minimumHeight();
if (mPressedMousePos.onTopLeftEdge)
{
left = globalMousePos.x();
top = globalMousePos.y();
}
else if (mPressedMousePos.onBottomLeftEdge)
{
left = globalMousePos.x();
bottom = globalMousePos.y();
}
else if (mPressedMousePos.onTopRightEdge)
{
right = globalMousePos.x();
top = globalMousePos.y();
}
else if (mPressedMousePos.onBottomRightEdge)
{
right = globalMousePos.x();
bottom = globalMousePos.y();
}
else if (mPressedMousePos.onLeftEdge)
{
left = globalMousePos.x();
int max_width = mWidget->maximumWidth();
if (right - left > max_width)
{
return;
}
}
else if (mPressedMousePos.onRightEdge)
{
right = globalMousePos.x();
}
else if (mPressedMousePos.onTopEdge)
{
top = globalMousePos.y();
}
else if (mPressedMousePos.onBottomEdge)
{
bottom = globalMousePos.y();
}
QRect newRect(QPoint(left, top), QPoint(right, bottom));
if (newRect.isValid())
{
if (minWidth > newRect.width())
{
//determine what has caused the width change.
if (left != origRect.left())
newRect.setLeft(origRect.left());
else
newRect.setRight(origRect.right());
}
if (minHeight > newRect.height())
{
//determine what has caused the height change.
if (top != origRect.top())
newRect.setTop(origRect.top());
else
newRect.setBottom(origRect.bottom());
}
if (d_ptr->mUseRubberBandOnResize)
{
if (mRubberBand->isVisible() == false)
{
mRubberBand->show();
}
mRubberBand->setGeometry(newRect);
}
else
{
//mWidget->setGeometry(newRect);
mWidget->move(newRect.topLeft());
mWidget->resize(newRect.size());
}
}
else
{
//qDebug() << "Calculated Rect is not valid" << newRect;
}
}
void WidgetData::moveWidget(const QPoint & globalMousePos)
{
bool canMove = false;
if (d_ptr->mLocalOnMove == true && d_ptr->mCanMoveFlag != true)
{
for (int i = ; i < d_ptr->mLocalWidget.size(); ++i)
{
if (d_ptr->mLocalWidget[i]->rect().contains(d_ptr->mLocalWidget[i]->mapFromGlobal(globalMousePos)))
{
canMove = true;
d_ptr->mCanMoveFlag = true;
break;
}
}
}
else
{
canMove = true;
}
if (canMove)
{
if (d_ptr->mUseRubberBandOnMove)
{
if (mRubberBand->isVisible() == false)
{
mRubberBand->show();
}
mRubberBand->move(globalMousePos - mDragPos);
}
else
{
mWidget->move(globalMousePos - mDragPos);
}
}
}
qt 拖拽 修改大小(二)的更多相关文章
- qt 拖拽 修改大小
写次篇文章之前,qt窗口的放大缩小和拖拽我都是通过setGeometry方法实现的,但是作为windows程序,windows支持橡 皮筋式(拖拽时有一个虚框)拖拽和拉伸.通过setGeometry方 ...
- qt 拖拽 修改大小(使用了nativeEvent和winEvent)
http://www.cnblogs.com/swarmbees/p/5621543.html http://blog.sina.com.cn/s/blog_9e59cf590102w3r6.html
- Qt::QWidget 无默认标题栏边框的拖拽修改大小方式
开发环境:win10+vs2015+qt5.9.1 背景:开发过程中,一般很少会使用系统提供的标题栏和边框:往往都是自定义一个自己设计的方案.这时候在QWidget中需要加上flag:Qt::Fram ...
- Qt拖拽界面 (*.ui) 缩放问题及解决办法
问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...
- canvas 图片拖拽旋转之二——canvas状态保存(save和restore)
引言 在上一篇日志“canvas 图片拖拽旋转之一”中,对坐标转换有了比较深入的了解,但是仅仅利用坐标转换实现的拖拽旋转,会改变canvas坐标系的状态,从而影响画布上其他元素的绘制.因此,这个时候需 ...
- 解决Delphi图形化界面的TEdit、TLable等组件手动拖拽固定大小,但是编译之后显示有差别的情况
经常遇到这样的情况,在我们使用Delphi的可视化工具进行UI设计的时候,我们拖拽TEdit或者Label组件,并且在可视化界面上设置它们的长.宽 但是当我们编译和运行程序的时候,却发现真正显示出来的 ...
- Qt拖拽界面 (*.ui) 缩放问题及解决办法(在最顶层放一个Layout)
问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...
- JavaScript小实例:拖拽应用(二)
经常在网站别人的网站的注册页中看到一个拖拽验证的效果,就是它的验证码刚开始不出来,而是有一个拖拽的条,你必须将这个拖拽条拖到底,验证码才出来,说了感觉跟没说一样,你还是不理解,好吧,我给个图你看看: ...
- react之每日一更(实现canvas拖拽,增、删、改拖拽模块大小功能)
效果图: import React, { Component } from 'react'; import scaleImage from './images/scale.png'; import c ...
随机推荐
- Unity Shader Learning
Toon 表面没有均匀的阴影. 为了达到这个效果,我们需要一个斜坡图. 其目的是将朗伯光强度NdotL重新映射到另一个值. 使用没有渐变的渐变映射,我们可以强制照明逐步渲染.下图显示了如何使用斜坡图来 ...
- SpringCloud Gateway 测试问题解决
本文针对于测试环境SpringCloud Gateway问题解决. 1.背景介绍 本文遇到的问题都是在测试环境真正遇到的问题,不一定试用于所有人,仅做一次记录,便于遇到同样问题的干掉这些问题. 使用版 ...
- 2018-2019-2 网络对抗技术 20162329 Exp2 后门原理与实践
目录 1.实践基础 1.1.什么是后门 1.2.基础问题 2.实践内容 2.1.使用netcat获取主机操作Shell,cron启动 2.2.使用socat获取主机操作Shell, 任务计划启动 2. ...
- 使用curl上传图片的方法
关键:当参数名为"@绝对路径",这时 CURL 會幫你做 multipart/form-data 編碼 实现方法: $params = array( 'file' => '@ ...
- json格式的数据及遍历:
代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- ES6学习
一.ES6的特点 1.let(变量),const(常量) 2.在ES6中不能重复定义 3.块级作用域 普通作用域 if(true){ var test =1; } console.log(test); ...
- CSS _text-align:justify;实现两端对齐
参考:https://segmentfault.com/q/1010000007136263 法一:text-align-last:justify: html <div> <p cl ...
- LeetCode 81 Search in Rotated Sorted Array II [binary search] <c++>
LeetCode 81 Search in Rotated Sorted Array II [binary search] <c++> 给出排序好的一维有重复元素的数组,随机取一个位置断开 ...
- cnn神经网络入门
1.神经网络最基本的原理也是函数拟合,所以lose function就显得非常重要了,我们训练的目的之一就是减小损失函数,常用的损失函数参考:https://www.cnblogs.com/hypnu ...
- CSS背景图片
1.背景图片插入 代码格式:background-image:url(): 括号内填写图片路径 2.背景图片设置大小 代码格式:background-size:宽.高 3.背景图片设置不平铺 代码格式 ...

