Графический интерфейс неожиданно закрывается

Я использую PyQT6, PyCharm и Python 3.11.

Очень кратко, я пытаюсь (мысленно) конвертировать из VBA в Python.

Я сделал графический интерфейс в pyQT6, назовем его test.ui. Он имеет несколько кнопок и таблицу. Я конвертирую это в файл Python с помощью утилиты pyuic6. Теперь у меня есть файл Test.py в той же папке. Пока все хорошо.

я добавить

self.mybutton.clicked.connect("buttonpushed")  

на скрипт Test.py и отметьте на

def buttonpushed(self):
  print("Button Pushed") 

Это работает отлично и как и ожидалось. Я намерен создать форму со множеством кнопок, поэтому вместо того, чтобы включать def в файл Test.py, который будет меняться каждый раз, когда я запускаю pyuic6, я перемещаю его в другой файл с именем ExtraBits.py и включаю его в Test.py со строкой

import ExtraBits

Он по-прежнему работает должным образом и печатает слова правильно.

Теперь моя проблема: Я хочу, чтобы эта кнопка скрывала таблицу в моем графическом интерфейсе. Поэтому я добавил

self.mytbl.hide() 

к процедуре buttonpushed в Test.py и она работает. Если я добавлю это в процедуру, когда она находится в ExtraBits.py, я все равно получу напечатанные слова, но таблица не скроется, и графический интерфейс закроется. Я убедил себя, что это связано с тем, что я неправильно ссылаюсь на объект mytable в импортированном файле ExtraBits.py, но не знаю, как это исправить.

Я перепробовал все возможные комбинации, включая имя объекта главного окна и т. д. Я не получаю никаких ошибок, просто hide() не скрывается, и графический интерфейс вылетает, но только если он находится в файле ExtraBits.py.

Переделал файл вминимальные.py и ExtraBits.py, чтобы попытаться подчеркнуть мою проблему.

from PyQt6 import QtCore, QtGui, QtWidgets
import ExtraBits

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.tableWidget = QtWidgets.QTableWidget(parent=self.centralwidget)
        self.tableWidget.setGeometry(QtCore.QRect(30, 20, 256, 192))
        self.tableWidget.setObjectName("tableWidget")
        self.tableWidget.setColumnCount(0)
        self.tableWidget.setRowCount(0)
        self.pushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(40, 260, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.pushButton.clicked.connect(ExtraBits.PushButton)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
    
        self.retranslateUi(MainWindow)
            QtCore.QMetaObject.connectSlotsByName(MainWindow)
    
        def retranslateUi(self, MainWindow):
            _translate = QtCore.QCoreApplication.translate
            MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
            self.pushButton.setText(_translate("MainWindow", "PushButton"))
    
    
    if __name__ == "__main__":
        import sys
        app = QtWidgets.QApplication(sys.argv)
        MainWindow = QtWidgets.QMainWindow()
        ui = Ui_MainWindow()
        ui.setupUi(MainWindow)
        MainWindow.show()
        sys.exit(app.exec())

и ExtraBits.py

def PushButton(self):
    print("Button pressed")
    self.tableWidget.hide()

Я действительно использую файл pyuic в качестве основного, я следил за видео на YouTube, которое делало это таким образом, и, поскольку оно работало с первого раза, я не стал еще больше усложнять ситуацию. Вы конечно правы, я тоже неправильно сделал разъем в тексте выше. Я напишу минимальный код и посмотрю, что получится...

perfo 12.06.2024 20:09

К сожалению, как и почти в любой области, на YouTube есть множество обучающих программ, которые являются просто дерьмом, опубликованными безрассудными людьми, считающими, что достаточно поиграть с чем-то в течение нескольких дней, чтобы стать «экспертами», полностью игнорируя свои обязанности. такое обучение предполагает отдание приоритета накоплению просмотров и подписчиков вместо ответственного обмена знаниями (что требует времени и терпения, что редко сочетается с ритмом подобных видеорелизов). Фактически, поверх любого файла, сгенерированного Pyuic, есть четкое предупреждение о его редактировании, и я »

musicamante 12.06.2024 20:36

» видел людей, явно предлагающих игнорировать это, что является крайне безответственным советом! Это предупреждение (которое, осмелюсь сказать, было добавлено по моему собственному предложению) существует по важной причине, а именно для предотвращения многих проблем, которые обычно возникают у новичков, когда они начинают редактировать такие файлы, что также может быть в вашем случае. . Это распространенная ошибка, понятная неосведомленным новичкам, которые могут не осознавать последствий своих действий в собственном коде, но непростительная для таких «создателей контента», которые игнорируют последствия своих действий для зрителей.

musicamante 12.06.2024 20:36

@ musicamante Интернет — это самая большая библиотека, которую вы когда-либо могли пожелать, но в ней нет библиотекарей, которые следили бы за порядком. Я многому узнаю из видеороликов на YouTube, это потрясающий ресурс, но, как вы правильно выразились, контроль над тем, что рассказывается, практически отсутствует, и учащиеся могут легко пойти по неправильному пути. На самом деле я предпочитаю обучение в классе.. < < Я просто старый, наверное :-)

perfo 12.06.2024 22:42

Это хорошая предпосылка. Осведомленность о «цели» преподавания и наши собственные возможности как «делителей знаний» еще более важны. Одна из главных проблем нынешнего «голода славы» заключается в том, что он часто вызывает безответственные ошибочные представления. Не то чтобы их не было раньше, но нынешнее состояние социальных сетей еще больше подчеркивает эту проблему. Настоящая проблема, как всегда было и, вероятно, всегда будет, заключается не в том, как люди учатся, а в том, как те, у кого они учатся, учатся самостоятельно. Хорошее обучение (следовательно, преподавание) демонстрирует обоснование метода, а не решение.

musicamante 13.06.2024 04:51

Я думаю, мы согласны с этим, тогда musicamante. Есть какие-нибудь предложения, куда я могу пойти, чтобы узнать, как заставить мой код работать? Я надеялся, что это будет очень простая вещь для новичка - неправильная ссылка на объект, но, поскольку ответов не было, мне интересно, не сложнее ли это. Я прочитал все по той ссылке, которую вы мне дали, но это не пролило никакого света на мою проблему. Ну, если я не пропустил это, я не думаю, что это произошло.

perfo 13.06.2024 20:09
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
7
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Проблема заключается в том, что все, что делается в ExtraBits, не имеет абсолютно никакой ссылки на контекст, из которого оно вызывается.

В частности, ваша попытка использовать self ошибочна:

def PushButton(self):
    print("Button pressed")
    self.tableWidget.hide()

Это по двум причинам:

  • использование self на самом деле является соглашением; на самом деле это может быть любое допустимое имя Python, и его цель — дать ссылку на сам экземпляр, и оно неявно задается и устанавливается, когда функция в классе вызывается из ее экземпляра, что делает ее методом; см. этот пост по теме, чтобы узнать цель self;
  • учитывая вышеизложенное, фактическое значение self не то, что вы думаете: на самом деле это аргумент сигнала, который в случае QAbstractButton.clicked является его проверенным состоянием (по умолчанию False);

Вышеупомянутое происходит потому, что PushButton() — это стандартная функция, а не метод экземпляра, поэтому для нее нет предопределенного self аргумента, указывающего экземпляр: это функция, экземпляра нет.

Учти это:

def someFunction(self, *args):
    print('hello', self)

class Test(object):
    def someFunction(self, *args):
        print('hello', self)

someFunction('world')
Test().someFunction('world')

Хотя функции вызываются с одним и тем же аргументом, стандартная функция на самом деле выведет hello world, а вторая, являющаяся методом экземпляра, выведет hello, за которым следует представление адреса памяти экземпляра Test() (что-то вроде <__main__.Test object at 0xdeadbeef>).

Таким образом, при использовании сигналов с аргументами любой позиционный аргумент, который принимает функция, устанавливается со значениями аргументов сигнала.
Измените свою функцию на следующее:

def PushButton(self):
    print(self)

И оно напечатается False. Это последняя проблема вашего кода: поскольку self является логическим значением, у него, очевидно, нет атрибута tableWidget. Это вызывает неуправляемое исключение AttributeError, что приводит к сбою программы.

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

Итак, как это решить?

Одним из вариантов может быть использование лямбды и «отправка» экземпляра в качестве аргумента функции:

self.pushButton.clicked.connect(lambda: ExtraBits.PushButton(self))

Хотя этот подход технически обоснован, он сопряжен с важными проблемами; среди них:

  • вам всегда нужна лямбда-выражение, что делает отключение одной и той же функции трудным, если не невозможным;
  • экземпляр всегда должен быть отправлен явно;

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

На самом деле, всего этого можно легко избежать, если использовать правильный подход, который заключается в том, чтобы никогда не редактировать файлы pyuic, а вместо этого следовать официальным рекомендациям по использованию Designer: вместо этого следует использовать настоящий, отдельный основной скрипт, с пьюическими классами, которые в конечном итоге были импортированы в него.

Используйте следующий код в качестве основного сценария вашей программы и перестройте файл пользовательского интерфейса с помощью pyuic (в данном случае я назвал его TestUi.py, что и используется для второго оператора import):

from PyQt6.QtWidgets import *
from TestUi import Ui_MainWindow

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.pushButton.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        self.tableWidget.hide()


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())

Я настоятельно рекомендую вам потратить время на изучение фундаментальных аспектов ООП, включая классы, экземпляры и то, как работают их члены и методы.

Некоторые надежные руководства по PyQt можно найти в официальной вики Python и на действующих веб-сайтах ресурсов Python, таких как realpython.com и pythonguis.com.

Спасибо за подробный и очень полезный ответ. Это не только работает (что всегда хорошо :-)) но и привело к тому, что я стал смотреть на это по-другому. Это кажется очень разумным подходом к тому, что я пытался сделать. В диких землях YouTube, похоже, также ведутся дебаты о том, чтобы оставить формат пользовательского интерфейса и импортировать или преобразовать .UI в .PY, как мы это сделали здесь. Я собираюсь немного почитать. Еще раз спасибо, музыкант.

perfo 14.06.2024 15:06

@perfo Всегда пожалуйста. Что касается выбора между файлами pyuic и динамической загрузкой пользовательского интерфейса, то это зависит от многих факторов, и оба варианта имеют свои плюсы и минусы. Например, подход pyuic работает быстрее во время выполнения, поскольку после импорта он ведет себя так же, как любой другой класс Python, а загрузка файла пользовательского интерфейса требует его анализа каждый раз, когда создается новый экземпляр. OTOH, использование файлов пользовательского интерфейса является более быстрым, поскольку вам не нужно заново создавать файлы Python для любых изменений, внесенных в Designer (иногда вы просто об этом забываете). Если производительность загрузки не является проблемой, я обычно предпочитаю (и предлагаю) подход с пользовательским интерфейсом.

musicamante 14.06.2024 17:13

Понял, спасибо

perfo 14.06.2024 17:22

Другие вопросы по теме