最近一直在仿照富途牛牛作组件化功能,目前已经有了初步的效果。windows
组件化基础的功能已经有了,接下来就是一些细节上的处理了,好比说加载模板、保存模板、标签页修更名称等等,细节上的问题咱们在后续的文章中都会一一作以介绍架构
最近打算把组件化中的工具箱相关功能作以实现。好比说迷你报价、自选股、小时钟这些窗口。并发
仔细观察了牛牛的小窗口,无非就是一个窗口外壳,标题栏和客户区内容,下面咱们来具体分析下ide
例如效果展现中的gif图,小时钟就是一个小窗口,他的外壳对咱们肉眼所见到的骑士就是外边线、或者说是高亮选中时候的黄色边框。函数
窗口外壳是咱们进行缩放小窗口的利器,当咱们鼠标属于这个外壳时,咱们按下鼠标就能够对其进行放大、缩小,若是把鼠标放到四个角进行拖拽的时,你会惊奇的发现他还能够同时朝着两个方向进行缩放工具
windows原生的窗口是包含标题栏的,一样他还为咱们提供了很好的放大缩小、拖拽、高亮消息通知等很好用的功能。可是windows原生的标题栏属于非客户端,咱们是不能直接进行操做的,也就是说咱们不能对其进行更多的定制化操做。组件化
这下糟心了,windows原生标题栏用不了了布局
既然windows原生的标题栏咱们用不了,那么咱们就只能隐藏原生的标题栏,而后本身去模拟一个新的标题栏,而后实现咱们本身须要的全部标题栏该有的行为。性能
对于这样的小窗口咱们已经实现了,在咱们以前的文章中也有提到,他就是SmallWidget,只是在这个版本的代码里,咱们才实现了放大、缩小等功能
理解了上边2个概念,客户区利器起来就很简单了,他就是咱们能够操做的区域。
既然标题栏都已经被咱们重写了,那么其实咱们定制化后的SmallWidget窗口,标题栏也就是一个客户区了。
如gif图中所展现的,小时钟窗口上的时间就是客户区内容了。
了解了小窗口这个概念以后,来进入咱们本篇文章的重点内容--优秀的时钟
接下来我将会讲述下咱们这个小时钟是怎么完成的。
下面gif图所示,录制的时间比较长,你们能够仔细看下,交互效果彻底是参照富途牛牛作的,只是目前视为没有接入正式数据。
若有问题,欢迎提出。感谢!!!
欢迎你们提出问题,交互、配色均可以
写这篇文章以前,一直想写一篇小窗口管理
的文章,主要是为了更好的经过工具箱来构造小窗口,让使用者使用起来成本更低,无奈架构一直没有写好,所以这里一直日后推。
写这篇文章的时候,小窗口管理
的代码结构已经在搭建了,所以展现的代码可能会暴露这一点,可是这不是咱们这篇文章讲解的核心,暂时不用关心。
小窗口仍是那个窗口类,就像上一篇文章高仿富途牛牛-组件化(三)-界面美化中说的那样,只是这一次咱们又加了一些新的功能。
最主要的就是咱们重写了鼠标3大事件,用于处理缩放效果
virtual void mousePressEvent(QMouseEvent * event) override; virtual void mouseMoveEvent(QMouseEvent *) override; virtual void mouseReleaseEvent(QMouseEvent * event) override;
这三个函数各司其职,分工协做,完成了缩放事件
下面主要分析下第二个步骤,鼠标移动事件处理,他的处理代码可能像下面这样,分为两个部分:修改鼠标状态和修改窗口大小
void SmallWidget::mouseMoveEvent(QMouseEvent * event) { if (mLeftButtonPressed) { ResizeWidget(event); } else { UpdateCursorShape(event->pos()); } __super::mouseMoveEvent(event); }
鼠标移动时,当咱们发现没有按下鼠标左键,这个时候咱们就执行UpdateCursorShape方法,去判断是否能够进行修改窗口大小,同时修改鼠标状态
当鼠标不在窗口边缘时,也即不知足修改鼠标状态时,记得把鼠标状态还原
当鼠标处于窗口边缘时,而且在必定的范围内,就认为他进入了修改大小的准备状态
如代码所示,咱们首先判断鼠标知足四个边的那几个边的缩放,而后在继续判断是不是在某一个角上拖拽,最后一个变量onEdges标志着是否有缩放状态。
当有一个变量onEdges变为true时,咱们就认为要进行缩放,而后下一步咱们就能够进行修改鼠标状态
void SmallWidget::Recalculate(const QPoint& mousePos, const QRect& frameRect) { int mouseX = mousePos.x(); int mouseY = mousePos.y(); int frameX = frameRect.x(); int frameY = frameRect.y(); int frameWidth = frameRect.width(); int frameHeight = frameRect.height(); onLeftEdge = mouseX >= frameX && mouseX <= frameX + mBorderWidth;//左 onRightEdge = mouseX >= frameX + frameWidth - mBorderWidth && mouseX <= frameX + frameWidth;//右 onTopEdge = mouseY >= frameY && mouseY <= frameY + mBorderWidth;//上 onBottomEdge = mouseY >= frameY + frameHeight - mBorderWidth && mouseY <= frameY + frameHeight;//下 onTopLeftEdge = onTopEdge && onLeftEdge; onBottomLeftEdge = onBottomEdge && onLeftEdge; onTopRightEdge = onTopEdge && onRightEdge; onBottomRightEdge = onBottomEdge && onRightEdge; //only these checks would be enough onEdges = onLeftEdge || onRightEdge || onTopEdge || onBottomEdge; }
判断完是否有边能够进行缩放以后,咱们只须要根据这些变量就能够轻易知道,咱们要把鼠标状态改为什么样子了。
最后记住,若是不使用了,记得还原鼠标状态
void SmallWidget::UpdateCursorShape(const QPoint & mousePos) { Recalculate(mousePos, rect()); if (onTopLeftEdge || onBottomRightEdge) { setCursor(Qt::SizeFDiagCursor); mCursorShapeChanged = true; } else if (onTopRightEdge || onBottomLeftEdge) { setCursor(Qt::SizeBDiagCursor); mCursorShapeChanged = true; } else if (onLeftEdge || onRightEdge) { setCursor(Qt::SizeHorCursor); mCursorShapeChanged = true; } else if (onTopEdge || onBottomEdge) { setCursor(Qt::SizeVerCursor); mCursorShapeChanged = true; } else { if (mCursorShapeChanged)//修改鼠标状态 { unsetCursor(); mCursorShapeChanged = false; } } }
修改了鼠标状态实在鼠标未按下的时候触发的,一旦咱们修改了鼠标状态后,拖拽的第一步准备工做算是作到位了,这个时候咱们只要按下鼠标,继续移动鼠标,而后就进入了修改窗口大小的流程中
修改窗口大小主要是使用了咱们鼠标按下时记录的一些信息
窗口大小的量应该等于鼠标按下时到移动的距离偏移,这里咱们就那有边框右移来讲明问题
假设说右边框自己的值时500,咱们鼠标按下时的全局坐标是1000,这个时候鼠标向右移动,移动到了1100这个坐标,那么鼠标其实就是移动了100像素,那么这样就很清晰了,咱们的右边框此时的值应该是500+100=600。
为何移动后的位置 = 从按下时窗口的位置+鼠标按下时到当前位置的偏移量?这样作有一个很大的好处,那就是咱们不须要考虑中间的过程,及时中间有些地方处理错了,若是又一次处罚了缩放,那么错误也会被修正。
还有一个好处就是,若是咱们每次只作上一次和本次的鼠标位置偏移量,这样处理结果会有异常,根据机器性能,有些机器会丢失须要处理的事件,致使鼠标移动的距离大,咱们窗口缩放的少,这个主要是由于Qt把不少事件优化了。
void SmallWidget::ResizeWidget(QMouseEvent * event) { QPoint mousePos = event->pos(); QPoint globalPos = event->globalPos(); QPoint offsetPos = globalPos - m_PressPos; QRect origRect = m_PressRect; if (onLeftEdge) { origRect.setLeft(origRect.left() + offsetPos.x()); } else if (onRightEdge) { origRect.setRight(origRect.right() + offsetPos.x()); } else if (onTopEdge) { origRect.setTop(origRect.top() + offsetPos.y()); } else if (onBottomEdge) { origRect.setBottom(origRect.bottom() + offsetPos.y()); } //其余四个角的处理省略 if (onEdges) { move(origRect.topLeft()); resize(origRect.size()); } }
讲完了小窗口,咱们的小时钟已经具备了放大、缩小、和移动的功能。
接下来咱们分析下这个时钟是怎么显示的,貌似他好像还支持自提自动放大。
首先咱们来分析下这个时钟窗口的布局,上边是一个动态刷新的时间文本,下边是一个文本框,主要显示当前日期。仔细看效果展现的gif图,其中主要的应该是上半部分的时间了,由于他竟然支持窗口当大时,自身也能够平滑的放大
整个窗口的代码布局,我这里就不作介绍了,比较简单,就是一个垂直布局,其中有一个须要注意的地方就是分割线,研究过QtDesigner的同窗应该都知道,Qt中的分割线其实就是一个像素的QFrame,他的实现代码可能像下面这样,而后加入布局便可
//实现代码 QFrame * line = new QFrame; line->setObjectName("line"); line->setFixedHeight(1); //样式表 ClockSmall QFrame#line{border:1 solid #474F57;}
重要环节登场了,也是咱们本篇文章中的核心,支持字体平滑放大,就像效果图那样
void TimeLabel::paintEvent(QPaintEvent * event) { __super::paintEvent(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QFont font = painter.font(); font.setPixelSize(14); painter.setFont(font); double side = qMin(width(), height()); int text_w = painter.fontMetrics().width(m_text); int text_h = painter.fontMetrics().height(); painter.translate(width() / 2, height() / 2); painter.scale(side / text_w * 1.5, side / text_w * 1.5); QRect r(-text_w / 2, -text_h / 2, text_w, text_h); painter.drawText(r, m_text); }
上面这几行代码就是负责绘制时钟的,这里边使用到了一个技巧--场景缩放
painter.scale()这行代码能够把绘制的场景缩放一个比例系数,也就是当咱们的窗体放大缩小时,咱们根据窗体的大小计算出一个合适的缩放比,而后把场景进行缩放,这样咱们的字体天然而然就会变大
绘制时,咱们也应该开启平滑绘制QPainter::Antialiasing这个属性,这样咱们的程序看起来就会更舒畅一些,不会出现很明显的锯齿
在缩放场景的时候,咱们是这么干的
这里须要注意一个点,咱们必需要计算矩形来绘制文字,若是想计算文字的起始点坐标这个比较困难,由于文字绘制时,并非说你给的起始点就是文本串的左上角
最后咱们作一个定时器,每一个一秒进行数据更新,而后刷新界面便可
//启动定时器 QTimer * timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &ClockSmall::UpdateTime); timer->start(1000); UpdateTime(); //更新数据 void ClockSmall::UpdateTime() { QDateTime dt = QDateTime::currentDateTime(); m_pTime->SetText(dt.time().toString("HH:mm:ss")); QString text = QStringLiteral("北京(CN) ") + dt.date().toString("yyyy/MM/d"); m_pText->setText(text); } //更新数据 并发起绘制请求 void TimeLabel::SetText(const QString & text){ m_text = text; update(); }
![]() |
![]() |
很重要--转载声明