Как сделать Qlabel с треугольным наконечником - PullRequest
0 голосов
/ 08 мая 2019

Я пытаюсь сделать Qlabel похожим на современные пузыри чата в мессенджерах (круглый прямоугольник с треугольным кончиком), как на этом изображении:

enter image description here

Мне удалось сделать клабель одним острым краем, но я не могу понять, как сделать наконечник. проблема заключалась в том, чтобы вставить треугольный путь в углу, квадратный прямоугольник и текст должен быть смещен в противоположном направлении, но из-за этого текст выходит из области метки

Вот подклассовая метка с переопределенными событиями рисования и изменениями размера (изменение размера используется в переносе слов, что выходит за рамки моей проблемы - возможно)> Я удалил некоторый ненужный код, связанный с раскрашиванием, шрифтами и т. Д.

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

Как мне достичь нужного мне вида? Н.Б .: Я знаю, что могу сделать это с изображениями, но для этого требуется масштабировать изображение (9 срезов) в соответствии с размером текста

Ответы [ 2 ]

1 голос
/ 08 мая 2019

Я понял то, что вы сказали следующим образом:

  1. Вы хотите нарисовать треугольник наконечника по краю в любом месте RoundRect.
  2. Но наконечник находится вне прямоугольникакак есть, и затем, если вы попытаетесь избежать этого, если вы увеличите размер метки для просмотра кончика, тексты на прямоугольнике будут выходить из прямоугольника наоборот.Вы хотите избежать этих явлений. То есть вы хотите включать тексты в RoundRect все время и показывать треугольник наконечника одновременно.
  3. В соответствии с этим пониманием я попытался написать код.
  4. Пожалуйста, не стесняйтесь спрашивать меня, если у вас остались какие-то проблемы.

Пояснение

Вы не можете показать кончик треугольника итексты одновременно, потому что размер круглого прямоугольника (я назову это «пузырем») почти равен размеру этикетки. Поэтому я попытался изменить его.

Чтобы изменить это, я рассчитал размерпузыря из каждого текста.Я сделал эту функцию:

calc_textswidth(self, text, minimumwidth)

Это вычисляет ширину текста каждого символа.и вернуть длину.если длина больше, чем ширина пузырька (ширина метки - длина строки пути), я вставил «\ n» для переноса.

Если вы установите setWrap(True), он станет грязным.Потому что это означает, что тексты будут переноситься, если тексты достигают конца метки. Поэтому я удаляю метод.

text = self.text() 
text = text.replace("\n", "")

Для пересчета положения текстов при каждом изменении размера важно объединить все текстыкак одна строка. И мы вычисляем всю длину строки, мы делим тексты каждый раз, когда длина становится больше, чем ширина пузырька.

Мы делаем это снова и снова.

КакПо объяснению я разделил размер этикетки и пузыря.Ширина пузырьков определяется исходя из длины текстов.

PS

Расчет очень многословен.Возможно, он станет компактным, если вы будете использовать списки и т. Д. ...

Надеюсь, этот расчет не является чем-то вроде бутылочного горлышка этого приложения ...

Если он останетсячто-то, пожалуйста, не стесняйтесь спрашивать меня.

Обновление

Как вы говорите, мой код является ловушкой линии.Я перемещаю край линии к центральной точке.

Я думаю, что это лучшее место для верхнего края.


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()
0 голосов
/ 10 мая 2019

Я думаю, что нашел простое решение для моей проблемы.

Поскольку проблема заключалась в том, что, если я сделал подсказку в правом углу, требуется сдвиг в тексте, чтобы сделать текст, содержащийся внутри круглого прямоугольника (пузыря).Мы можем сделать это с помощью отступов в таблицах стилей, которые сделают текст смещенным из угла.Таким образом, текст будет отображаться в том виде, в котором он включен в пузырь.

Благодарю пользователя user9402680 и его фрагмент кода, я добавил к нему строку таблицы стилей для достижения необходимого эффекта.

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()
...