5
votes

Est-il possible d'étendre la zone de dessin autour du QSlider

Mon objectif est d'avoir un QSlider personnalisé avec des coches et des libellés en Python 3 à l'aide du module PySide2 . Pour ce faire, j'édite le paintEvent par défaut de la classe QSlider dans une classe dérivée. Cependant, il s'avère que la zone imprimable est limitée et que les étiquettes haut / bas que j'ai placées sont rognées (voir capture d'écran). Le code que j'utilise pour générer ces curseurs est le suivant:

import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *

slider_x = 150
slider_y = 450
slider_step = [0.01, 0.1, 1, 10, 100]  # in microns


class MySlider(QSlider):
    def __init__(self, type, parent=None):
        super(MySlider, self).__init__(parent)
        self.Type = type

    def paintEvent(self, event):
        super(MySlider, self).paintEvent(event)
        qp = QPainter(self)
        pen = QPen()
        pen.setWidth(2)
        pen.setColor(Qt.red)

        qp.setPen(pen)
        font = QFont('Times', 10)
        qp.setFont(font)
        self.setContentsMargins(50, 50, 50, 50)
        # size = self.size()
        # print(size)
        # print("margins", self.getContentsMargins())
        # print(self.getContentsMargins())
        # print(self.contentsRect())
        contents = self.contentsRect()
        self.setFixedSize(QSize(slider_x, slider_y))
        max_slider = self.maximum()
        y_inc = 0
        for i in range(max_slider):
            qp.drawText(contents.x() - font.pointSize(), y_inc + font.pointSize() / 2, '{0:2}'.format(slider_step[i]))
            qp.drawLine(contents.x() + font.pointSize(), y_inc, contents.x() + contents.width(), y_inc)
            y_inc += slider_y/4


class Window(QWidget):
    """ Inherits from QWidget """
    def __init__(self):
        super().__init__()
        self.title = 'Control Stages'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 100
        self.AxesMapping = [0, 1, 2, 3]
        self.initUI()

    def initUI(self):
        """ Initializes the GUI either using the grid layout or the absolute position layout"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        Comp4 = self.createSlider("step_size")
        Comp5 = self.createSlider("speed")
        windowLayout = QGridLayout()
        windowLayout.setContentsMargins(50, 50, 50, 50)
        HGroupBox = QGroupBox()
        layout = QGridLayout()
        layout.addWidget(Comp4, 0, 0)
        layout.addWidget(Comp5, 0, 1)
        HGroupBox.setLayout(layout)
        HGroupBox.setFixedSize(QSize(740, 480))
        windowLayout.addWidget(HGroupBox, 0, 0)
        self.setLayout(windowLayout)
        self.show()

    def createSlider(self, variant):
        Slider = MySlider(Qt.Vertical)
        Slider.Type = variant
        Slider.setMaximum(5)
        Slider.setMinimum(1)
        Slider.setSingleStep(1)
        Slider.setTickInterval(1)
        Slider.valueChanged.connect(lambda: self.sliderChanged(Slider))
        return Slider

    @staticmethod
    def sliderChanged(Slider):
        print("Slider value changed to ", Slider.value(), "slider type is ", Slider.Type)
        if Slider.Type == "step_size":
            print("this is a step size slider")
        elif Slider.Type == "speed":
            print("this is a speed slider")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_())

Est-il possible d'étendre la zone de dessin autour du QSlider et si oui, comment puis-je obtenir cet effet? Vous pouvez voir sur la capture d'écran que les étiquettes rouges à côté des première et dernière graduations ne s'affichent pas correctement et qu'elles sont rognées (c'est-à-dire que dans la première étiquette de graduation, le haut de 1 et 0 est manquant pour l'étiquette 0.01).

 entrez la description de l'image ici

MODIFIER: Après avoir essayé la solution proposée, une partie de l'étiquette supérieure est coupée. La deuxième version ci-dessous est toujours similaire sur Windows 10 64 bits avec PySide2 5.12.0 et Python 3.6.6.

EDIT2 J'ai un système à double démarrage, donc je l'ai essayé Ubuntu 16.04.3 LTS avec Python 3.5.2 / PySide 5.12.0 et cela a fonctionné dès la sortie de la boîte. Voici une capture d'écran à partir de là, mais malheureusement, elle doit fonctionner sous Windows.

entrez la description de l'image ici


12 commentaires

Vous pourriez mieux expliquer votre question, votre code ne peut pas être exécuté donc je ne peux pas comparer ce que vous obtenez avec quelle image vous montrez pour comprendre ce que vous voulez.


@eyllanesc Vous avez raison J'ai fourni un MWE et clarifié ma question davantage. Comme vous pouvez le voir, les première et dernière étiquettes de la capture d'écran ne sont pas affichées correctement, probablement en raison de l'extension dans des régions qui ne peuvent pas être dessinées.


@eyllanesc Avez-vous une suggestion sur la façon de réaliser des graduations personnalisées avec le QSlider? La zone dessinable autour d'eux semble être tronquée. La méthode setContentsMargins ne change pas cette zone.


@Vesnog Le QSlider hérite de QAbstractSlider - pourriez-vous intégrer SliderToMaximum pour voir le dessus? Juste une pensée .. ( pyqt.sourceforge.net/Docs/PyQt4/ … )


@RachelGallen Je n'ai pas complètement compris votre déclaration. Voulez-vous dire que je devrais simplement appeler cette méthode et voir ce qui se passe?


@Vesnog Cela vaut la peine d'enquêter ...


@RachelGallen J'ai essayé avec Linux et tout a bien fonctionné, maintenant à nouveau sous Windows et je vais étudier cette méthode.


@RachelGallen TypeError: l'objet 'PySide2.QtWidgets.QAbstractSlider.SliderAction' n'est pas appelable C'est probablement une méthode privée en C ++.


@Vesnog qui donne l'impression que vous appelez la méthode de manière incorrecte plutôt que de ne pas l'appeler.


@RachelGallen Je ne l'ai probablement pas appelé correctement. Pouvez-vous m'aider?


@Vesnog mon dernier mot sur ce soir - doc.qt.io/qtforpython/PySide2/QtWidgets/... a dû poster pour me vider la tête ... zzzz


@RachelGallen J'ai fait print (Comp4.maximum ()) et la valeur est 5. Je n'ai pas pu faire SliderToMaximum , mais je peux faire Comp4.setSliderPosition (Comp4.maximum ())


3 Réponses :


4
votes

Si j'ai bien compris, vous voulez que les graduations soient visibles en haut et en bas, car elles semblent être "coupées" à cause des marges. Je pense que c'est le résultat de l'hypothèse que les marges des widgets sont correctement réglées sur zéro ou quelque chose du genre, vous pouvez jouer avec le changement des nombres et voir ces effets. (J'ai essayé de déplacer les marges et je n'ai pas réussi)

Maintenant, à la suite d'un ancien message , j'ai remarqué que réaliser quelque chose de similaire semble être délicat, et vous pouvez baser vos graduations sur une formule basée sur le minimum et maximum du QSlider .

Puisque la coche en bas sera sous le widget principal, vous pouvez simplement ajouter une condition spéciale pour qu'il le rende visible. P >

Je ne voulais pas coller tout le code, mais il vous suffit de déclarer opt et handle avant votre boucle. Calculez la position y à l'intérieur et utilisez-la à la place de votre y_inc .

handle_height = handle.height() + 10

 entrez la description de l'image ici

Edit: Il semble y avoir une marge supérieure négligée d'environ 10 unités sur Windows, et c'est à mon humble avis un bogue dans Qt. Une solution de contournement consiste à remplacer:

handle_height = handle.height()

par

def paintEvent(self, event):                                                                                                                                                            
    super(MySlider, self).paintEvent(event)                                                     
    qp = QPainter(self)                                                                         
    pen = QPen()                                                                                
    pen.setWidth(2)                                                                             
    pen.setColor(Qt.red)                                                                        

    qp.setPen(pen)                                                                              
    font = QFont('Times', 10)                                                                   
    qp.setFont(font)                                                                            
    self.setContentsMargins(50, 50, 50, 50)                                                     
    # size = self.size()                                                                        
    # print(size)                                                                               
    # print("margins", self.getContentsMargins())                                               
    # print(self.getContentsMargins())                                                          
    # print(self.contentsRect())                                                                
    contents = self.contentsRect()                                                              
    self.setFixedSize(QSize(slider_x, slider_y))                                                

    # New code                                                                                  
    max_slider = self.maximum()                                                                 
    min_slider = self.minimum()                                                                 
    len_slider = max_slider - min_slider                                                        
    height = self.height()                                                                      

    opt = QStyleOptionSlider()                                                                  
    handle = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self)   
    handle_height = handle.height() # Add +10 on windows (workarount)                                                            
    height_diff = height - handle_height                                                        
    point_size = font.pointSize()                                                               

    for i in range(max_slider):                                                                 
        y = round(((1 - i / len_slider) * height_diff + (handle_height / 2.0))) - 1             

        # Trick for the tick at the bottom                                                      
        if i == 0:                                                                              
            y = self.height() - handle_height/2.0 # To have the tick in the middle              
            #y = height_diff # to shift it a bit                                                

        qp.drawText(contents.x() - point_size, y + point_size / 2, '{0:2}'.format(slider_step[len_slider - i]))
        qp.drawLine(contents.x() + point_size, y, contents.x() + contents.width(), y)  

dans la méthode paintEvent .


11 commentaires

Merci, mais une partie de l'étiquette du haut est toujours coupée et la commande est modifiée avec 0,01 en bas.


oups, je n'ai pas remarqué. Changez simplement la direction de l'itération sur la boucle for, et changez simplement -5 par un -8 par exemple pour éviter le tick du bas.


Pourtant, l'étiquette continue de se couper en haut.


J'ai mis à jour la méthode paintEvent et joint une nouvelle capture d'écran, j'espère que cela aide.


J'ai copié la méthode paintEvent directement à partir de votre dernier article, mais les valeurs finales sont toujours rognées. Peut-être que cela a quelque chose à voir avec la version Python ou PySide2.


Peut-être, j'ai fait une pip install PySide2 sur un nouvel environnement virtuel Python 3.7.2 (linux). Quels PySide2, Python et OS utilisez-vous?


Windows 10 64 bits, Python 3.6.6, PySide2 5.11.2


pouvez-vous essayer de mettre à jour PySide2 ? la version officielle est 5.12.0 , et cela pourrait être un bogue résiduel de la version Technical Preview. Si 5.12.0 a également le problème, il peut s'agir d'un bogue Windows.


continuons cette discussion dans le chat .


Tout fonctionne bien sous Linux, mais je devrais le faire fonctionner sous Windows.


Agréable. Exactement une marge de 10 pixels aux deux extrémités du QSlider. Existe-t-il un moyen de récupérer cela à partir de Qt?



4
votes

Ma recommandation serait que vous étudiez QAbstractSlider qui est hérité par QSlider. Il contient une méthode appelée triggerAction (la valeur SliderToMaximum est 6) qui peut être utilisé pour définir la position du curseur lorsque l'action est déclenchée .

La syntaxe pour déclencher une action est

PySide2.QtWidgets.QAbstractSlider.setMaximum(arg) // pass your own value

Les valeurs de SliderAction sont les suivantes:

QAbstractSlider.SliderNoAction 0 QAbstractSlider.SliderSingleStepAdd 1

QAbstractSlider.SliderSingleStepSub 2

QAbstractSlider.SliderPageStepAdd 3

QAbstractSlider.SliderPageStepSub 4

QAbstractSlider.SliderToMinimum 5

QAbstractSlider.SliderToMaximum 6

QAbstractSlider.SliderMove 7

( source )

Utilisation de a PySide >, vous pouvez appeler ces méthodes en utilisant

PySide2.QtWidgets.QAbstractSlider.maximum()  //returns the value of the maximum

ou

 QAbstractSlider.triggerAction (self, SliderAction action)


4 commentaires

Merci pour la réponse dans la mesure où j'ai compris le PySide2.QtWidgets.QAbstractSlider.setMaximum (arg) définit la valeur maximale possible comme un entier, alors que la méthode PySide2.QtWidgets.QAbstractSlider.maximum ( ) renvoie cette valeur. Alors, comment le positionnement des étiquettes pourrait-il être affecté par cela? Désolé, je suis un peu confus et je n'ai pas compris votre déclaration La valeur rmax de la diapositive est de 6, le minimum est de 5, c'est pourquoi elle est probablement coupée. Je mets mes suggestions dans une réponse ci-dessous. dans le commentaire.


@Vesnog regarde les valeurs d'action du curseur QAbstractSlider que j'ai mis dans la réponse. Slider ToMinimum est de 5, Slider ToMaximum est de 6, vous avez dit que la valeur que vous avez imprimée était de 5 ... C'est ce que je pensais que vous vouliez dire, que c'était au minimum.


Vous pouvez définir la valeur maximale en utilisant setMaximum, sinon elle ne s'appellerait pas ainsi et il n'y aurait pas besoin d'un argument.


Ce n'est pas le cas désolé pour le malentendu. Le résultat de print ("Le max est", Comp4.maximum (), "Le min est:", Comp4.minimum ()) est Le max est 5 Le min est: 1



2
votes

Après avoir bidouillé, j'ai finalement trouvé une solution. Il comprend des feuilles de style et je l'avais déjà essayé. Cependant, à l'époque, je n'étais pas en mesure de l'implémenter correctement. J'ai maintenu la taille du widget constante, mais j'ai diminué la longueur de la rainure pour pouvoir adapter les étiquettes à l'intérieur de la zone imprimable. Le code complet est ci-dessous:

import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *

slider_x = 150
slider_y = 450
slider_step = [0.01, 0.1, 1, 10, 100]  # in microns
groove_y = 400
handle_height = 10


class MySlider(QSlider):
    def __init__(self, type, parent=None):
        # super(MySlider, self).__init__(parent)
        super().__init__()
        self.parent = parent
        self.Type = type
        # self.setFixedHeight(115)
        self.setStyleSheet("""QSlider::groove:vertical {
    border: 1px solid black;
    height: """ + str(groove_y) + """ px;
    width: 10px;
    border-radius: 2px;
    }

    QSlider::handle:vertical {
        background: red;
        border: 1px solid red;
        height: """ + str(handle_height) + """ px;
        margin: 2px 0;
        border-radius: 1px;
    }

    QSlider::add-page:vertical {
        background: blue;
    }
    QSlider::sub-page:vertical {
        background: red;
""")

    def paintEvent(self, event):
        super(MySlider, self).paintEvent(event)
        qp = QPainter(self)
        pen = QPen()
        pen.setWidth(2)
        pen.setColor(Qt.black)

        qp.setPen(pen)
        font = QFont('Times', 10)
        qp.setFont(font)
        self.setContentsMargins(50, 50, 50, 50)
        self.setFixedSize(QSize(slider_x, slider_y))
        contents = self.contentsRect()
        max = self.maximum()
        min = self.minimum()

        y_inc = slider_y - (slider_y - groove_y) / 2
        for i in range(len(slider_step)):
            qp.drawText(contents.x() - 2 * font.pointSize(), y_inc + font.pointSize() / 2, '{0:3}'.format(slider_step[i]))
            qp.drawLine(contents.x() + 2 * font.pointSize(), y_inc, contents.x() + contents.width(), y_inc)
            y_inc -= groove_y / (max - min)



class Window(QWidget):
    """ Inherits from QWidget """
    def __init__(self):
        super().__init__()
        self.title = 'Control Stages'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 100
        self.AxesMapping = [0, 1, 2, 3]
        self.initUI()

    def initUI(self):
        """ Initializes the GUI either using the grid layout or the absolute position layout"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        Comp4 = self.createSlider("step_size")
        Comp5 = self.createSlider("speed")
        windowLayout = QGridLayout()
        windowLayout.setContentsMargins(50, 50, 50, 50)
        HGroupBox = QGroupBox()
        layout = QGridLayout()
        layout.addWidget(Comp4, 0, 0)
        layout.addWidget(Comp5, 0, 1)
        HGroupBox.setLayout(layout)
        HGroupBox.setFixedSize(QSize(740, 480))
        windowLayout.addWidget(HGroupBox, 0, 0)
        self.setLayout(windowLayout)
        self.show()

    def createSlider(self, variant):
        Slider = MySlider(Qt.Vertical)
        Slider.Type = variant
        Slider.setMaximum(len(slider_step))
        Slider.setMinimum(1)
        Slider.setSingleStep(1)
        Slider.setTickInterval(1)
        Slider.valueChanged.connect(lambda: self.sliderChanged(Slider))
        return Slider

    @staticmethod
    def sliderChanged(Slider):
        print("Slider value changed to ", Slider.value(), "slider type is ", Slider.Type)
        if Slider.Type == "step_size":
            print("this is a step size slider")
        elif Slider.Type == "speed":
            print("this is a speed slider")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_())


0 commentaires