上一篇文章高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具咱们讲述了组件化的一些基础东西,并有了一个基本的雏形,使用过富途牛牛的同窗应该对其中的gif图比较熟悉了。虽然效果糙了一点儿,可是该有的基础功能是已经有了。架构
上述几个功能在上一篇文章中都已经有了,今天咱们来说述下第二个关键功能--磁力吸附和一些其余小功能ide
磁力吸附,顾名思义就是说窗口移动时,快要接近另外一个窗口边缘时,会有一种磁性,把正在拖拽的窗口直接吸过去,效果图以下图所示。
函数
高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具文章最后,我列出了工程中全部的类,并作了每一个类的功能说明。工具
本篇文章的工程代码在上一版本的基础上进行了一些优化,代码的结构也更加的清晰,阅读起来更容易,主要是增长了磁力吸附和一些同步功能。组件化
下面来思考下磁力吸附这个功能。优化
首先咱们来考虑下磁力吸附,什么是磁力吸附,明白咱们本身的需求是什么样子的?this
磁力表现出来可能像下面这样:spa
别名:被拖拽窗口(A)、吸附窗口(B)、事件处理(C)代理
有了清晰的需求以后,咱们下面就来考虑怎么实现咱们的需求,既然要作到小窗口之间进行吸附,想想,这个事件处理无论写到A窗口仍是B窗口都不是那么合适。那么可想而知,除过被拖拽的窗口A和将要的吸附窗口B以外,必然须要引入一个第三者C,进行事件处理,他不必定是一个窗口,主要是要能代理A和B的事件,而且进行各类处理便可。
有了第三者C以后,接下来咱们在第三者C中去处理A的移动事件,循环去判断是否和其中某个窗口知足了吸附条件。一旦知足吸附条件,咱们就触发吸附后操做
处理吸附事件时,可能像下面这样
假设咱们有10个窗口,分别是A一、A二、A三、A4...A九、A10等
当要引入第三者窗口时,咱们可能须要思考以下几个问题
思考如上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点
限制鼠标可移动区域的接口上边已经列出来来了,根据参数动态的去限制鼠标移动区域,或者不限制
LimitCursor(bool)
当进行拖拽小窗口时,咱们须要限制鼠标不能移除subPanel,若是不理解subPanel是什么东西,须要仔细去阅读下上一篇文章高仿富途牛牛-组件化(一)-支持页签拖拽、增删、小工具。
限制鼠标移动区域的代码以下所示,主要是使用了ClipCursor这个win32接口,代码比较简单,这里就不作详细说明了。
void SmallGroup::LimitCursor(bool limit) { #ifdef Q_OS_WIN if (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 }
看到这个标题是否是有点儿蒙圈,其实这个也很简单,这里主要说明的是,咱们移动小窗口时,小窗口不能移出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; }
磁力吸附最复杂的地方可能就是这个功能了,当咱们移动一个窗口时,咱们须要判断各类状况,而后去修正咱们的位置。
划重点1:磁力吸附是说当咱们靠近某个小窗口边框时,咱们拖拽的窗口能够被吸附过去,可是须要特别注意,咱们实际移动的距离根本没有到达那么多,所以,当咱们鼠标稍微往远移动一下,窗口应该像被弹开同样。
划重点2:要实现重点1,那么咱们在移动窗口时,就须要有必定的技巧,须要记录小窗口开始移动的位置,和当前移动的距离。根据移动后的距离判断是否能够被吸附,若是被吸附了,那么咱们直接把窗口移动多一点(或者少一点)距离,达到吸附的位置,可是实际上这个时候,咱们鼠标移动的距离并不等于咱们实际移动的距离,这样是为了当咱们鼠标在次偏移时,咱们能够继续去判断是否知足吸附条件,若是不知足则按实际的移动距离。这样就达到了被弹开的视觉效果
上边的描述可能理解起来会比较费劲,这里我在用公式说明下,理解不了就多看几遍吧
startMovePos:开始移动时,鼠标按下的位置
offsetPos:鼠标当前位置距离开始移动时的位置之间的距离
truthPos:按照鼠标位移,将要移动到的位置。
movePos:窗口将要被移动到的位置。磁力吸附后,会在truthPos上有所误差
如上四个变量所示,当咱们移动窗口时,可能会产生如下几个状况
磁力吸附须要处理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面板的左边框,同理其余边框都是同样
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; }
循环判断其余可被吸附的窗口,找到一个距离最近可悲吸附的窗口,而后进行位置修正。当函数返回为真时,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(); } }
以上的内容,基本上就是本篇文章的内容全部内容啦!磁力吸附功能基本完成,但愿能够帮到你们。
![]() |
![]() |
很重要--转载声明