现在大量的软件采用简洁的风格,即移除系统的标题栏及边框并自己实现标题栏以达到定制的目的,由于我们写要用Qt写一个类似IM的软件,故有此需求。
去除系统边框比较简单,setFlags(Qt::Window | Qt::FramelessWindowHint);即可,但下一步要实现窗体的拖拽和缩放。
这种方法比较简单直观,即点击鼠标的时候记录下鼠标位置,然后在鼠标移动的时候对窗体进行移动。示例代码如下:
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
观察系统默认的带标题栏的窗体,发现,拖动窗体时,窗体并不移动,而是由一个矩形框随着鼠标移动,当松开鼠标后窗体才进行一次性地移动。
于是,有了一个可行的方案(因为后面有更优的方案,但是如果后面的方案不行,这个方案可能成为最后的方案),当拖拽的时候绘制一个移动的和原窗体等大小的矩形进行移动,最后再一次性地移动(相信系统的拖拽也是采用类似的方案)。具体实施略,可以自行测试。
具体原理我也是参照这个博客http://blog.sina.com.cn/s/blog_6288219501015dwa.html,讲得比较详细,虽然是mfc程序,但毕竟也是windows程序,原理相同。
摘抄一段:
当窗口确定鼠标位置时,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
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(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;
}
大体和前人的代码类似,略有些小改动,下面会说这些改动的原因。
其实下一步相对来说比较简单,套路即可:在main函数中加入如下代码:
qmlRegisterType(“com.FramelessWindow”, 1, 0, “FramelessWindow”);
话说要记得FramelessWindow得继承自QObject才能导出哦,还有,我们的类得是用在主窗体下,所以继承自QQuickWindow
#ifndef MYWINDOW_H
#define MYWINDOW_H
#include
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,而且点击位置十分在控件下,比较简单~~