QT 解决设置FramelessWindowHint不可以再调整大小的问题

2,064 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情

在QT中,我们设置值 Qt::FramelessWindowHint 之后,就把边框隐藏掉了,这个时候,对应的,鼠标也不能对齐调整大小和移动位置了。 我们在日常使用过程中,要是要自定义标题栏,又得去这样干,所以,我们需要重新让鼠标事件得到监听。

其实本质上来说,我们只需要让鼠标重新处理对应的事件,就可以达到我们的目的。

解决鼠标移动窗口

现在的需求是:

  • 鼠标按下左键,光标变化
  • 鼠标在按下左键时移动,重新设置窗口的起始坐标
  • 鼠标松开左键,不再继续移动

在这里,需要重写三个虚函数 分别是:

  void mousePressEvent(QMouseEvent *event) override;
  void mouseReleaseEvent(QMouseEvent *event) override;
  void mouseMoveEvent(QMouseEvent *event) override;

重写函数的实现也比较简单,下列便是 (去掉了类名):

void mousePressEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton)
    {
      is_drag = true; // 用于记录是否按下
      drag = event->pos(); // 记录按下时的坐标
      setCursor(Qt::SizeAllCursor); // 设置光标
    }
}
void mouseReleaseEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton)
    {
      is_drag = false;
      setCursor(Qt::ArrowCursor); // 设置光标
    }
}
void mouseMoveEvent(QMouseEvent *event) {
  if (this->is_drag)
  {
    this->move(event->globalPos() - this->drag); // 移动窗口
  }
}

解决调整大小问题

在看调整大小问题之前,你得先明白监听的原理

在QT中,鼠标移动事件默认不监听,除非等你开启 setMouseTracking(true);

每次移动监听事件都会交给 mouseMoveEvent来做,对应的,会有一个event事件,在其中,我们可以拿到其鼠标当前的坐标点。

现在将窗口划分为9个区域。

分别是:


enum state_region {
  DEFAULT,
  TOP, BOTTOM, LEFT, RIGHT,
  LEFTTOP, LEFTBOTTOM,
  RIGHTTOP, RIGHTBOTTOM
};

image.png

对应着这九个区域

现在,每次移动事件触发时,就会去刷新我们的状态,看它位于哪一个区域内

假设现在的边框为 5 的大小,来计算:

image.png

(计算机中的y轴是相反的)

设边距为K,设左上角坐标(0,0),右下角坐标(n,m),当前坐标(x,y){LEFTTOPxK , yKLEFTxk , y(K,mK)LEFTBOTTOMxk , ymKRIGHTTOPxnK , yKRIGHTxnK , y(K,mk)RIGHTBOTTOMxnK , ymKTOPx(K,nK) , yKDEFAULTx(K,nK) , y(K,mK)BOTTOMx(K,nK) , ymK设边距为K, 设左上角坐标 (0, 0), 右下角坐标(n, m), 当前坐标(x, y) \\ \left\{ \begin{aligned} LEFTTOP & & x \leq K \ , \ y \leq K \\ LEFT & & x \leq k \ , \ y \in (K, m - K) \\ LEFTBOTTOM & & x \leq k \ , \ y \geq m - K \\ RIGHTTOP & & x \geq n - K \ , \ y \leq K\\ RIGHT & & x \geq n - K \ , \ y \in (K, m - k) \\ RIGHTBOTTOM & & x \geq n - K \ , \ y \geq m - K \\ TOP & & x \in (K, n - K) \ , \ y \leq K \\ DEFAULT & & x \in (K, n - K) \ , \ y \in (K, m - K) \\ BOTTOM & & x \in (K, n - K) \ , \ y \geq m - K \\ \end{aligned} \right.

计算出状态之后,然后去更新其鼠标光标之后,就可以调整移动了

设计调整大小有八种调整,其实是由四种调整组合而得到的

调整上方

image.png

调整上方是调整的整个矩形的高度,而上方的调整涉及到了矩形开始位置的 y轴的变化 所以,我们需要对新矩形的y轴进行调整

auto adjustTop = [&] {
    if (bottomRight.y() - point.y() <= this->minimumHeight())
      {
        newRect.setY(topLeft.y());
      }
    else
      {
        newRect.setY(point.y());
      }
  };

调整左方

image.png

调整左方调整的是整个矩形的宽度,而左方调整涉及到了矩形左上角的变化,所以要更改 x轴的位置

auto adjustLeft = [&] {
    if (bottomRight.x() - point.x() <= this->minimumWidth())
      {
        newRect.setLeft(topLeft.x());
      }
    else
      {
        newRect.setLeft(point.x());
      };
  };

调整下方

image.png

调整下方时,涉及到了高度的变化,可是并不涉及左上角变化,所以只用计算出新的高度即可

auto adjustBottom = [&] {
    newRect.setHeight(point.y() - topLeft.y());
  };

调整右方

image.png

调整左方时,不涉及左上角的变化,只用计算出新的宽度即可

auto adjustRight = [&] {
    newRect.setWidth(point.x() - topLeft.x());
  };

以上便是调整的大致逻辑

下面来看看代码(去掉了类名):


state_region refreshCursor(QPoint point)
{
  auto x = rect().width();
  auto y = rect().height();
  auto dx = point.x();
  auto dy = point.y();
  if (dx <= PADDING) // 左
    {
      if (dy <= PADDING) // 上
        {
          return state_region::LEFTTOP;
        }
      else if(dy >= y - PADDING) // 下
        {
          return state_region::LEFTBOTTOM;
        }
      else // 中
        {
          return state_region::LEFT;
        }
    }
  if (dx >= x - PADDING) // 右
    {
      if (dy <= PADDING) // 上
        {
          return state_region::RIGHTTOP;
        }
      else if(dy >= y - PADDING) // 下
        {
          return state_region::RIGHTBOTTOM;
        }
      else // 中
        {
          return state_region::RIGHT;
        }
    }

  // 中

  if (dy <= PADDING)
    {
      return state_region::TOP;
    }
  else if(dy >= y - PADDING)
    {
      return state_region::BOTTOM;
    }
  else
    {
      return state_region::DEFAULT;
    }
}

void setRegionCursor()
{
  switch (this->region_)
    {
    case state_region::TOP:
    case state_region::BOTTOM:
      setCursor(Qt::SizeVerCursor);
      break;

    case state_region::LEFT:
    case state_region::RIGHT:
      setCursor(Qt::SizeHorCursor);
      break;

    case state_region::LEFTBOTTOM:
    case state_region::RIGHTTOP:
      setCursor(Qt::SizeBDiagCursor);
      break;

    case state_region::LEFTTOP:
    case state_region::RIGHTBOTTOM:
      setCursor(Qt::SizeFDiagCursor);
      break;
    case state_region::DEFAULT:
      if (!this->is_drag)
        setCursor(Qt::ArrowCursor);
    }
}

void mousePressEvent(QMouseEvent *event)
{

  if (event->button() == Qt::LeftButton)
    {
      is_drag = true;
      drag = event->pos(); // 记录相对位置
      setCursor(Qt::SizeAllCursor);
      is_region = (region_ != state_region::DEFAULT);
    }

}

void mouseReleaseEvent(QMouseEvent *event)
{
  if (event->button() == Qt::LeftButton)
    {
      is_drag = false;
      is_region = false;
      setCursor(Qt::ArrowCursor);
    }
}

QRect adjustRegion(QPoint point)
{
  QRect rect = this->rect();
  QPoint topLeft = mapToGlobal(rect.topLeft());
  QPoint bottomRight = mapToGlobal(rect.bottomRight());

  qInfo() << topLeft << "x" << bottomRight;
  auto newRect = QRect(topLeft, bottomRight);

  auto adjustLeft = [&] {
      if (bottomRight.x() - point.x() <= this->minimumWidth())
        {
          newRect.setLeft(topLeft.x());
        }
      else
        {
          newRect.setLeft(point.x());
        };
    };
  auto adjustTop = [&] {
      if (bottomRight.y() - point.y() <= this->minimumHeight())
        {
          newRect.setY(topLeft.y());
        }
      else
        {
          newRect.setY(point.y());
        }
    };
  auto adjustRight = [&] {
      newRect.setWidth(point.x() - topLeft.x());
    };
  auto adjustBottom = [&] {
      newRect.setHeight(point.y() - topLeft.y());
    };

  switch (this->region_)
    {
    case state_region::LEFT:
      adjustLeft();
      break;
    case state_region::RIGHT:
      adjustRight();
      break;
    case state_region::TOP:
      adjustTop();
      break;
    case state_region::BOTTOM:
      adjustBottom();
      break;
    case state_region::LEFTTOP:
      adjustLeft();
      adjustTop();
      break;
    case state_region::LEFTBOTTOM:
      adjustLeft();
      adjustBottom();
      break;
    case state_region::RIGHTTOP:
      adjustRight();
      adjustTop();
      break;
    case state_region::RIGHTBOTTOM:
      adjustRight();
      adjustBottom();
      break;
    default:
      break;
    }

  return newRect;
}

void mouseMoveEvent(QMouseEvent *event)
{
  region_ = refreshCursor(event->pos());
  setRegionCursor();

  if (this->is_drag)
    {

      if (region_ != state_region::DEFAULT)
        {
          this->setGeometry(adjustRegion(event->globalPos()));
        }
      else
        {
          if (!this->is_region)
            this->move(event->globalPos() - this->drag);
        }
    }
}