最近一直在仿照富途牛牛作組件化功能,目前已經有了初步的效果。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(); }
很重要--轉載聲明