1 自定义无边框窗体

发布于:2022-12-24 ⋅ 阅读:(484) ⋅ 点赞:(0)

1 开发测试环境

        

        IDE:VS2017

        Qt:5.14.2 x64

        操作系统:Win10、Win11

        实现效果

2 实现

        网上搜索Qt窗口的无边框化实现方案有很多,但从实现的难易程度,以及效果上来说,最优

的还是通过Windows消息来实现,即重新实现Qt窗口的nativeEvent方法。该方案不仅实现了无边

框窗口,还保留了窗口的拖动、贴边等系统特性,以下为实现代码(以QWidget为例,

QMainWindow、QDialog可以平移过去):

#include "ui/frameless_widget.h"

#include <windows.h>
#include <windowsx.h>

#pragma comment (lib,"user32.lib")

FramelessWidget::FramelessWidget(QWidget *parent) : QWidget(parent)
, movale_(true)
, resizable_(true)
, border_width_(5)
, title_bar_(Q_NULLPTR)
, just_maximized_(false)
{
	this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint);

	this->installEventFilter(this);

	HWND hwnd = (HWND)this->winId();
	DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
	::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
}

FramelessWidget::~FramelessWidget()
{
}

void FramelessWidget::setTitleBar(QWidget *title_bar)
{
	title_bar_ = title_bar;
	title_bar_->installEventFilter(this);
}

void FramelessWidget::showEvent(QShowEvent *event)
{
	this->setAttribute(Qt::WA_Mapped);
	QWidget::showEvent(event);
}

bool FramelessWidget::eventFilter(QObject *watched, QEvent *event)
{
	if (watched == this)
	{
		if (event->type() == QEvent::WindowStateChange)
		{
			if (this->windowState() == Qt::WindowNoState)
			{
				movale_ = true;
				resizable_ = true;
			}
			else
			{
				movale_ = false;
				resizable_ = false;
			}

			emit signalWindowStateChanged(!movale_);
		}
	}

	return QWidget::eventFilter(watched, event);
}

bool FramelessWidget::nativeEvent(const QByteArray &event_type, void *message, long *result)
{
	MSG *msg = (MSG *)message;
	switch (msg->message)
	{
	case WM_NCCALCSIZE:
	{
		//this kills the window frame and title bar we added with WS_THICKFRAME and WS_CAPTION
		*result = 0;
		return true;
	}
	case WM_NCHITTEST:
	{
		*result = 0;

		const LONG border_width = border_width_;
		RECT winrect;
		GetWindowRect(HWND(winId()), &winrect);

		long x = GET_X_LPARAM(msg->lParam);
		long y = GET_Y_LPARAM(msg->lParam);

		bool resize_width = minimumWidth() != maximumWidth();
		bool resize_height = minimumHeight() != maximumHeight();

		if (resize_width)
		{
			//left border
			if (x >= winrect.left && x < winrect.left + border_width)
			{
				*result = HTLEFT;
			}
			//right border
			if (x < winrect.right && x >= winrect.right - border_width)
			{
				*result = HTRIGHT;
			}
		}
		if (resize_height)
		{
			//bottom border
			if (y < winrect.bottom && y >= winrect.bottom - border_width)
			{
				*result = HTBOTTOM;
			}
			//top border
			if (y >= winrect.top && y < winrect.top + border_width)
			{
				*result = HTTOP;
			}
		}
		if (resize_width && resize_height)
		{
			//bottom left corner
			if (x >= winrect.left && x < winrect.left + border_width &&
				y < winrect.bottom && y >= winrect.bottom - border_width)
			{
				*result = HTBOTTOMLEFT;
			}
			//bottom right corner
			if (x < winrect.right && x >= winrect.right - border_width &&
				y < winrect.bottom && y >= winrect.bottom - border_width)
			{
				*result = HTBOTTOMRIGHT;
			}
			//top left corner
			if (x >= winrect.left && x < winrect.left + border_width &&
				y >= winrect.top && y < winrect.top + border_width)
			{
				*result = HTTOPLEFT;
			}
			//top right corner
			if (x < winrect.right && x >= winrect.right - border_width &&
				y >= winrect.top && y < winrect.top + border_width)
			{
				*result = HTTOPRIGHT;
			}
		}

		//*result still equals 0, that means the cursor locate OUTSIDE the frame area
		//but it may locate in titlebar area
		if (*result == 0)
		{
			if (!title_bar_) return false;
			QPoint pos = title_bar_->mapFromGlobal(QPoint(x, y));
			if (!title_bar_->rect().contains(pos)) return false;
			QWidget* child = title_bar_->childAt(pos);
			if (!child)
			{
				*result = HTCAPTION;
				return true;
			}
			else {
				/*if (_lpWhiteList.contains(child))
				{
				*result = HTCAPTION;
				return true;
				}*/
				return false;
			}
		}
		return true;
	} //end case WM_NCHITTEST
	case WM_GETMINMAXINFO:
	{
		if (::IsZoomed(msg->hwnd)) {
			RECT frame = { 0, 0, 0, 0 };
			AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);

			//record frame area data
			frames_.setLeft(abs(frame.left));
			frames_.setTop(abs(frame.bottom));
			frames_.setRight(abs(frame.right));
			frames_.setBottom(abs(frame.bottom));

			QWidget::setContentsMargins(frames_.left() + margins_.left(), \
				frames_.top() + margins_.top(), \
				frames_.right() + margins_.right(), \
				frames_.bottom() + margins_.bottom());
			just_maximized_ = true;
		}
		else {
			if (just_maximized_)
			{
				QWidget::setContentsMargins(margins_);
				//after window back to normal size from maximized state
				//a twinkle will happen, to avoid this twinkle
				//repaint() is important used just before the window back to normal
				repaint();
				frames_ = QMargins();
				just_maximized_ = false;
			}
		}
		return false;
	}
	default:
		return QWidget::nativeEvent(event_type, message, result);
	}
}

3 扩展

        以上,已经实现一个无边框窗口,可以拖动边框进行放大、缩小,以及通过点击任务栏标签

进行最小化和还原。如果想进一步实现窗口的拖动以及贴边特性,则需要实现一个窗口标题栏,并

通过setTitleBar进行设置。为了方便扩展,我们提供一个中间适配窗口,继承自

FramelessWidget,实现基础的标题栏,并对外提供设置标题、隐藏最小化、最大化按钮等接口,

当软件有多个无边框窗口需求时,可以继承适配窗口,并通过接口定制标题栏显示内容。

#include "ui/base_widget.h"

BaseWidget::BaseWidget(QString title, QWidget *parent) : FramelessWidget(parent)
, layout_(Q_NULLPTR)
, title_bar_(Q_NULLPTR)
, title_bar_layout_(Q_NULLPTR)
, sys_icon_(Q_NULLPTR)
, title_(Q_NULLPTR)
, right_layout1_(Q_NULLPTR)
, right_layout2_(Q_NULLPTR)
, minimize_button_(Q_NULLPTR)
, switch_button_(Q_NULLPTR)
, close_button_(Q_NULLPTR)
, content_widget_(Q_NULLPTR)
, status_bar_(Q_NULLPTR)
, message_(Q_NULLPTR)
{
	initUI();

	title_->setText(title);
	this->setTitleBar(title_bar_);

	connect(this, &BaseWidget::signalWindowStateChanged, this, &BaseWidget::slotWindowStateChanged);

	connect(minimize_button_, &QToolButton::clicked, this, &BaseWidget::slotMinimizeButtonClicked);
	connect(switch_button_, &QToolButton::clicked, this, &BaseWidget::slotMaximizeButtonClicked);
	connect(close_button_, &QToolButton::clicked, this, &BaseWidget::slotCloseButtonClicked);
}

BaseWidget::~BaseWidget()
{
}

void BaseWidget::initUI()
{
	// 0
	layout_ = new QVBoxLayout;
	layout_->setContentsMargins(0, 0, 0, 0);
	layout_->setSpacing(0);

	// 1
	title_bar_ = new QWidget(this);
	title_bar_->setObjectName(QString("objTitleBar"));
	title_bar_->setFixedHeight(42);

	title_bar_layout_ = new QHBoxLayout;
	title_bar_layout_->setContentsMargins(5, 0, 0, 0);
	title_bar_layout_->setSpacing(0);

	sys_icon_ = new QLabel(title_bar_);

	title_ = new QLabel(title_bar_);
	title_->setObjectName(QString("objTitle"));

	right_layout1_ = new QVBoxLayout;
	right_layout1_->setContentsMargins(1, 1, 1, 1);

	right_layout2_ = new QHBoxLayout;
	right_layout2_->setContentsMargins(0, 0, 0, 0);
	right_layout2_->setSpacing(5);

	minimize_button_ = new QToolButton(title_bar_);
	minimize_button_->setObjectName(QString("objMinimizeButton32x32"));

	switch_button_ = new QToolButton(title_bar_);
	switch_button_->setObjectName(QString("objRestoreButton32x32"));

	close_button_ = new QToolButton(title_bar_);
	close_button_->setObjectName(QString("objCloseButton32x32"));

	right_layout2_->addWidget(minimize_button_);
	right_layout2_->addWidget(switch_button_);
	right_layout2_->addWidget(close_button_);

	right_layout1_->addLayout(right_layout2_);
	right_layout1_->addStretch();

	title_bar_layout_->addWidget(sys_icon_);
	title_bar_layout_->addStretch();
	title_bar_layout_->addWidget(title_);
	title_bar_layout_->addStretch();
	title_bar_layout_->addLayout(right_layout1_);

	title_bar_->setLayout(title_bar_layout_);

	content_widget_ = new QWidget(this);
	content_widget_->setObjectName("objContentWidget");

	status_bar_ = new QStatusBar(this);

	message_ = new QLabel(status_bar_);

	message_->setMinimumSize(message_->sizeHint());
	message_->setAlignment(Qt::AlignVCenter);
	status_bar_->addWidget(message_);

	layout_->addWidget(title_bar_);
	layout_->addWidget(content_widget_);
	layout_->addWidget(status_bar_);

	status_bar_->setMaximumHeight(30);
	//status_bar_->showMessage(QStringLiteral("123"));

	this->setLayout(layout_);
}

void BaseWidget::slotWindowStateChanged(bool max)
{
	if (max)
	{
		switch_button_->setObjectName(QString("objRestoreButton24x24"));
		switch_button_->setStyle(switch_button_->style());
	}
	else
	{
		switch_button_->setObjectName(QString("objMaximizeButton24x24"));
		switch_button_->setStyle(switch_button_->style());
	}
}

void BaseWidget::slotMinimizeButtonClicked()
{
	this->showMinimized();
}

void BaseWidget::slotMaximizeButtonClicked()
{
	if (this->isMaximized())
	{
		this->showNormal();
	}
	else
	{
		this->showMaximized();
	}
}

void BaseWidget::slotCloseButtonClicked()
{
	this->close();
}

完整代码

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到