我正在尝试创建一个带边框的圆形标签,其中包含一个图像。到目前为止,我尝试设置一个样式表,将圆形图像 QLabel 重叠在带有彩色背景的更大圆形 QLabel 上并更改其大小策略,但无法获得非平庸的结果:
带有父背景标签(标记为 FIX 2):
设置样式表(标记为 FIX 1):
这是 MRE。在这种情况下,我在同一目录中使用蓝色背景:
"steel_blue.png"
from pathlib import Path
import sys
from PyQt5 import QtCore, QtWidgets as qtw
from PyQt5 import QtGui
from PyQt5.QtGui import QFont, QImage, QPainter, QPainterPath, QPixmap
import os
import urllib.request
RUNTIME_DIR = Path(os.path.split(sys.argv[0])[0])
class QCustomQWidget(qtw.QWidget):
def __init__(self, parent=None, video=""):
super(QCustomQWidget, self).__init__(parent)
self.textQVBoxLayout = qtw.QVBoxLayout()
self.textUpQLabel = qtw.QLabel()
font = QFont()
font.setPointSize(12)
self.textUpQLabel.setFont(font)
color_path = str(Path.joinpath(RUNTIME_DIR, 'steel_blue.png'))
base_size = 40
border_width = 2
# FIX 2 : PARENT LABEL OFFSET BY border_width
self.textDownQLabelBorder = RoundLabelImage(
path=color_path, size=base_size + 2 * border_width
)
# Overlap filled border-label with image
self.textDownQLabel = RoundLabelImage(
parent=self.textDownQLabelBorder, urlpath=video.author_thumbnail, size=base_size
)
self.textDownQLabel.move(border_width, border_width)
#test:
# self.textDownQLabelBorder = RoundLabelImage(urlpath=video.author_thumbnail, size=base_size)
self.textQVBoxLayout.addWidget(self.textUpQLabel)
self.textQVBoxLayout.addWidget(self.textDownQLabelBorder)
self.allQHBoxLayout = qtw.QHBoxLayout()
self.iconQLabel = qtw.QLabel()
self.allQHBoxLayout.addWidget(self.iconQLabel, 0)
self.allQHBoxLayout.addLayout(self.textQVBoxLayout, 1)
self.setLayout(self.allQHBoxLayout)
self.textUpQLabel.setStyleSheet('''
color: rgb(70,130,180);
''')
def setTextUp(self, text):
self.textUpQLabel.setText(text)
def setIcon(self, imagePath):
img = QPixmap(imagePath)
img = img.scaledToWidth(200)
self.iconQLabel.setPixmap(img)
self.iconQLabel.setSizePolicy(
qtw.QSizePolicy(qtw.QSizePolicy.Maximum, qtw.QSizePolicy.MinimumExpanding)
)
# TODO cut off parent border
class RoundLabelImage(qtw.QLabel):
"""Based on:
https://stackoverflow.com/questions/50819033/qlabel-with-image-in-round-shape/50821539"""
def __init__(self, *args, path="", urlpath="", size=50, antialiasing=True, **kwargs):
super().__init__(*args, **kwargs)
self.Antialiasing = antialiasing
self.setMaximumSize(size, size)
self.setMinimumSize(size, size)
self.radius = size / 2
if path != "":
p = QPixmap(path).scaled(
size, size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation
)
elif urlpath != "":
data = urllib.request.urlopen(urlpath).read()
pixmap_author = QPixmap()
pixmap_author.loadFromData(data)
p = pixmap_author.scaled(
size, size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation
)
##### POSSIBLE FIX FOR CHILD
if self.parent is not None:
self.target = QPixmap(self.size())
self.target.fill(QtCore.Qt.transparent)
else:
self.target = QPixmap(self.size())
self.target.fill(QtCore.Qt.transparent)
painter = QPainter(self.target)
if self.Antialiasing:
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
painter_path = QPainterPath()
if self.parent is not None:
painter_path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
else:
painter_path.addRoundedRect(0, 0, self.width(), self.height(), self.radius, self.radius)
# FIX 1 : STYLESHEET
# self.setStyleSheet(
# f"""
# border: 2px solid blue;
# border-radius: {size/2}px;
# min-height: {size/2}px;
# min-width: {size/2}px;
# """
# )
painter.setClipPath(painter_path)
painter.drawPixmap(0, 0, p)
self.setPixmap(self.target)
class Video():
"""Store video information for ease of use
"""
def __init__(self, id, title="", time="", author="", thumbnail="", author_thumbnail=""):
self.id = str(id)
self.url = "https://www.youtube.com/watch?v=" + self.id
self.title = str(title)
self.time = str(time)
self.author = str(author)
self.thumbnail = str(thumbnail)
self.author_thumbnail = str(author_thumbnail)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(qtw.QVBoxLayout())
self.checkbox_dict = dict()
self.button_dict = dict()
listWidget = qtw.QListWidget()
self.layout().addWidget(listWidget)
data = {
'id':
'MpufpgwiW-0',
'url':
'https://www.youtube.com/watch?v=MpufpgwiW-0',
'title':
'Lycoriscoris - Seimei (生命)',
'time':
'2 hours ago',
'author':
'Anjunadeep',
'thumbnail':
'https://i.ytimg.com/vi/MpufpgwiW-0/hqdefault.jpg?sqp=-oaymwEbCNIBEHZIVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLDI8RjBj8u6GvY_bXeBqAOSCRNsqA',
'author_thumbnail':
'https://yt3.ggpht.com/ytc/AAUvwnie-87z6V-8oWCHVp4fn4CN17H5if1IqolvJaSL6g=s68-c-k-c0x00ffffff-no-rj',
}
my_videos = dict()
for key, val in data.items():
if key == "id":
video_id = val
my_videos[val] = Video(video_id)
else:
setattr(my_videos[video_id], key, val)
for video_id, video in my_videos.items():
myQCustomQWidget = QCustomQWidget(video=video)
myQCustomQWidget.setTextUp(video.title)
url = video.thumbnail
data = urllib.request.urlopen(url).read()
img_thumbnail = QImage()
img_thumbnail.loadFromData(data)
myQCustomQWidget.setIcon(img_thumbnail)
myQListWidgetItem = qtw.QListWidgetItem(listWidget)
myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint())
listWidget.addItem(myQListWidgetItem)
listWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget)
self.resize(800, 300)
self.show()
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec())
答案 0 :(得分:1)
如果您只想在圆形图像周围绘制边框,使用叠加的小部件当然不是最明智的选择。
您应该做的是直接创建一个包括边框的 QPixmap。
这是您的课程的修订和清理版本:
class RoundLabelImage(qtw.QLabel):
def __init__(self, path="", urlpath="", size=50, border_width=0, border_color=None, antialiasing=True):
super().__init__()
self.setFixedSize(size, size)
if path != "":
source = QPixmap(path)
elif urlpath != "":
# you really shouldn't rely on functions that are possibly blocking...
data = urllib.request.urlopen(urlpath).read()
source = QPixmap()
source.loadFromData(data)
pixmap_size = size - border_width * 2
p = source.scaled(
pixmap_size, pixmap_size, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
self.target = QPixmap(self.size())
self.target.fill(QtCore.Qt.transparent)
painter = QPainter(self.target)
if antialiasing:
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
rect = QtCore.QRectF(self.rect())
if border_width:
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtGui.QColor(border_color))
painter.drawEllipse(rect)
rect.adjust(border_width, border_width, -border_width, -border_width)
painter_path = QPainterPath()
painter_path.addEllipse(rect)
painter.setClipPath(painter_path)
painter.drawPixmap(border_width, border_width, p)
self.setPixmap(self.target)
您可以简单地使用正确的参数创建实例:
self.textDownQLabel = RoundLabelImage(
urlpath=video.author_thumbnail, size=base_size, border_width=border_width,
border_color=QtGui.QColor(0, 30, 66)
)
请注意,我使用 steel_blue.png
图像的颜色作为边框:因为它只是一种颜色的图像,而且您总是使用相同的来源,因此使用 QPixmap 毫无意义。>
最后,正如我在代码中添加的注释所指出的,您确实应该不要依赖 UI 元素中可能阻塞(甚至可能引发异常)的函数。下载 QWidget __init__
中的任何内容肯定会阻塞,直到加载完成;这刚开始是错误的,但如果您还有很多项目(或连接速度较慢),情况会更糟。
对异步下载做一些研究(Qt 为这些情况提供了 QtNetwork 模块,有数百篇关于它的帖子),在下载其网络内容之前创建项目,然后在下载完成后更新这些内容, 通过适当的信号/插槽连接。