将小部件重新插入网格布局时出现奇怪的图形伪影

问题描述

我正在使用 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("")

到目前为止,代码运行良好。它创建小部件并将它们添加到滚动区域的网格布局中。

使用两个测试网址,它看起来像这样:

After inserting two example links

当我使用以下删除项目功能时出现问题:

    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(如这张图片中所示)。

>

文本中的错误图形工件:

Error grafical artifacts in the text

我已经尝试在所有步骤之间调试所有文本,但最终总是得到完美的文本。 如果我清除 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 (将#修改为@)

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...