简单点说,是在windows下实现窗口拖动和改变大小,一种方法是重载mousePressEvent系列全家福函数,然后在mouseMoveEvent中实现边移鼠标窗口就一边重绘。这种方法网上资料很多,见下:
https://www.cnblogs.com/findumars/p/5518590.html
这种方法缺点是移动的时候窗口闪烁厉害。摒弃。但该方法给了一个很好的思路。
另一种方法:
看到MFC中有调用NCHitTest方法的,这个方法是当鼠标触碰到非客户区时触发,然后调用windows api,实现窗口移动,参照下:
https://bbs.csdn.net/topics/350134227
在这篇帖子的最下方,有MFC改变窗口的方法。
于是我也想到了用Qt调windows api,反正我也不想做跨平台的软件,咳咳。然后发现了Qt有nativeEvent函数,捕获本地所有事件,重载之,专门处理鼠标移动信号。ok,实现了,但绝不完美!为何,继续看。
重载nativeEvent的方法网上查查,有第三个参数Long* result,result=HTLEFT时,鼠标就会变成横向双箭头图案,其他就参照MFC实现吧,后面会给出代码。
然后我们来看哪里不完美。Qt没有非客户区的概念,但类之间的信号传递很明显,当顶层窗口的最外层布局这样设置:
m_layoutMain->setMargin(0);
那么鼠标移到边缘时,鼠标信号的捕获是被最上层的子控件捕获了,实际上就触发不了顶层窗口的nativeEvent了。退而求其次,以为把边界设成5就可以了么?是的,不美观点,设成5确实就可以了,但如果想触发拖动事件的范围也是5,就会发现,鼠标在边界的5范围内,是横向双箭头图案,继续往里移,OK,进入子控件了,鼠标移动事件不触发了,鼠标一直是横向双箭头图案,何况,5也是接受不了的,我要0,不能忍,继续想办法。
然后就想到了过滤器,接着发现了Qt5.7的一个bug。过滤器,在顶层窗口实现nativeEventFilter方法,接着在qApp安装过滤器。
过滤器其实一点都不复杂,选个类A,在类A中实现nativeEventFilter方法后,建个对象a,只要在其他类用installNativeEventFilter(&a)方法安装过滤器,那个这个被安装过滤器的类,接收到了事件后,就会优先发给a来处理,而在qApp中安装过滤器,qApp是第一个接收到消息的,会全部先丢给a处理,a吃剩的残羹冷炙还给qApp,qApp再拿去分给其他改领盒饭的类。怎么算吃剩呢,在过滤器中return false就是吃剩不要了。然后上代码了
这是Class A中的方法,判断鼠标是否在规定区域中,PADDING自己定一个,可以在四个角加大范围,方便触发,我这里+2。
#include
#include
#define PADDING 5
enum Direction{
UP = 0,
DOWN = 1,
LEFT,
RIGHT,
LEFTTOP,
LEFTBOTTOM,
RIGHTBOTTOM,
RIGHTTOP,
NONE
};
class A: public QDialog,public QAbstractNativeEventFilter
{
Q_OBJECT
public:
A(QWidget *parent=0);
~A();
private:
void mousePressEvent(QMouseEvent* event);
void mouseDoubleClickEvent(QMouseEvent *event);
//bool nativeEvent(const QByteArray &eventType, void *message, long *result);
void region(const QPoint &cursorPoint,bool &activeFlag);
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
}
void A::region(const QPoint &cursorGlobalPoint,bool &activeFlag)
{
QRect rect = this->rect();
QPoint tl = mapToGlobal(rect.topLeft());
QPoint rb = mapToGlobal(rect.bottomRight());
int x = cursorGlobalPoint.x();
int y = cursorGlobalPoint.y();
activeFlag = true;
if (tl.x() + PADDING+2 >= x && tl.x() <= x && tl.y() + PADDING+2 >= y && tl.y() <= y) {
// 左上角
dir = LEFTTOP;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
}
else if (x >= rb.x() - PADDING-2 && x <= rb.x() && y >= rb.y() - PADDING-2 && y <= rb.y()) {
// 右下角
dir = RIGHTBOTTOM;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
}
else if (x <= tl.x() + PADDING+2 && x >= tl.x() && y >= rb.y() - PADDING-2 && y <= rb.y()) {
//左下角
dir = LEFTBOTTOM;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
}
else if (x <= rb.x() && x >= rb.x() - PADDING-2 && y >= tl.y() && y <= tl.y() + PADDING+2) {
// 右上角
dir = RIGHTTOP;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
}
else if (x <= tl.x() + PADDING && x >= tl.x()) {
// 左边
dir = LEFT;
this->setCursor(QCursor(Qt::SizeHorCursor));
}
else if (x <= rb.x() && x >= rb.x() - PADDING) {
// 右边
dir = RIGHT;
this->setCursor(QCursor(Qt::SizeHorCursor));
}
else if (y >= tl.y() && y <= tl.y() + PADDING){
// 上边
dir = UP;
this->setCursor(QCursor(Qt::SizeVerCursor));
}
else if (y <= rb.y() && y >= rb.y() - PADDING) {
// 下边
dir = DOWN;
this->setCursor(QCursor(Qt::SizeVerCursor));
}
else {
// 默认
dir = NONE;
this->setCursor(QCursor(Qt::ArrowCursor));
activeFlag = false;
}
}
bool A::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG")
{
MSG* msg = (MSG*)message;
if (msg->message == WM_MOUSEMOVE)
{
QPoint pt = cursor().pos();
bool activeFlag;
region(pt, activeFlag);
if (activeFlag)
{
switch (dir)
{
case UP:
case DOWN:
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZENS)));
break;
case LEFT:
case RIGHT:
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE)));
break;
case LEFTTOP:
case RIGHTBOTTOM:
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZENWSE)));
break;
case RIGHTTOP:
case LEFTBOTTOM:
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZENESW)));
break;
case NONE:
default:
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
return false;
break;
}
return true;
}
}
if (msg->message == WM_LBUTTONDOWN)
{
QPoint pt = cursor().pos();
bool activeFlag;
region(pt, activeFlag);
if (activeFlag)
{
if (ReleaseCapture())
{
switch (dir)
{
case UP:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_TOP, 0);
break;
case DOWN:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOM, 0);
break;
case LEFT:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_LEFT, 0);
break;
case RIGHT:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_RIGHT, 0);
break;
case LEFTTOP:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPLEFT, 0);
break;
case RIGHTTOP:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPRIGHT, 0);
break;
case LEFTBOTTOM:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMLEFT, 0);
break;
case RIGHTBOTTOM:
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMRIGHT, 0);
break;
case NONE:return false;
break;
default:
break;
}
return true;
}
}
}
}
return false;
}
ReleaseCapture是干嘛的?windows 的api,官方说是可以捕捉到鼠标的一举一动,呵呵,我也觉得怎么就实现了呢?但真的实现了啊。
然后接下来安装过滤器:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
A w;
w.show();
a.installNativeEventFilter(&w);
return a.exec();
}
好了,这样,不管顶层窗口w里面有什么控件,都先给w处理了,不在判定区域内,就在nativeEventFilter中丢出false,给子窗口处理,拖动效果和windows是一样的,先拖出一个虚线框,鼠标松开,重绘窗口,到此完美实现!没有连续拖动时候的闪烁问题。
接下来说说bug。
参照本帖中第二个参考贴中的例子,最开始设置鼠标图案的时候,并没有用
SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE)));
而是采用了 *result=HTLEFT这样,用HTLEFT后,就不需要ReleaseCapture,而可以直接调SendMessage,改一下最后一个参数,改成这样,就是最后一个参数直接传入坐标,和第二贴中一样。
SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_TOP, MAKELPARAM(point.x, point.y));
然后发现,qApp中传过来的result,居然为空,气炸,于是换成了SetCursor,测试通过。
顺带,还有按住标题移动,见代码
void A::mousePressEvent(QMouseEvent* event)
{
QPoint ptMouse = event->globalPos();
m_pointRelative = ptMouse - mapToGlobal(QPoint(0, 0));
//点住标题则拖拽
if (m_pointRelative.ry() < 150 )
{
if (ReleaseCapture())
SendMessage((HWND)this->winId(), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
QDialog::mousePressEvent(event);
}
void A::mouseDoubleClickEvent(QMouseEvent *event)
{
QPoint ptMouse = event->globalPos();
m_pointRelative = ptMouse - mapToGlobal(QPoint(0, 0));
//双击最大化
if (m_pointRelative.ry() < 150)
{
if (this->windowState() == Qt::MaximumSize)
{
showNormal();
}
else
{
showMaximized();
}
}
QDialog::mouseDoubleClickEvent(event);
}
这里也用了windows 的api,先拖出一个虚线框。心满意足,继续干活。
就是虚线框有点细,如果有人知道怎么改变虚线框粗细,请一定要告诉我!我是windows api小白。