很久没有作业务相关的UI功能了,比较炫酷的交互效果也写的少了,最近花了2天时间写了一个简易的高仿富途牛牛组件化的功能,固然了这只是一个初步的效果,并且没有作贴图、美化等工做,可是基本的功能已经有了。本篇文章只是做为组件化的一个开始,后续还会陆续引入更多关于组件化的介绍,相信功能也会愈来愈丰富。除此以外,富途牛牛的一些其余高级功能也会陆续引入,不乏有k线、分时、五日、指标、自选这样的复杂功能。程序员
自选和k线这些东西我已经有成型的效果了,能够参考效果展现,而且已经被我封装成控件,来需求便可定制函数
说的再多,也不如贴一张效果图来的实在,下边的图展现了附图牛牛组件化的一个简单demo,虽然没有数据,可是该有的基础交互流程已经都有了。用过富途牛牛的人应该都知道。工具
可能有一些人不是特别了解这个功能,这里我简单的说一下:组件化
写这个demo,我基本上用了2天的时间,并且写的过程当中,代码结构也有一些变更,达到如今这个效果布局
当时只考虑了一个主组件化界面TemplateLayout的实现,把这个页面总共分了这么几个部门:主Frame、顶部工具条、页签窗体(支持拖拽)、中心内容窗体、工具箱、小窗体subWindow。优化
其中值得注意的有这么几个地方this
除过上述两个问题之外,第一阶段暂时没有其余比较棘手的问题,简单的页面编写我这里就不作过多介绍,主要分析下上边两个问题的处理过程,也是比较重要的处理过程spa
这个问题,说实话,当时真是想了好几个小时,花费了比较长的事件。指针
自开始的想法就是重写父窗口的moveEvent函数,当窗体移动时,我把移动的偏移量告知工具箱,这样工具箱就能够进行联动了,后来进行实现,发现工具箱移动的老是很慢,哎!moveEvent确定是优化了一些调用,真是坑爹啊
想了一下子了,到了吃饭时间了,算了,仍是换换脑子,或许吃完饭回来就有方案了,哈哈哈,事实上还就是这样,锁屏去吃饭啦!!!
程序员便是闲不下来,去吃饭的路上又开始想了,不过此次我想到了方案,咱们以前都是考虑怎么传递变化量到工具箱,那么反过来咱们若是只告诉工具箱主窗口位置发生变化了呢!!!,让工具箱本身去移动,只要保证和以前的主窗口的相对位置不变便可。你说什么?相对位置不变!,你们伙是否是也想到了呢,对了,就是这么简单了,咱们只须要在工具箱本身移动的时候更新这个相对位置便可,当主窗口移动时,咱们仍然是要保证相对位置不变。
就是这么简单!就是这么任性!
void ToolBoxDialog::LinkMove() { if (QWidget * parent = parentWidget()) { QPoint globalPos = parent->mapToGlobal(m_relativePos); move(globalPos); } }
在作这个组件化拖拽以前,我也简单的作过一些拖拽功能,所以这里作这个东西也不困难。首先就是咱们的技术方案选择了,Qt他为咱们提供了一些拖拽上的功能实现,可是定制化不强,一旦咱们想作的更美观,交互上更丰富一些,使用原有的操做逻辑就会遇到一些困难,所以这里咱们彻底本身实现一个拖拽的效果
看这里-咱们经过过滤鼠标的3种事件,来模拟拖拽。哪三种事件呢?鼠标按下、鼠标移动和鼠标抬起
下面咱们分析下具体的事件处理流程,既然咱们想要过滤这三种事件,那么咱们就必需要找一个过滤对象,来过滤全部的页签按钮事件,这里很天然的我就想到了他们的父窗口DragTabWidget,了解Qt的事件循环的人应该都知道接下来我要干什么了,2个步骤:
代码可能会像下面这样
//按钮把本身的事件先让父类处理 this这里表示父窗口 tabButton->installEventFilter(this); //父类重写eventFilter函数 bool DragTabWidget::eventFilter(QObject * watched, QEvent * event) { TabButton * button = dynamic_cast<TabButton *>(watched); if (button && m_buttonMaps.contains(button->GetID())) { } return __super::eventFilter(watched, event); }
有了上面这个步骤以后,咱们接下来只须要专心的处理鼠标事件便可
一、鼠标按下
鼠标按下时,咱们这里须要记录鼠标按下的位置,方便后续移动咱们的页签。
if (event->type() == QEvent::MouseButtonPress) { QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event); m_pressGlobalPos = button->mapToGlobal(mouseEvent->pos()); m_pressButtonPos = button->pos(); }
二、鼠标移动
当鼠标被按下时,而且进行了移动,这个时候的处理过程就比较复杂了,这里咱们涉及到2种效果
首先就是咱们的页签在自身的工具栏种移动,这样的状况比较好处理一些,咱们只须要把根据当前的鼠标位置移动咱们的占用控件
和被拖拽的页签,
占位控件:就是效果展现图中,咱们看到的空白区域,主要是告诉使用者,当鼠标抬起时,控件将会在被移动到这个地方
这里边有一个小技巧:就是咱们拖拽页签的时候,咱们构造一个被拖拽页签的图片,跟随鼠标移动便可,这样省时、省力又省心,最主要仍是bug少。
其次呢当咱们的鼠标移除工具栏时,咱们的页签也是要跟着被拖出原有的组件化页面的,也就是原有的TemplateLayout。移除的过程可能像下面这样
if (event->type() == QEvent::MouseMove) { if (IsMoveTab()) { //移动当前选中按钮 MoveIn(button); MoveSnapSeat(button); } else { //把当前选中按钮拖出布局 MoveOut(button); } }
三、鼠标抬起
当咱们移动页签时,无非就三种状况
处理代码以下,这是第一个阶段的代码,有点儿问题,不支持第三种状况。
仔细回想下咱们把按钮的事件都安装给了本身的父窗口DragTabWidget,可想而知不一样DragTabWidget之间可能也不能进行交互了,由于他们走的都是本身的事件循环、和处理逻辑。
if (event->type() == QEvent::MouseButtonRelease) { if (IsMoveTab()) { //移动拖拽的按钮到占位位置 MoveDragButton(); } else { //添加新的独立窗口 //button 添加到新的TemplateLayout布局中 emit ButtonMoveOutCompleted(button->GetID()); //原有布局中 若是只剩下一个按钮 关闭按钮将不让使用 if (m_buttonMaps.size() == 1) { m_buttonMaps.values().first()->SetCloseEanble(false); } } }
这不第一个版本有了解决不了的问题,这里我才进行了适当的重构,引入第二个版本。
第二个版本的修改主要是,按钮的事件不在交给父窗口去优先处理了,而是交给一个第三者,这个第三者接收了全部的按钮事件,包括不一样DragTabWidget窗体中的按钮,这样事件就达到了统一。
为了解决不一样工具栏之间能够相互拖拽页签,这里咱们引入了第三方类TabMoveManager,这个类专门负责处理全部的按钮事件,处理过程和第一阶段类型,这里我把关键部分的代码贴出来
bool TabMoveManager::eventFilter(QObject * watched, QEvent * event) { TabButton * button = dynamic_cast<TabButton *>(watched); if (button && m_tabButtonMap.contains(button->GetID())) { if (event->type() == QEvent::MouseButtonPress) { QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event); m_pressGlobalPos = mouseEvent->globalPos(); m_pressButtonPos = button->mapToParent(QPoint(0, 0)); //记录从当前被拖拽的tab和模板 ResolveDragTab(button); } else if (event->type() == QEvent::MouseMove) { if (IsMoveTab()) { //移动当前选中按钮 MoveIn(button); MoveSnapSeat(button); } else { //把当前选中按钮拖出布局 MoveOut(button); } } else if (event->type() == QEvent::MouseButtonRelease) { if (IsMoveTab()) { //移动拖拽的按钮到候选模板占位位置 MoveDragButton(); } else //空白处 释放鼠标 须要新构造一个TemplateLayout { emit NeedNewTemplateLayout(button->GetID()); } //清理原有模板布局 if (m_pDragTemplate != m_pCandidateTemplate && m_pCandidateTemplate != nullptr) { m_pDragTemplate->CleanUP(button->GetID()); } //原有布局中 若是只剩下一个按钮 关闭按钮将不让使用 if (m_pDragTabWidget->GetButtonCount() == 1) { m_pDragTabWidget->GetToolButton()->SetCloseEanble(false); } ··· } } return __super::eventFilter(watched, event); }
除了处理拖拽事件之外,他还起到了一个容器的做用,全部的subPanel我都注册到这个类中,其余的地方均可以经过这个类来获取指定Panel
void RegisterSubPanel(const QString &, SubContentWidget *); void UnregisterSybPanel(const QString &); SubContentWidget * GetSubPanel(const QString &) const;
前两个阶段已经把主要的功能基本都实现了,可是代码结构上,包括一些类负责的功能上仍是有一些问题。
第三阶段主要对代码进行了细微的重构,工程代码结构下图所示
下面是工程中包含的全部类:
以上代码基本把视线组件化功能的主要逻辑讲完了,具体的代码量比较大,并且是一个不成形的demo,暂时就不往外放了。
![]() |
![]() |
很重要--转载声明