PyQt6 / PySide 6 实现可拖拽的多标签页 web 浏览器【1】(有 Bug)

embedded/2024/9/25 10:34:19/

声明:

本项目代码来自以下两个项目

PyQt 5 / PySide 2 实现 QTabWidget 的拖入拖出功能
https://github.com/akihito-takeuchi/qt-draggable-tab-widget 
SimPyWeb X —— 使用PyQt5以及QWebEngineView构建网页浏览器

Bug:

存在很多问题:

1. 新拖拽的窗口无法新建标签页;
2. 旧窗口无法关闭;

......

代码:

main.py

python">from PySide6.QtCore import QUrl, QSize, QTimer
from PySide6.QtGui import QIcon, QPixmap, QAction
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWidgets import QToolBar, QLineEdit, QProgressBar, QLabel, QMainWindow, QTabWidget, QStatusBar, QWidget
from PySide6 import QtWidgets, QtGui, QtCore
from typing_extensions import LiteralSignal = QtCore.Signal
Slot = QtCore.Slotclass TabInfo:def __init__(self, widget=None, text=None, icon=None,tool_tip=None, whats_this=None):self.widget = widgetself.text = textself.icon = iconself.tool_tip = tool_tipself.whats_this = whats_thisclass DraggableTabWidget(QtWidgets.QTabWidget):tab_widget_instances_ = []def __init__(self, parent=None):super().__init__(parent)tab_bar = DraggableTabBar(self)self.setTabBar(tab_bar)tab_bar.createWindowRequested.connect(self.createNewWindow)self.setMovable(True)self.setTabsClosable(True)DraggableTabWidget.tab_widget_instances_.append(self)def event(self, event):if event.type() == QtCore.QEvent.Type.DeferredDelete:DraggableTabWidget.tab_widget_instances_.remove(self)return super().event(event)@Slot(QtCore.QRect, TabInfo)def createNewWindow(self, win_rect, tab_info):new_window = BrowserWindow(mode="push")new_window.tabs.addTab(tab_info.widget,tab_info.icon,tab_info.text)new_window.tabs.setTabToolTip(0, tab_info.tool_tip)new_window.tabs.setTabWhatsThis(0, tab_info.whats_this)new_window.show()new_window.setGeometry(win_rect)return new_windowclass DraggableTabBar(QtWidgets.QTabBar):createWindowRequested = Signal(QtCore.QRect, TabInfo)initializing_drag_ = Falsedrag_tab_info_ = TabInfo()dragging_widget_ = Nonedef __init__(self, parent=None):super().__init__(parent)self.click_point = QtCore.QPoint()self.can_start_drag = Falseself.a = 0def mousePressEvent(self, event):cls = DraggableTabBarif event.button() == QtCore.Qt.MouseButton.LeftButton:current_index = self.tabAt(event.pos())parent = self.parent()parent.setCurrentIndex(current_index)current_widget = parent.currentWidget()cls.drag_tab_info_ = TabInfo(current_widget, self.tabText(current_index),self.tabIcon(current_index), self.tabToolTip(current_index),self.tabWhatsThis(current_index))cls.dragging_widget_ = Noneself.click_point = event.pos()self.can_start_drag = Falseself.grabMouse()super().mousePressEvent(event)def mouseReleaseEvent(self, event):cls = DraggableTabBarif event.button() == QtCore.Qt.MouseButton.LeftButton:if cls.initializing_drag_:if self.parent().indexOf(cls.drag_tab_info_.widget) <= 0:cls.dragging_widget_ = cls.drag_tab_info_.widgetcls.dragging_widget_.setParent(None)cls.dragging_widget_.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)else:cls.dragging_widget_ = self.window()cls.initializing_drag_ = Falsecls.dragging_widget_.window().raise_()else:if cls.dragging_widget_:win_rect = cls.dragging_widget_.geometry()win_rect.moveTo(event.globalPos())idx = self.parent().indexOf(cls.drag_tab_info_.widget)if idx >= 0:self.parent().removeTab(idx)self.createWindowRequested.emit(win_rect, cls.drag_tab_info_)self.destroyUnnecessaryWindow()cls.dragging_widget_ = Nonecls.drag_tab_info_ = TabInfo()self.releaseMouse()self.click_point = QtCore.QPoint()self.can_start_drag = Falsesuper().mouseReleaseEvent(event)def mouseMoveEvent(self, event):cls = DraggableTabBarif cls.drag_tab_info_.widget is None:returnif not self.can_start_drag:moved_length = (event.pos() - self.click_point).manhattanLength()self.can_start_drag = moved_length > QtWidgets.QApplication.startDragDistance()if cls.dragging_widget_:for bar_inst in cls._tabBarInstances():bar_region = bar_inst.visibleRegion()bar_region.translate(bar_inst.mapToGlobal(QtCore.QPoint(0, 0)))if bar_region.contains(event.globalPos()):if (bar_inst == self):self.startTabMove()event.accept()returnelse:self.releaseMouse()bar_inst.grabMouse()event.accept()returnwidget_rect = self.geometry()widget_rect.moveTo(0, 0)if widget_rect.contains(event.pos()):super().mouseMoveEvent(event)elif cls.dragging_widget_ is None and self.can_start_drag:# start draggingself.startDrag()event.accept()returnif cls.dragging_widget_:cls.dragging_widget_.move(event.globalPos() + QtCore.QPoint(1, 1))cls.dragging_widget_.show()def startDrag(self):cls = DraggableTabBarif self.count() > 1:parent = self.parent()idx = parent.indexOf(cls.drag_tab_info_.widget)parent.removeTab(idx)cls.drag_tab_info_.widget.setParent(None)cls.dragging_widget_ = Nonecls.initializing_drag_ = Truerelease_event = self.createMouseEvent(QtCore.QEvent.Type.MouseButtonRelease,self.mapFromGlobal(QtGui.QCursor.pos()))QtWidgets.QApplication.postEvent(self, release_event)def createMouseEvent(self, event_type, pos=QtCore.QPoint()):if pos.isNull():global_pos = QtGui.QCursor.pos()else:global_pos = self.mapToGlobal(pos)modifiers = QtWidgets.QApplication.keyboardModifiers()event = QtGui.QMouseEvent(event_type, pos, global_pos,QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.LeftButton, modifiers)return eventdef startTabMove(self):cls = DraggableTabBarglobal_pos = QtGui.QCursor.pos()pos = self.mapFromGlobal(global_pos)if cls.drag_tab_info_.widget.parent() is not None:parent = cls.drag_tab_info_.widget.parent().parent()idx = parent.indexOf(cls.drag_tab_info_.widget)parent.removeTab(idx)parent.window().hide()idx = self.tabAt(pos)self.insertCurrentTabInfo(idx)cls.dragging_widget_ = Nonecls.drag_tab_info_ = TabInfo()press_event = self.createMouseEvent(QtCore.QEvent.Type.MouseButtonPress, self.tabRect(idx).center())QtWidgets.QApplication.postEvent(self, press_event)self.destroyUnnecessaryWindow()self.window().raise_()def destroyUnnecessaryWindow(self):cls = DraggableTabBarfor bar_inst in cls._tabBarInstances():if bar_inst.count() == 0 \and (not bar_inst.isVisible() or bar_inst.parent().parent() is None):bar_inst.deleteLater()bar_inst.parent().parent().close()def insertCurrentTabInfo(self, idx):cls = DraggableTabBarparent = self.parent()parent.insertTab(idx,cls.drag_tab_info_.widget,cls.drag_tab_info_.icon,cls.drag_tab_info_.text)parent.setTabToolTip(idx, cls.drag_tab_info_.tool_tip)parent.setTabWhatsThis(idx, cls.drag_tab_info_.whats_this)parent.setCurrentWidget(cls.drag_tab_info_.widget)@classmethoddef _tabBarInstances(cls):return [w for w in QtWidgets.QApplication.allWidgets() if w.__class__ == cls]class BrowserEngineView(QWebEngineView):tabs = []def __init__(self, Main, parent=None):super(BrowserEngineView, self).__init__(parent)self.mainWindow = Maindef createWindow(self, QWebPage_WebWindowType):webview = BrowserEngineView(self.mainWindow)tab = BrowserTab(self.mainWindow)tab.browser = webviewtab.setCentralWidget(tab.browser)self.tabs.append(tab)self.mainWindow.add_new_tab(tab)return webviewclass BrowserTab(QMainWindow):def __init__(self, Main, parent=None):super(BrowserTab, self).__init__(parent)self.mainWindow = Mainself.browser = BrowserEngineView(self.mainWindow)self.browser.load(QUrl("https://cn.bing.com"))self.setCentralWidget(self.browser)self.navigation_bar = QToolBar('Navigation')self.navigation_bar.setIconSize(QSize(24, 24))self.navigation_bar.setMovable(False)self.addToolBar(self.navigation_bar)self.status_bar = QStatusBar()self.setStatusBar(self.status_bar)self.back_button = QAction(QIcon('Assets/back.png'), '后退', self)self.next_button = QAction(QIcon('Assets/forward.png'), '前进', self)self.stop_button = QAction(QIcon('Assets/stop.png'), '停止', self)self.refresh_button = QAction(QIcon('Assets/refresh.png'), '刷新', self)self.home_button = QAction(QIcon('Assets/home.png'), '主页', self)self.enter_button = QAction(QIcon('Assets/enter.png'), '转到', self)self.add_button = QAction(QIcon('Assets/new.png'), '新建标签页', self)self.ssl_label1 = QLabel(self)self.ssl_label2 = QLabel(self)self.url_text_bar = QLineEdit(self)self.url_text_bar.setMinimumWidth(300)self.progress_bar = QProgressBar()self.progress_bar.setMaximumWidth(120)self.set_button = QAction(QIcon('Assets/setting.png'), '设置', self)self.navigation_bar.addAction(self.back_button)self.navigation_bar.addAction(self.next_button)self.navigation_bar.addAction(self.stop_button)self.navigation_bar.addAction(self.refresh_button)self.navigation_bar.addAction(self.home_button)self.navigation_bar.addAction(self.add_button)self.navigation_bar.addSeparator()self.navigation_bar.addWidget(self.ssl_label1)self.navigation_bar.addWidget(self.ssl_label2)self.navigation_bar.addWidget(self.url_text_bar)self.navigation_bar.addAction(self.enter_button)self.navigation_bar.addSeparator()self.navigation_bar.addWidget(self.progress_bar)self.navigation_bar.addAction(self.set_button)self.status_icon = QLabel()self.status_icon.setScaledContents(True)self.status_icon.setMaximumHeight(24)self.status_icon.setMaximumWidth(24)self.status_icon.setPixmap(QPixmap("Assets/main.png"))self.status_label = QLabel()self.status_label.setText(self.mainWindow.version + " - SimPyWeb X")self.status_bar.addWidget(self.status_icon)self.status_bar.addWidget(self.status_label)def navigate_to_url(self):s = QUrl(self.url_text_bar.text())if s.scheme() == '':s.setScheme('http')self.browser.load(s)def navigate_to_home(self):s = QUrl("https://www.baidu.com/")self.browser.load(s)def renew_urlbar(self, s):prec = s.scheme()if prec == 'http':self.ssl_label1.setPixmap(QPixmap("Assets/unsafe.png").scaledToHeight(24))self.ssl_label2.setText(" 不安全 ")self.ssl_label2.setStyleSheet("color:red;")elif prec == 'https':self.ssl_label1.setPixmap(QPixmap("Assets/safe.png").scaledToHeight(24))self.ssl_label2.setText(" 安全 ")self.ssl_label2.setStyleSheet("color:green;")self.url_text_bar.setText(s.toString())self.url_text_bar.setCursorPosition(0)def renew_progress_bar(self, p):self.progress_bar.setValue(p)class BrowserWindow(QWidget):name = "SimPyWeb X"version = "3.0"date = "2020.1.26"def __init__(self, mode: Literal["main", "push"] = "main"):super().__init__()self.setWindowTitle(self.name + " " + self.version)self.setWindowIcon(QIcon('Assets/main.png'))self.resize(1200, 900)self.tabs = DraggableTabWidget(self)self.tabs.setTabsClosable(True)self.tabs.setMovable(True)self.tabs.tabCloseRequested.connect(self.close_current_tab)self.tabs.currentChanged.connect(lambda i: self.setWindowTitle(self.tabs.tabText(i) + " - " + self.name))if mode == "main":self.init_tab = BrowserTab(self)self.init_tab.browser.load(QUrl("https://cn.bing.com"))self.add_new_tab(self.init_tab)def add_blank_tab(self):blank_tab = BrowserTab(self)self.tabs.parent().add_new_tab(blank_tab)def add_new_tab(self, tab):i = self.tabs.addTab(tab, "")self.tabs.setCurrentIndex(i)self.tabs.setTabIcon(i,QIcon('Assets/main.png'))tab.back_button.triggered.connect(tab.browser.back)tab.next_button.triggered.connect(tab.browser.forward)tab.stop_button.triggered.connect(tab.browser.stop)tab.refresh_button.triggered.connect(tab.browser.reload)tab.home_button.triggered.connect(tab.navigate_to_home)tab.enter_button.triggered.connect(tab.navigate_to_url)tab.add_button.triggered.connect(self.add_blank_tab)tab.url_text_bar.returnPressed.connect(tab.navigate_to_url)tab.browser.urlChanged.connect(tab.renew_urlbar)tab.browser.loadProgress.connect(tab.renew_progress_bar)tab.browser.titleChanged.connect(lambda title: (self.tabs.setTabText(i, title) if len(title) <= 11 else self.tabs.setTabText(i, title[:9]+"..."),self.tabs.setTabToolTip(i, title),self.setWindowTitle(self.tabs.tabText(i) + " - " + self.name)))#tab.browser.iconChanged.connect(self.tabs.setTabIcon(i, tab.browser.icon()))def close_current_tab(self, i):if self.tabs.count() > 1:self.tabs.removeTab(i)else:self.close()def resizeEvent(self, event):self.tabs.setGeometry(0, 0, self.width(), self.height())

__init__.py

python">import sys
from PySide6.QtWidgets import QApplication
from main import BrowserWindowif __name__ == '__main__':app = QApplication(sys.argv)MainWindow = BrowserWindow()MainWindow.show()sys.exit(app.exec())


http://www.ppmy.cn/embedded/105370.html

相关文章

互联网 Java 工程师面试题(Java 面试题四)

下面列出这份 Java 面试问题列表包含的主题 多线程&#xff0c;并发及线程基础数据类型转换的基本原则垃圾回收&#xff08;GC&#xff09;Java 集合框架数组字符串GOF 设计模式SOLID抽象类与接口Java 基础&#xff0c;如 equals 和 hashcode泛型与枚举Java IO 与 NIO常用网络协…

4.负载均衡

文章目录 1.多级部署2.实现请求计数器3.负载均衡3.1服务端负载均衡3.2客户端负载均衡3.3自定义负载均衡3.4负载均衡策略3.5 LoadBalance 原理 4.部署实现 大家好&#xff0c;我是晓星航。今天为大家带来的是 负载均衡 相关的讲解&#xff01;&#x1f600; 1.多级部署 复制一…

Python常用库-nump的使用

文章目录 安装 NumPy导入 NumPy创建数组1. 使用列表创建数组2. 多维数组3. 使用特殊函数 数组的基本操作1. 数组形状和大小2. 数据类型3. 转换数据类型4. 数组索引5. 数组切片6. 维度转换7. 数组连接8. 数组分割 数学运算1. 算术运算2. 广播机制3. 统计函数4. 最大最小值5. 排序…

软通动力子公司鸿湖万联重磅发布SwanLinkOS 5,擘画开源鸿蒙AI PC新篇章

在刚刚落下帷幕的首届H•I AI 探索峰会上&#xff0c;软通动力再次于鸿蒙生态领域实现突破。此次活动中&#xff0c;软通动力高级副总裁、鸿湖万联总经理秦张波发布SwanLinkOS 5&#xff08;天鸿操作系统&#xff09;&#xff0c;并联合软通计算&#xff08;同方计算机&#xf…

watchdog: BUG: soft lockup - CPU#3 stuck for 23s! [swapper/0:1]

测试代码 如下&#xff1a; #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h>static DEFINE_SPINLOCK(hack_spinA); static DEFINE_SPINLOCK(hack_spinB);void hack_spinAB(void) {printk("hack_lockdep:A->B\n"…

Android 使用原生相机Camera在预览界面进行识别二维码或者图片处理

1 项目需求 最近项目中有个需求:使用原生相机在预览界面进行识别二维码和图片处理。其实这个需求不是很难,难在对预览画面的处理过程。 自己针对这个需求写了一个工具类,便于后续进行复盘,同时也分享给有类似需求的伙伴们。 2 遇到的问题 2.1 二维码识别成功率低 使用…

编程之路:在Bug迷宫中寻找光明

编程&#xff0c;一个看似充满魔法的词汇&#xff0c;背后却隐藏着无数挑战与艰辛。在这条充满未知与探索的道路上&#xff0c;每一个程序员都如同一位勇敢的冒险家&#xff0c;不断在Bug的迷宫中寻找着出口。正是这些经历&#xff0c;塑造了编程高手们坚韧不拔的精神&#xff…

第十五届蓝桥杯青少组省赛成绩查询及国赛考试安排

刚刚&#xff0c;蓝桥杯青少组官网发布了“关于第十五届蓝桥杯大赛青少组省赛成绩查询及全国总决赛参赛证下载的通知”&#xff0c;第十五届蓝桥杯大赛青少组将开通省赛成绩查询通道&#xff0c;获得省赛一等奖的选手晋级全国总决赛&#xff0c;全国总决赛比赛时间为9月7日。 关…