如何在PySide2中实现可拖拽、带样式的无标题栏现代搜索栏
PySide2 实现Edge风格无标题栏自定义搜索栏方案
实现效果参考

核心实现逻辑
- 无边框窗口配置:通过设置窗口标志移除系统自带标题栏,同时保留原生窗口调整大小、系统菜单、最小化最大化能力,避免纯
FramelessWindowHint带来的窗口功能缺失 - 自定义顶部栏:顶部栏同时承载搜索输入框、窗口控制按钮(最小化/最大化/关闭),同时作为窗口拖拽的响应区域
- 拖拽逻辑实现:重写顶部栏鼠标事件,记录鼠标按下时的位置偏移,移动时同步更新窗口坐标;自动排除输入框、按钮等交互控件的拖拽触发,避免影响正常操作
- 全QSS自定义样式:搜索栏圆角、背景色、交互态(hover/focus)、按钮样式全部通过样式表控制,可自由调整对齐Edge设计风格
- 搜索交互:输入框支持回车触发加载,自动识别网址/搜索关键词,分别跳转对应地址或调用搜索引擎搜索
完整实现代码
import sys import ctypes from ctypes.wintypes import MSG from PySide2.QtGui import * from PySide2.QtCore import * from PySide2.QtWidgets import * from PySide2.QtWebEngine import * from PySide2.QtWebEngineWidgets import * # 自定义可拖拽标题栏/搜索栏 class EdgeStyleTitleBar(QWidget): def __init__(self, parent=None): super().__init__(parent) self.parent_window = parent self.setFixedHeight(48) self.drag_pos = None self.maximized = False # 顶部栏布局 self.layout = QHBoxLayout(self) self.layout.setContentsMargins(12, 8, 8, 8) self.layout.setSpacing(8) # 搜索/地址输入框 self.search_input = QLineEdit() self.search_input.setPlaceholderText("搜索或输入网址") self.search_input.returnPressed.connect(self.load_url) self.search_input.setFixedHeight(32) # 窗口控制按钮 self.min_btn = QPushButton("—") self.max_btn = QPushButton("□") self.close_btn = QPushButton("✕") for btn in [self.min_btn, self.max_btn, self.close_btn]: btn.setFixedSize(46, 32) btn.setFlat(True) self.min_btn.clicked.connect(self.parent_window.showMinimized) self.max_btn.clicked.connect(self.toggle_max) self.close_btn.clicked.connect(self.parent_window.close) # 控件加入布局 self.layout.addWidget(self.search_input) self.layout.addWidget(self.min_btn) self.layout.addWidget(self.max_btn) self.layout.addWidget(self.close_btn) # 应用QSS样式,对齐Edge风格 self.setStyleSheet(""" QWidget { background-color: #f3f3f3; } QLineEdit { background-color: #e8e8e8; border-radius: 16px; padding: 0 16px; font-size: 14px; border: none; } QLineEdit:hover { background-color: #dfdfdf; } QLineEdit:focus { background-color: #ffffff; outline: 2px solid #0078d4; } QPushButton { font-size: 12px; color: #333333; border-radius: 4px; } QPushButton:hover { background-color: #dfdfdf; } QPushButton#close_btn:hover { background-color: #e81123; color: #ffffff; } """) self.close_btn.setObjectName("close_btn") def toggle_max(self): if self.maximized: self.parent_window.showNormal() self.max_btn.setText("□") self.maximized = False else: self.parent_window.showMaximized() self.max_btn.setText("❐") self.maximized = True def load_url(self): text = self.search_input.text().strip() # 简单判断是否为网址,否则走DuckDuckGo搜索 if "." in text and " " not in text: if not text.startswith(("http://", "https://")): text = "https://" + text self.parent_window.browser.webview.load(QUrl(text)) else: search_url = f"https://duckduckgo.com/?q={text}" self.parent_window.browser.webview.load(QUrl(search_url)) # 窗口拖拽逻辑 def mousePressEvent(self, event): if event.button() == Qt.LeftButton and not self.childAt(event.pos()): self.drag_pos = event.globalPos() - self.parent_window.frameGeometry().topLeft() event.accept() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton and self.drag_pos and not self.childAt(event.pos()): # 最大化状态下拖拽先还原窗口 if self.maximized: self.toggle_max() self.drag_pos = event.globalPos() - QPoint(self.parent_window.width()//2, 10) self.parent_window.move(event.globalPos() - self.drag_pos) event.accept() def mouseReleaseEvent(self, event): self.drag_pos = None class BrowserEngine(QWidget): def __init__(self, *args, **kwargs): super(BrowserEngine, self).__init__() self.webview = QWebEngineView(self) self.webview.load(QUrl("https://www.duckduckgo.com")) self.setMinimumSize(800, 600) self.mainLayout = QVBoxLayout() self.mainLayout.addWidget(self.webview) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.mainLayout) class BrowserWindow(QMainWindow): def __init__(self, *args, **kwargs): super(BrowserWindow, self).__init__() # 无边框窗口配置,保留原生窗口能力 self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint | Qt.WindowMaximizeButtonHint) self.setAttribute(Qt.WA_TranslucentBackground, False) self.browser = BrowserEngine() # 主容器布局 self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.main_layout = QVBoxLayout(self.central_widget) self.main_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.setSpacing(0) # 加入自定义标题栏和浏览器视图 self.title_bar = EdgeStyleTitleBar(self) self.main_layout.addWidget(self.title_bar) self.main_layout.addWidget(self.browser) # 同步页面标题到窗口 self.browser.webview.titleChanged.connect(self.setWindowTitle) # 处理Windows平台原生窗口消息,保留边缘调整大小能力 def nativeEvent(self, eventType, message): if eventType == b"windows_generic_MSG": msg = ctypes.cast(int(message), ctypes.POINTER(MSG)).contents WM_NCCALCSIZE = 0x0083 if msg.message == WM_NCCALCSIZE: return True, 0 return super().nativeEvent(eventType, message) if __name__ == '__main__': app = QApplication(sys.argv) window = BrowserWindow() window.show() sys.exit(app.exec_())
注意事项
- Windows平台下上述代码可直接保留窗口边缘拖拽调整大小的能力,macOS/Linux平台如果需要边缘resize可自行补充边缘检测逻辑
- 所有样式参数都在
EdgeStyleTitleBar的setStyleSheet部分,可直接修改颜色、圆角、尺寸参数调整外观,不需要修改业务逻辑 - 可根据需求在搜索栏两侧补充前进/后退/刷新/收藏等功能按钮,布局逻辑和现有控制按钮一致
- 当前网址判断逻辑为简单实现,可替换为正则表达式提升识别准确率
内容的提问来源于stack exchange,提问作者user16435384
相关产品推荐
相关产品推荐

