You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PySide2中实现可拖拽、带样式的无标题栏现代搜索栏

PySide2 实现Edge风格无标题栏自定义搜索栏方案

实现效果参考

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:57:17