问题描述
我正在使用 pytube 开发 pyqt5 YouTube-downloader-kindof 应用。
总结一下问题:从网格布局中删除一个小部件并重新插入其他小部件后,重新插入的对象中的文本有一些grafical小故障(看起来多个文本相互重叠)
旁注: 在我提出这种方法之前,我尝试使用 QListView 或 QListModel 和 ItemDelegates 在动态系统上实现相同的动态“插入和删除小部件”,但在某些时候,我遇到了砖墙,我要么不明白如何解决发生的问题或 qt 本身不允许我尝试的操作。
详情:
为了使用 pytubes 函数并以一种很好的方式排列它们,我创建了一个继承自 QWidgets 的新类 QYTVidItem。
class QYTVidItem(qtw.QWidget):
def __init__(self,parent=None):
super(QYTVidItem,self).__init__(parent)
self.vid_layout = qtw.QHBoxLayout()
self.vidDetailLayout = qtw.QVBoxLayout()
self.vidHeaderLayout = qtw.QHBoxLayout()
self.vidSpecsLayout = qtw.QHBoxLayout()
self.vidStartEndLayout = qtw.QHBoxLayout()
self.buttonLayout = qtw.QHBoxLayout()
self.title = ""
self.url = ""
self.defaultThumbPix = qtg.QPixmap(":/assets/default.png")
def setData(self,purl):
#debug
print("setdata for: {}".format(purl))
self.url = purl.strip()
errTxt = self.checkLink(purl)
if (errTxt != "OK"):
return errTxt
# load Youtube vid infos
yt_vid = YouTube(url=purl)
#debug
print("Url Found. Title:{}".format(yt_vid.title))
self.title = yt_vid.title
thumbNail_url = yt_vid.thumbnail_url
thumbPixmap = qtg.QPixmap()
try:
request = urllib.request.Request(thumbNail_url)
response = urllib.request.urlopen(request)
data = response.read()
thumbPixmap.loadFromData(data)
except Exception:
self.thumbPixmap.load(":/assets/default.png")
##left Side:
# Thumbnail of video (or default if no thumb is found)
icoLabel = qtw.QLabel()
icoLabel.setPixmap(thumbPixmap.scaled(150,150,aspectRatioMode=qtc.Qt.KeepAspectRatio))
self.vid_layout.addWidget(icoLabel)
##right side
# most upper row:
# title
title = qtw.QLabel(yt_vid.title)
self.vidHeaderLayout.addWidget(title)
pixmap_remove = qtg.QPixmap(":/assets/cancel.svg")
icon_remove = qtg.QIcon(pixmap_remove.scaled(50,50,aspectRatioMode=qtc.Qt.KeepAspectRatio))
self.btn_remove = qtw.QToolButton()
self.btn_remove.setIcon(icon_remove)
self.vidHeaderLayout.addWidget(self.btn_remove)
self.vidDetailLayout.addItem(self.vidHeaderLayout)
# Mid upper row:
# Video Specs
dur_time = yt_vid.length
duration = qtw.QLabel("Duration: {} sec".format(dur_time))
self.vidSpecsLayout.addWidget(duration)
self.vidDetailLayout.addItem(self.vidSpecsLayout)
# mid lower row:
# Start/End Marker
self.vidStartEndLayout.setContentsMargins(5,5,0)
self.vidStartEndLayout.setSpacing(10);
self.vidStartEndLayout.setAlignment(qtc.Qt.AlignLeft)
#info: i use a double slider from the range_slider package
slider = RangeSlider(qtc.Qt.Horizontal)
slider.setMinimumHeight(10)
slider.setMaximumWidth(200)
slider.setMinimum(0)
slider.setMaximum(dur_time)
slider.setLow(0)
slider.setHigh(dur_time)
slider.setTickPosition(qtw.QSlider.TicksBelow)
slider.sliderMoved.connect(self.updateSliderVal)
slider.show()
slider.raise_()
self.vidStartEndLayout.addWidget(slider)
vid_start_txt = qtw.QLabel("start at(sec):")
vid_start_txt.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidStartEndLayout.addWidget(vid_start_txt)
lowVal = slider.low()
self.vid_start = qtw.QLabel(str(lowVal))
self.vid_start.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidStartEndLayout.addWidget(self.vid_start)
vid_end_txt = qtw.QLabel("end at(sec):")
vid_end_txt.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidStartEndLayout.addWidget(vid_end_txt)
highVal = slider.high()
self.vid_end = qtw.QLabel(str(highVal))
self.vidStartEndLayout.addWidget(self.vid_end)
self.vidStartEndLayout.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidDetailLayout.addItem(self.vidStartEndLayout)
# lower row:
# Video specific Buttons (picograms)
self.buttonLayout.setSpacing(10);
self.buttonLayout.setAlignment(qtc.Qt.AlignLeft)
pixmap_play = qtg.QPixmap(":/assets/play2.svg")
icon_play = qtg.QIcon()
icon_play.addPixmap(pixmap_play.scaled(50,aspectRatioMode=qtc.Qt.KeepAspectRatio))
btn_play = qtw.QToolButton()
#btn_play.setPixmap(pixmap_play.scaled(25,25,aspectRatioMode=qtc.Qt.KeepAspectRatio))
btn_play.setIcon(icon_play)
btn_play.clicked.connect(self.playItem)
self.buttonLayout.addWidget(btn_play)
pixmap_stop = qtg.QPixmap(":/assets/stop2.svg")
icon_stop = qtg.QIcon()
icon_stop.addPixmap(pixmap_stop.scaled(50,aspectRatioMode=qtc.Qt.KeepAspectRatio))
btn_stop = qtw.QToolButton()
#btn_stop.setPixmap(pixmap_stop.scaled(25,aspectRatioMode=qtc.Qt.KeepAspectRatio))
btn_stop.setIcon(icon_stop)
btn_play.clicked.connect(self.stopItem)
self.buttonLayout.addWidget(btn_stop)
pixmap_download = qtg.QPixmap(":/assets/download.svg")
icon_download = qtg.QIcon()
icon_download.addPixmap(pixmap_download.scaled(50,aspectRatioMode=qtc.Qt.KeepAspectRatio))
btn_download = qtw.QToolButton()
btn_download.setIcon(icon_download)
btn_download.clicked.connect(self.downloadItem)
self.buttonLayout.addWidget(btn_download)
self.vidDetailLayout.addItem(self.buttonLayout)
#assamble Layouts and activate it
self.vid_layout.addItem(self.vidDetailLayout)
self.setLayout(self.vid_layout)
return errTxt
def updateSliderVal(self,low_value,high_value):
self.vid_start.setText(str(low_value))
self.vid_end.setText(str(high_value))
为了显示这个小部件,我通过在行编辑中插入一个 url 来生成它们,创建一个新的 QYTVidItem 并将该 url 提供给它的 setData-Function。
def insertUrl(self):
self.main_window.insert_url_msg.setText("")
if (self.main_window.lineEdit_url.text() == ''):
return
else:
newUrl = self.main_window.lineEdit_url.text()
#check in ordered dict if url allready is in the grid
if newUrl.strip() in self.ytVidODict:
self.main_window.insert_url_msg.setText("Error: URL allready loaded")
return
#Test URL: Rick Roll https://www.youtube.com/watch?v=Lrj2Hq7xqQ8
#Test URL2: BudS&TerH - Lala Remix: https://www.youtube.com/watch?v=lL7jwhWAU_k
#parent of each QYTVidItem is a scroll area which contains a gridlayout
item = QYTVidItem(self.main_window.scrollA)
returnCode = item.setData(newUrl)
if (returnCode == "OK"):
row = self.main_window.url_scroller_grid.count()
col = 0
self.main_window.url_scroller_grid.addWidget(item,row,col,1,1)
#debug to test title text
gridItem = self.main_window.url_scroller_grid.itemAt(row).widget()
print("Gridpos: {} ; Title: {}".format(row,gridItem.title))
#save in OrderedDict:
item.btn_remove.clicked.connect(partial(self.removeYTVid,newUrl))
self.ytVidODict[newUrl.strip()] = item
else:
self.main_window.insert_url_msg.setText(returnCode)
self.main_window.lineEdit_url.setText("")
到目前为止,代码运行良好。它创建小部件并将它们添加到滚动区域的网格布局中。
使用两个测试网址,它看起来像这样:
当我使用以下删除项目功能时出现问题:
def removeYTVid(self,url):
#debug
print("remove Triggered by {}".format(url))
#remove from Scrollarea -> this automaticly causes the remaining items to re-arrange their indexes in the gridlayout
for vidItem in self.ytVidODict:
#debug
print("url: {}".format(vidItem))
print("vid: {}".format(self.ytVidODict[vidItem].title))
if vidItem == url.strip():
self.ytVidODict.pop(url.strip())
#debug
print("poped: {}".format(url.strip()))
break
#after poping the to be deleted item from the ordered dict,reverse removing all child-widgets
for i in reversed(range(self.main_window.url_scroller_grid.count())):
widgetToRemove = self.main_window.url_scroller_grid.itemAt( i ).widget()
# remove it from the layout list
self.main_window.url_scroller_grid.removeWidget( widgetToRemove )
# re-populate the gridlayout by iterating through the ytVid-Items in the ordered dict
for vidItem in self.ytVidODict:
row = self.main_window.url_scroller_grid.count()
col = 0
self.main_window.url_scroller_grid.addWidget(self.ytVidODict[vidItem],1)
#debug
gridItem = self.main_window.url_scroller_grid.itemAt(row).widget()
print("Gridpos: {} ; Title: {}".format(row,gridItem.title))
就其核心而言,这工作正常,因为它从网格和有序字典中删除了已删除的项目,但在重新插入剩余项目后,ytviditems 在文本中显示奇怪的 grafical articats(如这张图片中所示)。
>文本中的错误图形工件:
我已经尝试在所有步骤之间调试所有文本,但最终总是得到完美的文本。 如果我清除 gridlayout 中的所有项目(通过使用按钮删除所有项目),这些工件也会持续存在。值得注意的是,在完全清除之后,下一个 URL 是什么并不重要,它将在文本中包含这些工件。
有人可以解释这种现象以及如何避免它吗? 我想可能不得不触发重新渲染布局或类似的东西。
编辑: 这是代码的最小运行版本:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
from pytube import YouTube
from urllib.request import urlopen
import urllib.request
from PyQt5.QtWidgets import QApplication,QGridLayout,QVBoxLayout,QLabel
from collections import OrderedDict
from functools import partial
import sys
class Ui_MainWindow(object):
def setupUi(self,MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(388,255)
self.centralwidget = qtw.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.widget = qtw.QWidget(self.centralwidget)
self.widget.setGeometry(qtc.QRect(20,10,361,241))
self.widget.setObjectName("widget")
self.gridLayout = qtw.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.verticalLayout = qtw.QVBoxLayout(self.widget)
self.verticalLayout.setContentsMargins(0,0)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = qtw.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.lineEdit_url = qtw.QLineEdit(self.widget)
self.lineEdit_url.setObjectName("lineEdit_url")
self.horizontalLayout.addWidget(self.lineEdit_url)
self.insertUrl = qtw.QPushButton(self.widget)
self.insertUrl.setObjectName("insertUrl")
self.horizontalLayout.addWidget(self.insertUrl)
self.verticalLayout.addLayout(self.horizontalLayout)
self.scrollA = qtw.QScrollArea(self.widget)
self.scrollA.setWidgetResizable(True)
self.scrollA.setObjectName("scrollA")
self.scrollAreaWidgetContents = qtw.QWidget()
self.scrollAreaWidgetContents.setGeometry(qtc.QRect(0,357,206))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.gridLayout_scroller = qtw.QGridLayout(self.scrollAreaWidgetContents)
self.gridLayout_scroller.setObjectName("gridLayout_scroller")
self.scrollA.setWidget(self.scrollAreaWidgetContents)
self.verticalLayout.addWidget(self.scrollA)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
qtc.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self,MainWindow):
_translate = qtc.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow","MainWindow"))
self.insertUrl.setText(_translate("MainWindow","PushButton"))
class QYTVidItem(qtw.QWidget):
def __init__(self,self).__init__(parent)
self.vid_layout = qtw.QHBoxLayout()
self.vidDetailLayout = qtw.QVBoxLayout()
self.vidHeaderLayout = qtw.QHBoxLayout()
self.vidSpecsLayout = qtw.QHBoxLayout()
self.vidStartEndLayout = qtw.QHBoxLayout()
self.buttonLayout = qtw.QHBoxLayout()
self.title = ""
self.url = ""
def setData(self,purl):
self.url = purl.strip()
errTxt = "OK"
# load Youtube vid infos
yt_vid = YouTube(url=purl)
self.title = yt_vid.title
##left Side:
# Thumbnail of video (or default if no thumb is found)
thumbNail_url = yt_vid.thumbnail_url
thumbPixmap = qtg.QPixmap()
try:
request = urllib.request.Request(thumbNail_url)
response = urllib.request.urlopen(request)
data = response.read()
thumbPixmap.loadFromData(data)
icoLabel = qtw.QLabel()
icoLabel.setPixmap(thumbPixmap.scaled(150,aspectRatioMode=qtc.Qt.KeepAspectRatio))
self.vid_layout.addWidget(icoLabel)
except Exception:
pass
##right side
# most upper row:
# title
title = qtw.QLabel(yt_vid.title)
self.vidHeaderLayout.addWidget(title)
self.btn_remove = qtw.QToolButton()
self.vidHeaderLayout.addWidget(self.btn_remove)
self.vidDetailLayout.addItem(self.vidHeaderLayout)
# Mid row:
# Video Specs
dur_time = yt_vid.length
duration = qtw.QLabel("Duration: {} sec".format(dur_time))
self.vidSpecsLayout.addWidget(duration)
self.vidDetailLayout.addItem(self.vidSpecsLayout)
# mid lower row:
# Start/End Marker
self.vidStartEndLayout.setContentsMargins(5,0)
self.vidStartEndLayout.setSpacing(10);
self.vidStartEndLayout.setAlignment(qtc.Qt.AlignLeft)
vid_start_txt = qtw.QLabel("start at(sec):")
vid_start_txt.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidStartEndLayout.addWidget(vid_start_txt)
lowVal = 0
self.vid_start = qtw.QLabel(str(lowVal))
self.vid_start.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidStartEndLayout.addWidget(self.vid_start)
vid_end_txt = qtw.QLabel("end at(sec):")
vid_end_txt.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidStartEndLayout.addWidget(vid_end_txt)
highVal = 99
self.vid_end = qtw.QLabel(str(highVal))
self.vidStartEndLayout.addWidget(self.vid_end)
self.vidStartEndLayout.setAlignment(qtc.Qt.AlignLeft|qtc.Qt.AlignVCenter)
self.vidDetailLayout.addItem(self.vidStartEndLayout)
# lower row:
# Video specific Buttons (picograms)
self.buttonLayout.setSpacing(10);
self.buttonLayout.setAlignment(qtc.Qt.AlignLeft)
btn_play = qtw.QToolButton()
self.buttonLayout.addWidget(btn_play)
btn_stop = qtw.QToolButton()
self.buttonLayout.addWidget(btn_stop)
btn_download = qtw.QToolButton()
self.buttonLayout.addWidget(btn_download)
self.vidDetailLayout.addItem(self.buttonLayout)
#assamble Layouts and activate it
self.vid_layout.addItem(self.vidDetailLayout)
self.setLayout(self.vid_layout)
return errTxt
class MainWindow(qtw.QMainWindow):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.main_window = Ui_MainWindow()
self.main_window.setupUi(self)
self.ytVidODict = OrderedDict()
self.main_window.insertUrl.clicked.connect(self.insertUrl)
def insertUrl(self):
if (self.main_window.lineEdit_url.text() == ''):
return
else:
newUrl = self.main_window.lineEdit_url.text()
if newUrl.strip() in self.ytVidODict:
return
#Test URL: Rick Roll https://www.youtube.com/watch?v=Lrj2Hq7xqQ8
#Test URL2: BudS&TerH - Lala Remix: https://www.youtube.com/watch?v=lL7jwhWAU_k
item = QYTVidItem(self.main_window.scrollA)
returnCode = item.setData(newUrl)
if (returnCode == "OK"):
row = self.main_window.gridLayout_scroller.count()
col = 0
self.main_window.gridLayout_scroller.addWidget(item,1)
#save in OrderedDict:
item.btn_remove.clicked.connect(partial(self.removeYTVid,newUrl))
self.ytVidODict[newUrl.strip()] = item
else:
self.main_window.insert_url_msg.setText(returnCode)
self.main_window.lineEdit_url.setText("")
def removeYTVid(self,url):
#pop from ordered dict
for vidItem in self.ytVidODict:
if vidItem == url.strip():
self.ytVidODict.pop(url.strip())
break
#clear gridlayout from remaining children
for i in reversed(range(self.main_window.gridLayout_scroller.count())):
widgetToRemove = self.main_window.gridLayout_scroller.itemAt( i ).widget()
self.main_window.gridLayout_scroller.removeWidget( widgetToRemove )
#repopulate gridlayout
for vidItem in self.ytVidODict:
row = self.main_window.gridLayout_scroller.count()
col = 0
self.main_window.gridLayout_scroller.addWidget(self.ytVidODict[vidItem],1)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)