Qt quick實現無邊框可拖拽風格
由來:
現在大量的軟體採用簡潔的風格,即移除系統的標題欄及邊框並自己實現標題欄以達到定製的目的,由於我們寫要用Qt寫一個類似IM的軟體,故有此需求。
方法:
去除系統邊框比較簡單,setFlags(Qt::Window | Qt::FramelessWindowHint);即可,但下一步要實現窗體的拖拽和縮放。
1,最簡單的方法:
這種方法比較簡單直觀,即點選滑鼠的時候記錄下滑鼠位置,然後在滑鼠移動的時候對窗體進行移動。示例程式碼如下:
MouseArea { //為視窗新增滑鼠事件
property int xMouse //儲存滑鼠x座標
property int yMouse //儲存滑鼠y座標
anchors.fill: parent
acceptedButtons: Qt.LeftButton //只處理滑鼠左鍵
drag.filterChildren: true
onPressed: { //接收滑鼠按下事件
xMouse = mouse.x
yMouse = mouse.y
}
onPositionChanged: { //滑鼠按下後改變位置
loginScreen.x = loginScreen.x + (mouse.x - xMouse)
loginScreen.y = loginScreen.y + (mouse .y - yMouse)
}
}
以上方法簡單直觀,但當窗體比較複雜的時候將發生窗體移動比較遲鈍,甚至,當從上標題快速拖拽窗體時,滑鼠移出窗體,而窗體不再移動的問題。
網上的一個案例,大家可以參考http://blog.sina.com.cn/s/blog_a6fb6cc90101au8r.html
2,改進方案:
觀察系統預設的帶標題欄的窗體,發現,拖動窗體時,窗體並不移動,而是由一個矩形框隨著滑鼠移動,當鬆開滑鼠後窗體才進行一次性地移動。
於是,有了一個可行的方案(因為後面有更優的方案,但是如果後面的方案不行,這個方案可能成為最後的方案),當拖拽的時候繪製一個移動的和原窗體等大小的矩形進行移動,最後再一次性地移動(相信系統的拖拽也是採用類似的方案)。具體實施略,可以自行測試。
3,繼續改進方案,應該是一個較優方案:
當視窗確定滑鼠位置時,Windows向視窗傳送WM_NCHITTEST訊息,可以處理該訊息,使得只要滑鼠在視窗內,Windows便認為滑鼠在標題條上。這需要過載CWnd類處理WM_NCHITTEST訊息的OnNcHitTest函式,在函式中呼叫父類的該函式,如果返回HTCLIENT,說明滑鼠在視窗客戶區內,使過載函式返回HTCAPTION,使Windows誤認為滑鼠處於標題條上。
UINT MyWndDlg::OnNcHitTest(CPoint point)
{
// 取得滑鼠所在的視窗區域
UINT nHitTest = CDialog::OnNcHitTest(point);
// 如果滑鼠在視窗客戶區,則返回標題條代號給Windows
// 使Windows按滑鼠在標題條上類進行處理,即可單擊移動視窗
return (nHitTest==HTCLIENT) ? HTCAPTION : nHitTest;
}
知道了原理咱便可以開工了!!
接著又發現瞭如下例子:http://blog.csdn.net/kfbyj/article/details/9284923,不過貌似博主文章是在Qt4時寫的,現在早就沒有MainWindow::winEvent(MSG *message, long *result)函數了呀!!
不過既然Qt堵了一扇門,他一定會開啟另一扇窗,於是有了nativeEvent函式~~
參看該部落格http://blog.csdn.net/tujiaw/article/details/43836039,哈哈,貌似一切都搞定了~ 確實,我們可以開始寫自己的無邊框窗體了~~
上程式碼:
#include "framelessWindow.h"
#include <QQuickItem>
FramelessWindow::FramelessWindow(QWindow *parent) : QQuickWindow(parent)
{
setFlags(Qt::Window | Qt::FramelessWindowHint);
}
FramelessWindow::~FramelessWindow()
{
}
void FramelessWindow::setTitleBar(QQuickItem *titleBar)
{
m_pTitleBar = titleBar;
}
bool FramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
Q_UNUSED(eventType);
if ( windowState() && Qt::WindowMaximized) {
return false;
}
const int HIT_BORDER = 8;
const MSG *msg=static_cast<MSG*>(message);
if(msg->message == WM_NCHITTEST) {
int xPos = ((int)(short)LOWORD(msg->lParam)) - this->frameGeometry().x();
int yPos = ((int)(short)HIWORD(msg->lParam)) - this->frameGeometry().y();
if (m_pTitleBar && m_pTitleBar->contains(QPointF(xPos,yPos))) {
*result = HTCAPTION;
}
if (m_pTitleBar) {
if (m_pTitleBar->contains(QPointF(xPos,yPos)) && !m_pTitleBar->childAt(xPos,yPos)) {
*result = HTCAPTION;
return true;
}
} else {
/*
if(contentItem()->childItems()[0]->childAt(xPos,yPos) == 0)
{
*result = HTCAPTION;
}*/
}
auto child = contentItem()->childAt(xPos,yPos);
if(child)
{
if (child != m_pTitleBar) {
return false;
} else {
if (m_pTitleBar && !m_pTitleBar->childAt(xPos,yPos)) {
*result = HTCAPTION;
return true;
}
return false;
}
}
if(xPos > 0 && xPos < HIT_BORDER) {
*result = HTLEFT;
}
if(xPos > (this->width() - HIT_BORDER) && xPos < (this->width() - 0)) {
*result = HTRIGHT;
}
if(yPos > 0 && yPos < HIT_BORDER) {
*result = HTTOP;
}
if(yPos > (this->height() - HIT_BORDER) && yPos < (this->height() - 0)) {
*result = HTBOTTOM;
}
if(xPos > 0 && xPos < HIT_BORDER && yPos > 0 && yPos < HIT_BORDER) {
*result = HTTOPLEFT;
}
if(xPos > (this->width() - HIT_BORDER) && xPos < (this->width() - 0) && yPos > 0 && yPos < HIT_BORDER) {
*result = HTTOPRIGHT;
}
if(xPos > 0 && xPos < HIT_BORDER && yPos > (this->height() - HIT_BORDER) && yPos < (this->height() - 0)) {
*result = HTBOTTOMLEFT;
}
if(xPos > (this->width() - HIT_BORDER) && xPos < (this->width() - 0) && yPos > (this->height() - HIT_BORDER) && yPos < (this->height() - 0)) {
*result = HTBOTTOMRIGHT;
}
return true;
}
return false;
}
大體和前人的程式碼類似,略有些小改動,下面會說這些改動的原因。
4,進一步,將c++類匯出到qml中使用:
其實下一步相對來說比較簡單,套路即可:在main函式中加入如下程式碼:
qmlRegisterType(“com.FramelessWindow”, 1, 0, “FramelessWindow”);
話說要記得FramelessWindow得繼承自QObject才能匯出哦,還有,我們的類得是用在主窗體下,所以繼承自QQuickWindow
#ifndef MYWINDOW_H
#define MYWINDOW_H
#include <QQuickWindow>
class FramelessWindow : public QQuickWindow
{
Q_OBJECT
public:
explicit FramelessWindow(QWindow *parent = 0);
~FramelessWindow();
Q_INVOKABLE void setTitleBar(QQuickItem *titleBar);
signals:
public slots:
// QWidget interface
protected:
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
private:
QQuickItem *m_pTitleBar;
};
#endif // MYWINDOW_H
於是一切搞定,最後附上具體使用的qml
import QtQuick.Window 2.2
import QtWebEngine 1.2
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebChannel 1.0
import com.FramelessWindow 1.0
FramelessWindow {
width: 500
height: 859
id:window
TitleBar {
id: titleBar
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
height: 100
color: "blue"
}
Component.onCompleted: {
window.setTitleBar(titleBar)
}
}
看,我們指定了一個TitleBar(其實就是一個Rectangle),注意setTitleBar一定得是Q_INVOKABLE或者定義為slot,否則qml中無法呼叫!
還有這段
if (m_pTitleBar && m_pTitleBar->contains(QPointF(xPos,yPos))) {
*result = HTCAPTION;
}
if (m_pTitleBar) {
if (m_pTitleBar->contains(QPointF(xPos,yPos)) && !m_pTitleBar->childAt(xPos,yPos)) {
*result = HTCAPTION;
return true;
}
} else {
/*
if(contentItem()->childItems()[0]->childAt(xPos,yPos) == 0)
{
*result = HTCAPTION;
}*/
}
用於判斷是否設定了titlebar,而且點選位置十分在控制元件下,比較簡單~~