基于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_())
关键实现说明
- 主窗口隐藏:在
MainWindowHide初始化时直接调用self.hide(),确保主窗口仅作为进度条初始化容器,始终不可见;关闭时遍历所有顶级窗口,清理所有进度条实例。 - 单个进度条拖拽合并:重写鼠标事件启动拖拽,通过QMimeData传递进度值;拖拽接收时创建标签页窗口,合并两个进度条并关闭原窗口。
- 标签页拆分逻辑:重写标签栏的
dropEvent,检测拖拽位置是否在窗口外部,若是则将标签拆分为独立进度条窗口;若拆分后仅剩一个标签,自动转为单个窗口。 - 细节优化:单个进度条使用无边框样式,拖拽时同步传递进度状态,所有窗口保持置顶避免被遮挡。
内容的提问来源于stack exchange,提问作者titan digitalsoft
相关产品推荐
相关产品推荐

