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

基于PyQt5实现类Chrome标签拖拽的进度条合并与拆分需求

实现可拖拽合并/拆分的进度条组件(基于PyQt5)

需求清单

  • 拖拽两个ProgressBar Widget到彼此区域时,合并为一个带标签的Widget
  • MainWindowHide窗口始终保持隐藏,仅用于进度条初始化
  • 支持类似Chrome标签页的拖拽操作:标签可拖出拆分为独立窗口,外部进度条可拖入合并到标签组

完整实现代码

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QTabWidget, QVBoxLayout

class Widget_Progressbar(QtWidgets.QWidget):
    def __init__(self, parent=None, progress_value=50):
        super().__init__(parent)
        self.setWindowTitle("Progress Bar")
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint)
        self.setGeometry(500, 500, 680, 80)
        self.progress_value = progress_value
        
        self._init_ui()
        self._setup_drag_drop()

    def _init_ui(self):
        self.progressBar = QtWidgets.QProgressBar(self)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHeightForWidth(self.progressBar.sizePolicy().hasHeightForWidth())
        self.progressBar.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setPointSize(8)
        self.progressBar.setFont(font)
        self.progressBar.setOrientation(QtCore.Qt.Horizontal)
        self.progressBar.setTextDirection(QtWidgets.QProgressBar.TopToBottom)
        self.progressBar.setAlignment(QtCore.Qt.AlignCenter)
        self.progressBar.setValue(self.progress_value)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.progressBar)
        self.setLayout(layout)

    def _setup_drag_drop(self):
        self.setAcceptDrops(True)
        self.drag_start_pos = None

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.drag_start_pos = event.globalPos() - self.frameGeometry().topLeft()
            event.accept()
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if (event.buttons() & QtCore.Qt.LeftButton) and self.drag_start_pos:
            drag = QtGui.QDrag(self)
            mime_data = QtCore.QMimeData()
            mime_data.setData("application/progressbar", QtCore.QByteArray(str(self.progress_value).encode()))
            mime_data.setData("window/type", b"single")
            drag.setMimeData(mime_data)
            pixmap = self.grab()
            drag.setPixmap(pixmap)
            drag.setHotSpot(event.pos())
            drag.exec_(QtCore.Qt.MoveAction)
            self.drag_start_pos = None
        super().mouseMoveEvent(event)

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat("application/progressbar"):
            event.acceptProposedAction()
        super().dragEnterEvent(event)

    def dropEvent(self, event):
        if event.mimeData().hasFormat("application/progressbar"):
            tab_window = ProgressBarTabWidget()
            tab_window.add_progress_bar(self.progress_value)
            progress_val = int(event.mimeData().data("application/progressbar").data().decode())
            tab_window.add_progress_bar(progress_val)
            tab_window.move(self.pos())
            tab_window.show()
            self.close()
            source = event.source()
            if source and isinstance(source, Widget_Progressbar):
                source.close()
            event.acceptProposedAction()
        super().dropEvent(event)

class ProgressBarTabWidget(QTabWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Combined Progress Bars")
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        self.setGeometry(500, 500, 680, 100)
        self._setup_drag_drop()

    def _setup_drag_drop(self):
        self.setAcceptDrops(True)
        self.tabBar().setMovable(True)
        self.tabBar().setAcceptDrops(True)
        self.tabBar().dropEvent = self.tab_drop_event

    def add_progress_bar(self, progress_value=50):
        progress_widget = Widget_Progressbar(progress_value=progress_value)
        progress_widget.setWindowFlags(QtCore.Qt.Widget)
        tab_index = self.addTab(progress_widget, f"Progress {self.count() + 1}")
        self.setCurrentIndex(tab_index)

    def tab_drop_event(self, event):
        if not self.rect().contains(event.pos()):
            tab_index = self.tabBar().tabAt(event.pos())
            if tab_index != -1:
                progress_widget = self.widget(tab_index)
                progress_value = progress_widget.progress_value
                new_window = Widget_Progressbar(progress_value=progress_value)
                new_window.move(event.globalPos())
                new_window.show()
                self.removeTab(tab_index)
                if self.count() == 1:
                    remaining_widget = self.widget(0)
                    remaining_value = remaining_widget.progress_value
                    remaining_window = Widget_Progressbar(progress_value=remaining_value)
                    remaining_window.move(self.pos())
                    remaining_window.show()
                    self.close()
            event.accept()
        else:
            super().tabBar().dropEvent(event)

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat("application/progressbar"):
            event.acceptProposedAction()
        super().dragEnterEvent(event)

    def dropEvent(self, event):
        if event.mimeData().hasFormat("application/progressbar"):
            progress_val = int(event.mimeData().data("application/progressbar").data().decode())
            self.add_progress_bar(progress_val)
            source = event.source()
            if source and isinstance(source, Widget_Progressbar):
                source.close()
            event.acceptProposedAction()
        super().dropEvent(event)

class MainWindowHide(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Hide Main Window")
        self.setGeometry(100, 100, 800, 600)
        self.hide()
        
        self.widgetprogressbar = Widget_Progressbar()
        self.widgetprogressbar.move(300, 300)
        self.widgetprogressbar.show()

        self.widgetprogressbar2 = Widget_Progressbar()
        self.widgetprogressbar2.move(300, 400)
        self.widgetprogressbar2.show()

    def closeEvent(self, event):
        for window in QtWidgets.QApplication.topLevelWidgets():
            if isinstance(window, (Widget_Progressbar, ProgressBarTabWidget)):
                window.close()
        event.accept()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = MainWindowHide()
    sys.exit(app.exec_())

关键实现说明

  1. 主窗口隐藏:在MainWindowHide初始化时直接调用self.hide(),确保主窗口仅作为进度条初始化容器,始终不可见;关闭时遍历所有顶级窗口,清理所有进度条实例。
  2. 单个进度条拖拽合并:重写鼠标事件启动拖拽,通过QMimeData传递进度值;拖拽接收时创建标签页窗口,合并两个进度条并关闭原窗口。
  3. 标签页拆分逻辑:重写标签栏的dropEvent,检测拖拽位置是否在窗口外部,若是则将标签拆分为独立进度条窗口;若拆分后仅剩一个标签,自动转为单个窗口。
  4. 细节优化:单个进度条使用无边框样式,拖拽时同步传递进度状态,所有窗口保持置顶避免被遮挡。

内容的提问来源于stack exchange,提问作者titan digitalsoft

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:38:18