手把手教你写产品级QT项目(二)--- 自定义无边框、阴影,可拖拽、伸缩窗口

前言

我们今天实现一个无边框,带窗口阴影,可拖拽、伸缩的窗口。

第一步

去除qt窗口的边框、工具栏等属性。我们的窗口的类Window,继承于QWidget这个基础的控件类,然后直接在构造函数内一句函数搞定:

Window::Window(const QSize &initSize, QWidget *parent)
    : QWidget(parent)
{
	// 设置窗口大小
    resize(initSize);
	
	// 去除边框
    setWindowFlags(Qt::Window |Qt::FramelessWindowHint);
}

在主程序内直接show这个窗口类:

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    Window *mainWindow = new Window(QSize(994,650);
    mainWindow->show();

    return app.exec();
}

效果如下(桌面背景为白色):
在这里插入图片描述

第二步,给它套个阴影

Window::Window(const QSize &initSize, QWidget *parent)
    : QWidget(parent)
{
    resize(initSize);

    setWindowFlags(Qt::Window |Qt::FramelessWindowHint);

    HWND hwnd = reinterpret_cast<HWND>(winId());
    LONG style = static_cast<LONG>( WS_POPUP | WS_CAPTION | (true ? WS_MINIMIZEBOX : 0) | (true ? WS_MAXIMIZEBOX : 0) | WS_THICKFRAME | WS_CLIPCHILDREN );
    ::SetWindowLongPtr(hwnd, GWL_STYLE, style);

    const MARGINS shadow = {1, 1, 1, 1};
    DwmExtendFrameIntoClientArea(hwnd, &shadow);

}

效果如下:
在这里插入图片描述

第三步,加上实现可拖拽、伸缩效果

原理是重写nativeEvent函数,处理windows下的鼠标事件,其实也就是把窗口边界的事件转化为Windows的窗口事件

bool Window::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
	if (eventType != "windows_generic_MSG")
		return false;

	MSG* msg = reinterpret_cast<MSG*>(message);
	QWidget* widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
	if (!widget)
		return false;

	switch (msg->message)
    {
	case WM_NCCALCSIZE:
	{
		*result = 0;
		return true;
	}
	case WM_NCHITTEST:
	{
		const LONG borderWidth = 9;
		RECT winrect;
		::GetWindowRect(msg->hwnd, &winrect);
		long x = GET_X_LPARAM(msg->lParam);
		long y = GET_Y_LPARAM(msg->lParam);

		if (m_resizeable && m_freeSizeable)
		{
			// bottom left
			if (x >= winrect.left && x < winrect.left + borderWidth &&
					y < winrect.bottom && y >= winrect.bottom - borderWidth)
			{
				*result = HTBOTTOMLEFT;
				return true;
			}

			// bottom right
			if (x < winrect.right && x >= winrect.right - borderWidth &&
					y < winrect.bottom && y >= winrect.bottom - borderWidth)
			{
				*result = HTBOTTOMRIGHT;
				return true;
			}

			// top left
			if (x >= winrect.left && x < winrect.left + borderWidth &&
					y >= winrect.top && y < winrect.top + borderWidth)
			{
				*result = HTTOPLEFT;
				return true;
			}

			// top right
			if (x < winrect.right && x >= winrect.right - borderWidth &&
					y >= winrect.top && y < winrect.top + borderWidth)
			{
				*result = HTTOPRIGHT;
				return true;
			}

			// left
			if (x >= winrect.left && x < winrect.left + borderWidth)
			{
				*result = HTLEFT;
				return true;
			}

			// right
			if (x < winrect.right && x >= winrect.right - borderWidth)
			{
				*result = HTRIGHT;
				return true;
			}

			// bottom
			if (y < winrect.bottom && y >= winrect.bottom - borderWidth)
			{
				*result = HTBOTTOM;
				return true;
			}

			// top
			if (y >= winrect.top && y < winrect.top + borderWidth)
			{
				*result = HTTOP;
				return true;
			}
		}


		// 高分屏
		double dpr = this->devicePixelRatioF();
		QPoint pos = mapFromGlobal(QPoint(static_cast<int>(x/dpr),
										  static_cast<int>(y/dpr)));

		// 全区域移动,前提是窗口内没有子控件,有的话需要加入m_ignoreList
		if (m_fullAreaMoveable)
		{
			if (!rect().contains(pos)) return false;

			QWidget *curParent = this;
			while (1)
			{
				QWidget* child = curParent->childAt(pos);
				if (!child)
				{
					*result = HTCAPTION;
					return true;
				}
				else
				{
					// 当鼠标在m_ignoreList列表的窗口子控件内时才能拖动窗口
					if (m_ignoreList.contains(child))
					{
						curParent = child;
						continue;
					}
					else
						break;
				}
			}

			break;
		}


		// 标题栏移动
		if (!m_titleBar) return false;

		if (!m_titleBar->rect().contains(pos)) return false;
		QWidget* child = m_titleBar->childAt(pos);
		if (!child)
		{
			*result = HTCAPTION;
			return true;
		}

		return false;
	}

	case WM_GETMINMAXINFO:
	{
		// 最大化窗口部件位置修正
		if (::IsZoomed(msg->hwnd))
		{

			RECT frame = { 0, 0, 0, 0 };
			AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
			frame.left = abs(frame.left);
			frame.top = abs(frame.bottom);
			widget->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
		}
		else
		{
			widget->setContentsMargins(0, 0, 0, 0);
		}

		// 限定最小及最大窗口
		reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMinTrackSize.x = minimumWidth();
		reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMinTrackSize.y = minimumHeight();
		reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMaxTrackSize.x = maximumWidth();
		reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMaxTrackSize.y = maximumHeight();

		*result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
		return true;
	}
	default:
		break;
	}

	return QWidget::nativeEvent(eventType, message, result);
}

// 加入全局拖拽时的控件忽略列表
void Window::AddToMoveableIgnoreList(QWidget *widget)
{
	m_ignoreList.append(widget);
}

// 使能全局拖拽
void Window::SetFullAreaMoveable(bool enable)
{
	m_fullAreaMoveable = enable;
}

这样调用窗口

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    Window *mainWindow = new Window(QSize(994, 650));

    mainWindow->SetFullAreaMoveable(true);
    mainWindow->SetResizeable(true);

    mainWindow->show();

    return app.exec();
}

效果如下:
在这里插入图片描述

如有疑问欢迎交流

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一个跨平台的C++应用程序开发框架,具有良好的可扩展性和易于使用的特点。而CSDN是一个国内知名的技术社区,提供了大量的编程程和经验分享。 Qt项目实战CSDN可以是一个关于Qt开发程或者项目案例分享。在这个项目中,我会结合Qt的特点,通过撰一系列的技术文章或者视频程,详细介绍如何使用Qt进行应用程序的开发。 在这个项目中,我会从安装Qt开始,讲解Qt的基础知识,如界面设计、信号与槽机制、Qt对象模型等。然后,我会介绍Qt的高特性,比如网络编程、数据库操作、多线程等。通过这些文章或者程,读者可以系统地学习Qt的各个方面,从而快速掌握Qt开发技巧。 同时,为了更好地帮助读者理解和学习Qt,我会结合实际的项目案例,展示如何使用Qt进行实际应用的开发。具体案例可以涵盖不同的领域,比如一个简单的音乐播放器、一个图像处理应用、一个聊天程序等。通过这些案例,读者可以更好地理解Qt的实际应用场景,并且通过参考案例代码,快速上手Qt开发。 除了撰技术文章或者制作视频程,我还会积极参与到CSDN的技术社区中,回答读者的问题,与他们交流经验。这样可以进一步提升读者对于Qt开发的理解,并且根据读者的反馈,不断优化项目内容,使之更加符合读者的需求。 通过Qt项目实战CSDN,我希望能够帮助更多的开发者掌握Qt开发技巧,实现自己的应用想法。同时,也希望能够通过与读者的交流,不断提升自己的技术水平,共同促进技术的发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值