Artem
Artem

Reputation: 587

How to fix QPropertyAnimation "starting an animation without end value" error

I'm trying to port qt example from c++ to pyqt/pyside2. This example shows hide/show action with a textedit field using QPropertyAnimation.

It doesn't seem to be complicated, but I still can't figure out why it is not working properly

The main idea is: textEdit widget "collapses" by pressing ">" button and returns back on pressing the button again.

Here's my python code:

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

class myWidget(QWidget):
    def __init__(self):
        super(myWidget, self).__init__()

        self.m_deltaX = 0
        self.m_isClosed = False

        self.btn = QPushButton(self)
        self.btn.setText(">")
        self.btn.setCheckable(True)
        self.btn.setFixedSize(QSize(25, 25))
        self.btn.connect(SIGNAL("clicked()"), self.closeOpenEditor)

        self.text1 = QTextEdit(self)
        self.text1.setText("some sample text")

        self.text2 = QTextEdit(self)

        self.layout_btn = QVBoxLayout()
        self.layout_btn.addWidget(self.btn)

        self.layout_m = QHBoxLayout()
        self.layout_m.addWidget(self.text1, 10)
        self.layout_m.addSpacing(15)
        self.layout_m.addLayout(self.layout_btn)
        self.layout_m.setSpacing(0)
        self.layout_m.addWidget(self.text2, 4)

        self.setLayout(self.layout_m)
        self.resize(800, 500)

    def closeOpenEditor(self):
        self.m_isClosed = self.btn.isChecked()

        animation1 = QPropertyAnimation(self.text2, b"geometry")

        if self.m_isClosed:
            self.text2.setMaximumWidth(self.text2.width())
            text2Start = int(self.text2.maximumWidth())

            self.m_deltaX = text2Start
            text2End = int(3)

            animation1.setDuration(250)
            animation1.setStartValue(text2Start)
            animation1.setEndValue(text2End)

            self.btn.setText("<")
        else:
            text2Start = int(self.text2.maximumWidth())
            text2End = int(self.m_deltaX)

            animation1.setDuration(250)
            animation1.setStartValue(text2Start)
            animation1.setEndValue(text2End)

            self.btn.setText(">")

        animation1.start()

    def resizeEvent(self, event:QResizeEvent):
        if not self.m_isClosed:
            self.text2.setMaximumWidth(QWIDGETSIZE_MAX)



if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = myWidget()
    w.show()
    sys.exit(app.exec_())

I will also add c++ code:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QTextEdit>
#include <QPushButton>
#include <QHBoxLayout>

class MyWidget : public QWidget
{

    Q_OBJECT

    QTextEdit       *m_textEditor1;
    QTextEdit       *m_textEditor2;
    QPushButton     *m_pushButton;
    QHBoxLayout     *m_layout;
    QVBoxLayout     *m_buttonLayout;

    int              m_deltaX;
    bool             m_isClosed;


public:

    MyWidget(QWidget * parent = 0);
    ~MyWidget(){}

    void resizeEvent( QResizeEvent * event );

private slots:
    void closeOrOpenTextEdit2(bool isClosing);

};

#endif // MAINWINDOW_H

main.cpp

#include <mainwindow.h>
#include <QPropertyAnimation>
#include <QApplication>
#include <QIcon>



MyWidget::MyWidget(QWidget * parent):QWidget(parent),m_deltaX(0)
{

  m_pushButton = new QPushButton(this);
  m_pushButton->setText(">");
  m_pushButton->setCheckable(true);
  m_pushButton->setFixedSize(25,25);
  //m_pushButton->setStyleSheet("background-color: yellow;");
  connect(m_pushButton, SIGNAL(clicked(bool)), this, SLOT(closeOrOpenTextEdit2(bool)));

  m_textEditor1 = new QTextEdit(this);
  m_textEditor1->setText("И рвется в пляс душа моя, головой я выбиваю дверцы...");

  m_textEditor2 = new QTextEdit(this);

  m_buttonLayout = new QVBoxLayout();
  m_buttonLayout->addWidget(m_pushButton);
  m_buttonLayout->addItem( new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding) );


  m_layout = new QHBoxLayout;
  m_layout->addWidget(m_textEditor1, 10);
  m_layout->addSpacing(15);
  m_layout->addLayout(m_buttonLayout);
  m_layout->setSpacing(0);
  m_layout->addWidget(m_textEditor2, 4);

  setLayout(m_layout);
  resize(800,500);
}

void MyWidget::closeOrOpenTextEdit2(bool isClosing)
{
    m_isClosed = isClosing;
    QPropertyAnimation *animation1 = new QPropertyAnimation(m_textEditor2, "maximumWidth");

    if(isClosing) //close the second textEdit
    {
        m_textEditor2->setMaximumWidth(m_textEditor2->width());

        int textEdit2_start = m_textEditor2->maximumWidth();

        m_deltaX = textEdit2_start;
        int textEdit2_end = 3;



        animation1->setDuration(250);
        animation1->setStartValue(textEdit2_start);
        animation1->setEndValue(textEdit2_end);


        m_pushButton->setText("<");

    }
    else //open
    {


        int textEdit2_start = m_textEditor2->maximumWidth();
        int textEdit2_end = m_deltaX;


        animation1->setDuration(250);
        animation1->setStartValue(textEdit2_start);
        animation1->setEndValue(textEdit2_end);


        m_pushButton->setText(">");
        //m_pushButton->setIcon()

    }

    animation1->start();

}


void MyWidget::resizeEvent( QResizeEvent * event )
{
    if(!m_isClosed)
        m_textEditor2->setMaximumWidth( QWIDGETSIZE_MAX );
}


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyWidget w;
    w.show();

    return a.exec();
}

In my rewritten code it doesn't want to start an animation. You ought to press the button twice and after that it will collapse the widget permanently without any animation. So you can't open it again.

Also i recieved some errors:

QPropertyAnimation::updateState (geometry, QTextEdit, ): starting an animation without end value

and

NameError: name 'QWIDGETSIZE_MAX' is not defined

Upvotes: 1

Views: 1894

Answers (1)

eyllanesc
eyllanesc

Reputation: 243887

Your code has the following errors:

  • The property that is modified in the C++ code is maximumWidth but in your python code you use geometry, in the case of maximumWidth an integer is expected but geometry expects a QRect so you get the error message since the endValue is of an incorrect type so it is not established by noting that it did not establish a value.

  • In Python a local variable is deleted when its scope ends, except in the case of PyQt if the local variable is a QObject that has as a parent another QObject with a larger scope. In the case of C++ in addition to using the parent to extend the life cycle is to use pointers.

  • QWIDGETSIZE_MAX is not defined in PySide2 (but it is defined in PyQt5: QtWidgets.QWIDGETSIZE_MAX) so you must define a variable that takes the value it takes in C++.

from PySide2 import QtCore, QtWidgets

# https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/kernel/qwidget.h#n873
QWIDGETSIZE_MAX = (1 << 24) - 1

class MyWidget(QtWidgets.QWidget):
    def __init__(self):
        super(MyWidget, self).__init__()
        self.m_deltaX = 0
        self.btn = QtWidgets.QPushButton(
            ">", checkable=True, clicked=self.closeOpenEditor
        )
        self.btn.setFixedSize(QtCore.QSize(25, 25))

        self.text1 = QtWidgets.QTextEdit()
        self.text1.setText("some sample text")

        self.text2 = QtWidgets.QTextEdit()

        layout_btn = QtWidgets.QVBoxLayout()
        layout_btn.addWidget(self.btn)

        lay = QtWidgets.QHBoxLayout(self)
        lay.addWidget(self.text1, 10)
        lay.addSpacing(15)
        lay.addLayout(layout_btn)
        lay.setSpacing(0)
        lay.addWidget(self.text2, 4)

        self.resize(800, 500)

        self.m_animation = QtCore.QPropertyAnimation(
            self.text2, b"maximumWidth", parent=self, duration=250
        )

    def closeOpenEditor(self):
        if self.btn.isChecked():
            self.text2.setMaximumWidth(self.text2.width())
            text2Start = int(self.text2.maximumWidth())
            self.m_deltaX = text2Start
            text2End = 3
            self.m_animation.setStartValue(text2Start)
            self.m_animation.setEndValue(text2End)
            self.btn.setText("<")
        else:
            text2Start = int(self.text2.maximumWidth())
            text2End = self.m_deltaX
            self.m_animation.setStartValue(text2Start)
            self.m_animation.setEndValue(text2End)
            self.btn.setText(">")

        self.m_animation.start()

    def resizeEvent(self, event: "QResizeEvent"):
        if not self.btn.isChecked():
            self.text2.setMaximumWidth(QWIDGETSIZE_MAX)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

Upvotes: 1

Related Questions