如何用三角尖头制作Qlabel

2024-06-15 05:40:55 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图使Qlabel看起来像信使中的现代聊天泡泡(圆形矩形,三角形尖端),如下图所示:

enter image description here

我设法使qlabel有一个锋利的边缘,但不知道如何使尖端。问题是在拐角处插入一条三角形路径,qlabel round rect和文本应该朝相反的方向移动,但是这样做会导致文本超出标签区域

这是一个子类标签,它覆盖了paint事件和resize事件(在word wrapping中使用的resize可能超出了我的问题范围)>;我删除了一些与颜色、字体等相关的不必要的代码

class chatLabel(QtWidgets.QLabel):
    def __init__(self,text):
        super(chatLabel, self).__init__(text)
        self.setContentsMargins(6,6,6,6)
        sizePolicy = QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Expanding )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.setSizePolicy(sizePolicy)
        self.color = QtGui.QColor("#333C43")

    def paintEvent(self, e):
        p = QtGui.QPainter(self)
        p.setRenderHint(QtGui.QPainter.Antialiasing, False)
        rect =  QtCore.QRectF(0,0,self.width()-1,self.height()-1)
        p.setPen(Qt.NoPen)
        path = QtGui.QPainterPath()
        path.setFillRule(Qt.WindingFill )
        path.addRoundedRect(rect, 15.0, 15.0)
        path.addRect(self.width()-13, 0, 13, 13)
        p.fillPath(path, self.color)

       super(chatLabel, self).paintEvent(e)

    def resizeEvent(self, e): #Due to a bug in Qt, we need this. ref:https://bugreports.qt.io/browse/QTBUG-37673
        #heightForWidth rely on minimumSize to evaulate, so reset it before
        self.setMinimumHeight( 0 )

        # define minimum height
        self.setMinimumHeight( self.heightForWidth( self.width() ) )
        if self.width()>256:
            self.setWordWrap(True)
            self.setMinimumWidth(128)

        super(chatLabel, self).resizeEvent(e)

这是上述子类标签的结果

enter image description here

我怎样才能达到我想要的样子? N、 B:我知道我可以用图像来做,但这需要根据文本大小缩放图像(9片)


Tags: pathrect文本selfdef标签qtwidth
2条回答

我理解你说的话如下:

  1. 你想在RoundRect上的任何地方画一个尖端三角形。在
  2. 但是笔尖已经超出了矩形,如果你试图避免这种情况,如果你扩大标签的尺寸来查看笔尖,那么矩形上的文本就会脱离矩形,反之亦然。你想避免这些吗现象。那也就是说,你想在RoundRect中一直包含文本,同时显示尖端三角形。在
  3. 根据这个理解,我试着写代码。
  4. 如果还有什么问题,请尽管问我。在

说明

你不能同时显示三角形提示和文本,因为圆形矩形(我称之为“气泡”)的大小几乎等于标签的大小大小。所以我试着改变它。在

为了改变这一点,我计算了每个文本的气泡大小。 我做了这个功能:

calc_textswidth(self, text, minimumwidth)

这将计算每个字符的文本宽度。 并返回长度。 如果长度大于气泡宽度(标签宽度-线条路径长度) ,我插入“\n”进行包装。在

如果设置setWrap(True),它将变得混乱。因为这意味着如果文本到达末尾,文本将被包装标签。所以我删除了这个方法。在

^{pr2}$

为了在每次调整大小时重新计算文本的位置,将所有文本合并为一个很重要字符串。和我们计算绳子的长度, 我们把文本分开,每一个长度都超过了气泡的宽度。在

我们一遍又一遍地做。在

作为解释,我划分了标签和气泡的大小。 气泡的宽度是根据文本的长度来划分的。在

附言

计算非常繁琐。如果你使用列表理解,它可能会变得紧凑,等等。。。在

我希望这个计算不是这个应用程序的瓶颈。。。在

如果还留着什么东西,请尽管问我。在

更新

正如你所说,我的代码是一个陷阱。 我将线边缘移到中心点。在

我认为这是最适合上边缘的地方。在


from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class chatLabel(QLabel):
    def __init__(self, text):
        super(chatLabel, self).__init__(text)
        self.setContentsMargins(6,6,6,6)
        sizePolicy = QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Expanding )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.setSizePolicy(sizePolicy)
        #I changed this color because I can't see the text good.
        self.color = QColor("#333C43")
        self.tip_length = 15
        self.coodinate_point = 10
        self.setText(text)
        self.initial_minimumwidth = 128
        self.setMinimumWidth(self.initial_minimumwidth)
        self.initial_maximumwidth = 128
        self.setMaximumWidth(self.initial_maximumwidth)
    def paintEvent(self, e):
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing, False)
        #I changed this width from - 1 to - 16 because I can't see the result good.
        rect =  QRectF(0,0,self.width()- self.tip_length,self.height()-1)
        p.setPen(Qt.NoPen)
        path = QPainterPath()
        path.setFillRule(Qt.WindingFill )
        path.addRoundedRect(rect, 15.0, 15.0)
        #I deleted this object
        #path.addRect(self.width()-13, 0, 13, 13)

        linePath = QPainterPath()
        linePath.moveTo(rect.right() + 15 , rect.top())       
        center = rect.center()
        linePath.lineTo(center.x()  , rect.bottom())
        linePath.lineTo(rect.right() , rect.top() - rect.height())

        path = path.united(linePath)


        p.fillPath(path, self.color)

        super(chatLabel, self).paintEvent(e)
    def checktextsinside(self, text):
        font = self.font()
        fontmetrics = QFontMetricsF(font)
        fontwidth = fontmetrics.width(text)
        return fontwidth
    def checkeachcharinside(self, text, minimumwidth):
        font = self.font()
        fontmetrics = QFontMetricsF(font)
        t_sum = 0
        t_join = ""
        chat_data = []
        for num, t in enumerate(text):   
            cw = fontmetrics.widthChar(t)
            t_sum += cw
            t_join += t        
            if t_sum > minimumwidth - self.tip_length :

                chat_data.append(t_join+"\n")
                t_sum = 0
                t_join = ""

        #append the final extra t_join
        chat_data.append(t_join)
        return t_sum, chat_data
    def resizeEvent(self, e): #Due to a bug in Qt, we need this. ref:https://bugreports.qt.io/browse/QTBUG-37673
        #heightForWidth rely on minimumSize to evaulate, so reset it before   
        if self.width()>256:
            self.setMinimumWidth(128)       
        text = self.text() 
        text = text.replace("\n", "")
        n_width, chat_data = self.checkeachcharinside(text, self.initial_minimumwidth - (self.tip_length + self.coodinate_point))        
        joint_text = ""
        for joint in chat_data:          
            joint_text += joint            
        self.setText(joint_text)
        self.setMinimumSize(QSize(n_width + self.tip_length, self.heightForWidth( self.width())))
        super(chatLabel, self).resizeEvent(e)
def main():
    try:
        app=QApplication([])
    except Exception as e:
        print(e)
    widget = chatLabel("This is the result typing!Please Don't wrap!Yaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")

    widget.show()
    sys.exit(app.exec_())
if __name__ == "__main__":
    main()

我想我找到了一个简单的解决方法。在

因为问题是,如果我在右角做了一个提示,文本中的一个移动就需要使文本包含在圆形矩形(气泡)中。我们可以通过在样式表中使用填充来实现这种转换,这将使文本从角落移动。因此,文本将显示为它包含在气泡中。在

感谢用户9402680的回答和他的代码片段,我在其中添加了样式表行以达到所需的效果。在

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class chatLabel(QLabel):
    def __init__(self,text):
        super(chatLabel, self).__init__(text)
        self.setContentsMargins(6,6,6,6)
        sizePolicy = QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Expanding )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.setSizePolicy(sizePolicy)
        self.color = QColor("#333C43")
        # 17 px margin from right (to make the text included in the bubble
        # 8  px margin from left. 
        self.setStyleSheet("QLabel{padding: 0px 8px 0px 17px;}")


    def paintEvent(self, e):
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing, False)
        #I changed this width from - 1 to - 16 because I can't see the result good.
        rect =  QRectF(0,0,self.width()- 16,self.height()-1)
        p.setPen(Qt.NoPen)
        path = QPainterPath()
        path.setFillRule(Qt.WindingFill )
        path.addRoundedRect(rect, 15.0, 15.0)
        #I deleted this object
#        path.addRect(self.width()-13, 0, 13, 13)

        linePath = QPainterPath()
        linePath.moveTo(rect.right() + rect.width()/6 , rect.top())
        linePath.lineTo(rect.right() - rect.width()/2, rect.bottom())
        linePath.lineTo(rect.right() , rect.top() - rect.height()/3)
#        linePath.lineTo(rect.right() - rect.width()/5, rect.top() - rect.height()/2)
        path = path.united(linePath)
        #cubic bezier curve, please try this , too.
#        cubicPath =QPainterPath()
#        cubicPath.moveTo(rect.right() - 20, rect.top())
#        cubicPath.cubicTo(rect.right() - 20, rect.top() + rect.height()/2, rect.right() , rect.top() , rect.right() + 15, rect.top())
#        path = path.united(cubicPath)

        p.fillPath(path, self.color)

        super(chatLabel, self).paintEvent(e)

    def resizeEvent(self, e): #Due to a bug in Qt, we need this. ref:https://bugreports.qt.io/browse/QTBUG-37673
        #heightForWidth rely on minimumSize to evaulate, so reset it before
        self.setMinimumHeight( 0 )

        # define minimum height
        self.setMinimumHeight( self.heightForWidth( self.width() ) )
        if self.width()>256:
            self.setWordWrap(True)
            self.setMinimumWidth(128)

        super(chatLabel, self).resizeEvent(e)
def main():
    try:
        app=QApplication([])
    except Exception as e:
        print(e)
    widget = chatLabel("This is the result!")
    widget.show()
    sys.exit(app.exec_())
if __name__ == "__main__":
    main()
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class chatLabel(QLabel):
    def __init__(self,text):
        super(chatLabel, self).__init__(text)
        self.setContentsMargins(6,6,6,6)
        sizePolicy = QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Expanding )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.setSizePolicy(sizePolicy)
        self.color = QColor("#333C43")

    def paintEvent(self, e):
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing, False)
        #I changed this width from - 1 to - 16 because I can't see the result good.
        rect = QRectF(16,0,self.width()- 16,self.height()-1)
        p.setPen(Qt.NoPen)
        path = QPainterPath()
        path.setFillRule(Qt.WindingFill )
        path.addRoundedRect(rect, 15.0, 15.0)
        #I deleted this object
#        path.addRect(self.width()-13, 0, 13, 13)

        linePath = QPainterPath() linePath.moveTo(rect.left() - rect.width()/6 ,rect.top()) 
        linePath.lineTo(rect.left() + rect.width()/2, rect.bottom()) 
        linePath.lineTo(rect.left() , rect.top() - rect.height()/3)
#       linePath.lineTo(rect.right() - rect.width()/5, rect.top() - rect.height()/2)
        path = path.united(linePath)
        #cubic bezier curve, please try this , too.
#        cubicPath =QPainterPath()
#        cubicPath.moveTo(rect.right() - 20, rect.top())
#        cubicPath.cubicTo(rect.right() - 20, rect.top() + rect.height()/2, rect.right() , rect.top() , rect.right() + 15, rect.top())
#        path = path.united(cubicPath)

        p.fillPath(path, self.color)

        super(chatLabel, self).paintEvent(e)

    def resizeEvent(self, e): #Due to a bug in Qt, we need this. ref:https://bugreports.qt.io/browse/QTBUG-37673
        #heightForWidth rely on minimumSize to evaulate, so reset it before
        self.setMinimumHeight( 0 )

        # define minimum height
        self.setMinimumHeight( self.heightForWidth( self.width() ) )
        if self.width()>256:
            self.setWordWrap(True)
            self.setMinimumWidth(128)

        super(chatLabel, self).resizeEvent(e)
def main():
    try:
        app=QApplication([])
    except Exception as e:
        print(e)
    widget = chatLabel("This is the result!")
    widget.show()
    sys.exit(app.exec_())
if __name__ == "__main__":
    main()

相关问题 更多 >