高仿富途牛牛-组件化(二)-磁力吸附
文章目录
- 一、概述
- 二、效果展示
- 三、磁力吸附
- 1、限制鼠标区域
- 2、修正窗口可以移动的区域
- 3、获取最邻近的可被吸附的窗口
- a、A窗口和subPanel面板之间的吸附
- b、A窗口的左边框和被吸附窗口B的右边框之间的吸附
- 四、其他
- 五、相关文章
一、概述
上一篇文章高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具我们讲述了组件化的一些基础东西,并有了一个基本的雏形,使用过富途牛牛的同学应该对其中的gif图比较熟悉了。虽然效果糙了一点儿,但是该有的基础功能是已经有了。
- 工具栏页签拖拽
- 工具栏之间页签拖拽
- 小工具
- 多页签架构
- 小窗口
上述几个功能在上一篇文章中都已经有了,今天我们来讲述下第二个关键功能–磁力吸附和一些其他小功能
二、效果展示
磁力吸附,顾名思义就是说窗口移动时,快要接近另一个窗口边缘时,会有一种磁性,把正在拖拽的窗口直接吸过去,效果图如下图所示。
三、磁力吸附
高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具文章最后,我列出了工程中所有的类,并做了每个类的功能说明。
本篇文章的工程代码在上一版本的基础上进行了一些优化,代码的结构也更加的清晰,阅读起来更容易,主要是增加了磁力吸附和一些同步功能。
下面来思考下磁力吸附这个功能。
首先我们来考虑下磁力吸附,什么是磁力吸附,明白我们自己的需求是什么样子的?
磁力表现出来可能像下面这样:
- 不同子窗口之间希望进行磁力吸附,也就是说窗口移动时,可以被吸附到邻近的窗口边框上
- 不同页签之间不需要关联
- 鼠标不能移动到subPanel之外
别名:被拖拽窗口(A)、吸附窗口(B)、事件处理(C)
有了清晰的需求之后,我们下面就来考虑怎么实现我们的需求,既然要做到小窗口之间进行吸附,想一想,这个事件处理不管写到A窗口还是B窗口都不是那么合适。那么可想而知,除过被拖拽的窗口A和将要的吸附窗口B之外,必然需要引入一个第三者C,进行事件处理,他不一定是一个窗口,主要是要能代理A和B的事件,并且进行各种处理即可。
有了第三者C之后,接下来我们在第三者C中去处理A的移动事件,循环去判断是否和其中某个窗口满足了吸附条件。一旦满足吸附条件,我们就触发吸附后操作
处理吸附事件时,可能像下面这样
假设我们有10个窗口,分别是A1、A2、A3、A4…A9、A10等
- 当我们拖拽A1窗口时,其他窗口都是吸附窗口(B)
- 当我们拖拽A2窗口时,A1和其他窗口都是吸附窗口(B)
- 同理,当我们拖拽其他An窗口时,除过An的窗口都是吸附窗口(B)
当要引入第三者窗口时,我们可能需要思考如下几个问题
- 怎么样引入第三者事件处理类呢?
- 他是怎么初始化的?
- 他的作用范围?
思考如上3个问题,怎么去解决他们!我第一时间就想到了Qt中提供的QButtonGroup类,这个类的作用是用于管理其中的按钮,在他里边包含的按钮不允许有两个同时选中。 是不是很相似,也是管理一堆相同的控件,但是他们中,其中一个控件的操作会对其他所有的控件产生相同的效果。
也就是说:我们可以新增一个SmallGroup类,专门负责处理移动的窗口和其他窗口之间的事件
这个类可能就像这边这样!他提供了新增一个小窗口和移除一个小窗口的接口,添加进来的小窗口我们都可以进行磁力吸附管理。
class SmallGroup : public QObject
{
public:SmallGroup(QObject * object = nullptr);~SmallGroup(){}public:void AddSmall(SmallWidget *);void RemoveSmall(SmallWidget *);void MagneticEnable(bool);void LimitCursor(bool);//限制鼠标移动范围void MoveStart(SmallWidget *, const QPoint &);//开始移动 void MovingDistance(SmallWidget *, const QPoint &);//距离开始移动时的偏差距离protected:virtual bool eventFilter(QObject *, QEvent *) override;private:QPoint MagneticPos(SmallWidget *, const QRect &);private:bool m_bMagnetic;QPoint m_startPos;QVector<SmallWidget *> m_smallVec;SmallWidget * m_pMoveWidget;
};
这个类的思路不难,只是里边有一些比较繁杂的实现,这里我主要说3点
- 限制鼠标区域
- 修正窗口可以移动的区域
- 获取最邻近的可被吸附的窗口
1、限制鼠标区域
限制鼠标可移动区域的接口上边已经列出来来了,根据参数动态的去限制鼠标移动区域,或者不限制
LimitCursor(bool)
当进行拖拽小窗口时,我们需要限制鼠标不能移除subPanel,如果不理解subPanel是什么东西,需要仔细去阅读下上一篇文章高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具。
限制鼠标移动区域的代码如下所示,主要是使用了ClipCursor这个win32接口,代码比较简单,这里就不做详细说明了。
void SmallGroup::LimitCursor(bool limit)
{
#ifdef Q_OS_WINif (limit){if (QWidget * subPanel = dynamic_cast<QWidget *>(parent())){QRect q_rect = subPanel->geometry();QPoint g_pos = subPanel->mapToGlobal(QPoint(0, 0));CRect w_rect;w_rect.left = g_pos.x();w_rect.top = g_pos.y();w_rect.right = g_pos.x() + q_rect.width();w_rect.bottom = g_pos.y() + q_rect.height();ClipCursor(&w_rect);}}else{ClipCursor(nullptr);}
#endif
}
2、修正窗口可以移动的区域
看到这个标题是不是有点儿蒙圈,其实这个也很简单,这里主要说明的是,我们移动小窗口时,小窗口不能移出subPanel,也就是说当subPanel显示时,其中的小窗口都可以全部显示出来,或者被其他小窗口遮挡。
当然了,这个也是需要根据需求来定的,我最开始做的就是4个边都不能出subPanel,但是后来发现,富途牛牛的代码是只有顶部不能出去。因此代码里我注释了3个if修正操作,大家可以根据自家的需求进行修改。
QRect CorrentRect(const QRect & rect, const QRect & subPanel)
{QRect correntRect = rect;//if (correntRect.left() < subPanel.left())//{// correntRect.moveLeft(subPanel.left());//}if (correntRect.top() < subPanel.top()){correntRect.moveTop(subPanel.top());}//if (correntRect.right() > subPanel.right())//{// correntRect.moveRight(subPanel.right());//}//if (correntRect.bottom() > subPanel.bottom())//{// correntRect.moveBottom(subPanel.bottom());//}return correntRect;
}
3、获取最邻近的可被吸附的窗口
磁力吸附最复杂的地方可能就是这个功能了,当我们移动一个窗口时,我们需要判断各种情况,然后去修正我们的位置。
划重点1:磁力吸附是说当我们靠近某个小窗口边框时,我们拖拽的窗口可以被吸附过去,但是需要特别注意,我们实际移动的距离根本没有到达那么多,因此,当我们鼠标稍微往远移动一下,窗口应该像被弹开一样。
划重点2:要实现重点1,那么我们在移动窗口时,就需要有一定的技巧,需要记录小窗口开始移动的位置,和当前移动的距离。根据移动后的距离判断是否可以被吸附,如果被吸附了,那么我们直接把窗口移动多一点(或者少一点)距离,达到吸附的位置,但是实际上这个时候,我们鼠标移动的距离并不等于我们实际移动的距离,这样是为了当我们鼠标在次偏移时,我们可以继续去判断是否满足吸附条件,如果不满足则按实际的移动距离。这样就达到了被弹开的视觉效果
上边的描述可能理解起来会比较费劲,这里我在用公式说明下,理解不了就多看几遍吧
startMovePos:开始移动时,鼠标按下的位置
offsetPos:鼠标当前位置距离开始移动时的位置之间的距离
truthPos:按照鼠标位移,将要移动到的位置。
movePos:窗口将要被移动到的位置。磁力吸附后,会在truthPos上有所偏差
如上四个变量所示,当我们移动窗口时,可能会产生以下几个情况
- 没有磁力吸附,直接移动到truthPos
- 有磁力吸附,移动到被吸附的窗口边框跟前(会产生一个便宜值value,被吸过去了)
- 上一次有磁力吸附,本次不满足处理吸附,直接移动到truthPos,产生弹开的感觉。因为之前被吸附了,有一个偏移值value。
磁力吸附需要处理4个方向的事件,这里我们只讲下左侧吸附,其他情况类似,这里不做介绍
如下代码所示,就是处理吸附位置时的主流程,代码里我只保留了处理做边框吸附的,其他边框代码已删,逻辑都差不多。
QPoint SmallGroup::MagneticPos(SmallWidget * widget, const QRect & rect)
{QPoint pos(rect.topLeft());if (QWidget * subPanel = dynamic_cast<QWidget *>(parent())){QRect panelRect = subPanel->rect();QRect correntRect = CorrentRect(rect, panelRect);if (m_bMagnetic == false){return correntRect.topLeft();}//修改位置后的ps 更准确pos = correntRect.topLeft();QVector<SmallWidget *> smallWidgets = m_smallVec;smallWidgets.removeOne(widget);int distance = 0;//左边框与subPanel左测比较if (CanMagneticPanel(ME_LEFT, rect.left(), panelRect, distance)){pos.setX(panelRect.left());}else {//左边框与其他窗口右边框比较if (CanMagneticSmall(ME_LEFT, rect.left(), smallWidgets, distance)){pos.setX(distance);}}...}
}
左侧吸附具体分两个情况
- 移动窗口A和subPanel之间的吸附
- 移动窗口A的左边框和被吸附窗口B的右边框之间的吸附
a、A窗口和subPanel面板之间的吸附
吸附规则时:A窗口左边框吸附subPanel面板的左边框,同理其他边框都是一样
bool CanMagneticPanel(MagneticEdge edge, int s, const QRect & subPanel, int & distance)
{int value;switch (edge){case ME_LEFT:value = subPanel.left();break;case ME_TOP:value = subPanel.top();break;case ME_RIGHT:value = subPanel.right();break;case ME_BOTTOM:value = subPanel.bottom();break;default:break;}distance = qFabs(s - value);if (distance <= MagneticDistance){return true;}return false;
}
b、A窗口的左边框和被吸附窗口B的右边框之间的吸附
循环判断其他可被吸附的窗口,找到一个距离最近可悲吸附的窗口,然后进行位置修正。当函数返回为真时,distance就是最后要被修复的位置。
值得注意的是,如果有多个满足吸附的窗口边框,我们需要找到一个距离最近的窗口进行修复,也就是说呗吸附的窗口边框和我们正在拖拽的窗口边框距离最近。
不同于和subPanel之间的吸附规则,子窗口之间的吸附规则是,A窗口的左边框会吸附B窗口的右边框;A窗口的顶边框会吸附B窗口的低边框,规则是不是很清晰了,刚好是反的。左对右、顶对低、右对左和低对顶
bool CanMagneticSmall(MagneticEdge edge, int moving, const QVector<SmallWidget *> & allWidget, int & distance)
{distance = 10000;bool result = false;int minDistance = 10000;//根据edge的值 动态去获取窗口的边//例如:edge为ME_LEFT时 需要获取其他窗口的ME_RIGHT 去对比for each (SmallWidget * widget in allWidget){int otherValue = -1; switch (edge){case ME_LEFT:otherValue = widget->geometry().right() + 2;break;case ME_TOP:otherValue = widget->geometry().bottom() + 2;break;case ME_RIGHT:otherValue = widget->geometry().left() - 1;break;case ME_BOTTOM:otherValue = widget->geometry().top() - 1;break;default:break;}if (otherValue != -1){int tmp = qFabs(moving - otherValue);if (minDistance > tmp){minDistance = tmp;if (minDistance <= MagneticDistance){result = true;distance = otherValue;}}}}return result;
}
四、其他
工具箱窗口和工具栏工具按钮联动,按理说这个功能属于比较常见的功能,但是这里我也想拿出来跟大家分享下,这里我主要是借助了QAction这个类,把工具栏种的按钮QToolButton和工具箱窗口进行了绑定,这样不需要过多的信号餐同步,我们就可以很简单的实现功能联动
以前的时候我都是使用信号槽进行同步的,后来才发现这个比较取巧的办法,不是多么高端,主要是可以让代码更清晰。当有越来越多的复杂业务时,QAction的联动同步优势就出来了。
下面是QToolButton和工具箱同步状态的代码
//工具箱,关闭时,同步工具栏按钮状态
void ToolBoxDialog::BindAction(QAction * act)
{connect(m_pToolBoxAct, &QAction::triggered, act, &QAction::setChecked, Qt::UniqueConnection);
}connect(m_pTitle, &ToolBoxTitle::CloseWindow, this, [this](){m_pToolBoxAct->triggered(false);setVisible(false);});//点击工具栏按钮时,打开工具箱
void TemplateLayout::ShowToolBox(bool visible)
{if (m_pToolBox == nullptr){m_pToolBox = new ToolBoxDialog(this);m_pToolBox->BindAction(m_pToolBar->GetToolBoxButton());connect(m_pToolBox, &ToolBoxDialog::SubWindowClicked, m_pPanel, &ContentPanel::CreateSubWindow);}if (visible){m_pToolBox->show();}else{m_pToolBox->hide();}
}
五、相关文章
高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具
以上的内容,基本上就是本篇文章的内容所有内容啦!磁力吸附功能基本完成,希望可以帮到大家。
很重要–转载声明
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。
高仿富途牛牛-组件化(二)-磁力吸附相关推荐
- 高仿富途牛牛-组件化(五)-如何去管理炒鸡多的小窗口
目录 一.概述 二.效果展示 三.功能类 四.设计上的考虑 1.功能拆分 2.关键类 五.相关文章 一.概述 代码写的久了,什么功能都想搞点儿模式.不知道是不是只有我一个人这么想的,做功能时不在是只为 ...
- 高仿富途牛牛-组件化(三)-界面美化
文章目录 一.概述 二.效果展示 三.工具箱 1.布局 a.标题栏 b.客户区 2.功能详解 四.组件模板工具栏 五.其他界面美化 六.使用qss文件 七.相关文章 一.概述 今天是组件化的第三篇文章 ...
- 高仿富途牛牛-组件化-如何去管理超级多的小窗口
一.概述 代码写的久了,什么功能都想搞点儿模式.不知道是不是只有我一个人这么想的,做功能时不在是只为了完成功能,而是在完成功能的同时会去考虑可扩展性怎么样?哪块儿的代码可以复用?又或者哪里需要留更多的 ...
- Qt (高仿Visio)流程图组件开发(二) 基本图元绘制 图元间连线绘制
文章目录 本系列目录 前言 一.如何绘制图元 二.两图元之间如何连线 三.如何实现线跟随图元移动 四.线的位置判断 总结 本系列目录 Qt (高仿Visio)流程图组件开发(一) 效果展示及基本开发框 ...
- Qt (高仿Visio)流程图组件开发(三) 图元基类如何定义,流程图多种图元类型实现
文章目录 本系列目录 前言 一.图元基类的定义 1.图元信息基类结构体 2.图元位置 3.父子对象关系 二.自定义图元实现 1.自定义图元基类(FlowchartGraphicsItem)与Qt原生图 ...
- Android之高仿墨迹天气桌面组件(AppWidgetProvider) .
Android之高仿墨迹天气桌面组件(AppWidgetProvider) . 点击:382 发布时间:2012-10-03 更多0 相信墨迹天气,大家都见过,他在时间显示和天气界面上,很吸引人,今天 ...
- Qt (高仿Visio)流程图组件开发(九) 流程图 代码展示
文章目录 本系列目录 本系列目录 Qt (高仿Visio)流程图组件开发(一) 效果展示及基本开发框架构思 Qt (高仿Visio)流程图组件开发(二) 基本图元绘制 图元间连线绘制 Qt (高仿Vi ...
- Kotlin高仿微信-第35篇-支付-二维码收款(二维码)
Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册.登录.主页.单聊(文本.表情.语音.图片.小视频.视频通话.语音通话.红包.转账).群聊.个人信息.朋友圈.支付服务.扫一扫. ...
- Flutter高仿微信-第22篇-支付-二维码收款(二维码)
Flutter高仿微信系列共59篇,从Flutter客户端.Kotlin客户端.Web服务器.数据库表结构.Xmpp即时通讯服务器.视频通话服务器.腾讯云服务器全面讲解. 详情请查看 效果图: 实现代 ...
- 纯html+css+js高仿苹果手机时钟小组件
话不多数说,这是一款用html+css+js开发的高仿苹果手机时钟组件, 作者将苹果的小组件移植到浏览器插件中,可以在BdTab插件安装该组件 HTML <div style="wid ...
最新文章
- android获取软件大小,android获取屏幕大小包括状态栏和软件导航栏的大小
- .net 页面传参方式总结
- 电脑硬盘为什么叫计算机,电脑分区为何从C盘开始?
- YOLOv5瓷砖表面瑕疵质检
- 精选| 2019年2月R新包推荐
- 数据结构和算法——八种常用的排序算法------基数排序的优化(队列实现)
- 微信公众号扫码登录(一)—— 获取微信公众号二维码
- jpg格式如何转eps路径_jpg/png格式图片转eps格式的方法总结
- FANUC机器人示教器介绍
- 学习笔记:强化学习之A3C代码详解
- 网页页面缩小放大的快捷键
- HTML表格循环中合并单元格,table循环实现表格相同列合并
- 安然公司特殊目的实体(SPEs)解读
- 我爱赚钱吧:你知道自己建网站可以赚钱吗?①
- c语言小鱼的游泳时间,小鱼系列简单题参考代码
- 企业微信的渠道活码怎么用?到底有什么好处?
- Android平台下的图片/视频转Ascii码图片/视频 (一)
- 为何iPad 2充电快 但用不了多久?
- jenkins部署流程图_jenkins怎样实现自动部署的流程图
- 计算机网络协议分析全知识点总结兼期末复习重点
热门文章
- CF235C Cyclical Quest
- vue实现PC端和移动端的界面切换
- 固态硬盘打开计算机就死机,SSD固态硬盘死机卡顿无响应怎么办?SSD卡顿故障处理教程 | 麦田一棵葱...
- Android Runtime.getRuntime().exec()
- SentiLR:Linguistic Knowledge Enhanced Language Representation for Sentiment Analysis 论文阅读笔记
- database rough 1
- 广东海洋大学微型计算机控制技术,LCD显示实验(综合性)
- 项目踩坑随记 —— getTime() is not a function
- 编写程序,先将输入的一系列整数中的最小值与第一个数交换,然后将最大值与最后一个数交换,最后输出交换后的序列。
- 中国菜刀与一句话木马之间的原理分析