How to access components from another module with PyQt6? - python

I made a minimum working environment for this question.
First I have this UI file (GDrive Link),which I'll later load in my UI.py., which I'll later load in my
The UI.py goes thus:
import sys
from PyQt6.QtWidgets import QMainWindow, QApplication, QPlainTextEdit
from PyQt6 import uic
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.ui = uic.loadUi(r"C:\Documents\Qt Designer\mwe.ui") # Change it to yoour own .ui
self.txtBox = self.findChild(QPlainTextEdit, 'plainTextEdit')
self.ui.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
import main
sys.exit(app.exec())
Essentially, what I want to do is to access this PlainTextEditor in main.py, becuase that's where I'll write the functions.
import UI
UI.Window.txtBox.insertPlainText('foo')
But when I try to run UI.py, I get this error:
Traceback (most recent call last):
File "C:\Users\Me\PycharmProjects\dynamic_ui_foo\UI.py", line 18, in <module>
import main
File "C:\Users\Me\PycharmProjects\dynamic_ui_foo\main.py", line 3, in <module>
UI.Window.txtBox.insertPlainText('foo')
AttributeError: type object 'Window' has no attribute 'txtBox'
It says Window doesn't have this attribute. How do I access the components from another module? And am I going in the right way by separating UI codes and function codes (I know the cross-importing looks terrible).

To illustrate your error message, consider the following example:
class bla:
def __init__(self):
self.foo = 'test'
print(bla.foo) # <-- results in your error
b = bla()
print(b.foo) # <-- this is what you would like to access
Right now you are trying to access txtBox of the class Window, but you need to access txtBox from your instance window.
However, I have doubts about it working in the way you do your imports.
I would suggest to move
if __name__ == "__main__":
app = QApplication(sys.argv)
window = UI.Window()
sys.exit(app.exec())
to main.py. Use UI.py to only define the layout. Changing the text txtBox can be implemented either as a method of Window:
class Window(QMainWindow):
# init code
def change_content(self, content):
self.txtBox.insertPlainText(content)
Then in main you call that:
if __name__ == "__main__":
app = QApplication(sys.argv)
window = UI.Window()
window.change_content()
sys.exit(app.exec())
Of course you can use a more direct approach:
if __name__ == "__main__":
app = QApplication(sys.argv)
window = UI.Window()
window.txtBox.insertPlainText(content)
sys.exit(app.exec())
The last example seems to be easier. But that way, if you change txtBox to something else you need to keep in mind to also do changes in main.py. With the first example you only have to do changes in UI.py.
Edit:
Added missing argument self. Thanks to musicamante for pointing that out.

Related

How to Import Pyqt5 Widget In Another File

First File first.py
import pyqt5py
ret=pyqt5py.confirm()
print(ret)
Second File Having PYQT5 name: pyqt5py.py
import sys
from PyQt5 import QtWidgets, uic
class Ui(QtWidgets.QDialog):
def __init__(self,button1='Ok',button2='Cancel',text='Are You Sure?'):
super(Ui, self).__init__() # Call the inherited classes __init__ method
uic.loadUi('dialog.ui', self) # Load the .ui file
# Show the GUI
self.pushButton1.clicked.connect(lambda: self.click(1))
self.pushButton2.clicked.connect(lambda: self.click(2))
self.label.setText(text)
self.pushButton1.setText(button1)
self.pushButton2.setText(button2)
self.show()
def click(self,args):
print(self)
return self.sender().text()
app = QtWidgets.QApplication(sys.argv) # Create an instance of QtWidgets.QApplication
def confirm():
def pressed():
return 'clicked'
window = Ui(button1='Ok',button2='Cancel',text='Are You Sure?') # Create an instance of our class
print(window)
window.pushButton1.clicked.connect(pressed)
app.exec_() # Start the application
but i dont know what changes should i do make my first.py to work,i have correctly made the pyqt5 file but i dont know how to add def to call it for confirm
#######################
I Updated My Second File
As suggested by bfris you should rewrite the last lines of pyqt5py.py as follows:
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Ui(button1='Ok',button2='Cancel',text='Are You Sure?')
app.exec_()
That way you can run this file directly for debbugging purposes, but also import it elsewhere.
To use your widget in first.py you need to create an instance of it there.
first.py:
from pyqt5py.py import UI
app = QtWidgets.QApplication(sys.argv)
window = Ui(button1='Ok',button2='Cancel',text='Are You Sure?')
app.exec_()
Usually I use a QDialog within a Qt environment where it is opened from a QMainWindow and also returning which button was clicked to the QMainWindow.
However as I understand you would like to run another program and in between open your UI? I am not experienced doing that but it seems to me that its exec method does exactly that though you should read this discussion about a bug related to it.
Alternatively in first.py you connect the pushbutton's clicked signal to a slot, a function there.

Loading python module with GUI

I have a organization setup question I need help with. Here is my current folder structure.
What i want to do is run the Main UI with the specified AppCommands module that contains functions. Based on which application i want to run the tool. Is there a way using another python file, where i can load the gui and the associated app commands moduel? So when users click the button it calls the corrects app command.
So say for example I create a python file like this pseudo code
main execute py file for Photoshop
Import photoshop.appcommands as cmds
Import GUI
Gui(cmds)
How do I then tell my main GUI tool to load the photoshop modules 'AppCommands' when it runs?
app #1 code:
def runTool():
msg = 'This is Notepad'
print msg
app #2 code:
def runTool():
msg = 'This is Photoshop'
print msg
Main ui code:
import sys
import os
from PySide import QtGui, QtCore
import AppCommands as cmds
class MainWindow(QtGui.QMainWindow):
def __init__(self,parent=None):
super(MainWindow, self).__init__(parent)
self.uiButton = QtGui.QPushButton('Button', self)
# layout
grid = QtGui.QGridLayout()
grid.addWidget(self.uiButton, 3, 1)
main_widget = QtGui.QWidget()
main_widget.setLayout(grid)
self.setCentralWidget(main_widget)
self.uiButton.clicked.connect(self.browse_clicked)
# actions
def browse_clicked(self):
print 'Execute Command'
cmds.runTool()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
Well, if I understood correctly, you need to choose the right module to load a function after then you started the program with some variable args?
In this case you may import both and try globals:
import notepad.AppCommands
import photoshop.AppCommands
...
# in your app init code:
x = 'photoshop' # an arg passed from config/CLI or from somewhere else
actual_module = globals()[x]
actual_module.AppComands.runTools()
++ But if you just don't know how to run a certain function from a certain module, follow #eyllanesc's answer.

PySide \ PyQt: How to return value to UI after button click?

I am trying to return a path from a browse folder dialog box.
I have tried passing the instance object or even attribute to the call and setting it there:
self.browseBtn.clicked.connect(myExporter.browseFolder(self))
or
self.browseBtn.clicked.connect(myExporter.browseFolder(self.path))
But this doesn't work. It causes the browser dialog to pop open immediately upon load and then once you choose a folder it errors out with : Failed to connect signal clicked().
I have tried to set the clicked call to a return, with no luck:
result = self.browseBtn.clicked.connect(myExporter.browseFolder)
Can someone lead me in the right direction as far as how to return a value, when you are dealing with separate classes handling the UI and logic? Also... is it bad practice to be separating them like this? I know I could probably easily solve this if I threw everything into just one python file, but I figured that is not proper.
Here is my ui file (ui.py):
from PySide import QtCore, QtGui
class Ui_Dialog(object):
def __init__(self):
self.path =""
def setupUi(self, Dialog, myExporter):
Dialog.setObjectName("Dialog")
Dialog.resize(382, 589)
...
.......
.............
.................
self.retranslateUi(Dialog)
self.tabWidget.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(Dialog)
self.browseBtn.clicked.connect(myExporter.browseFolder)
Here is my exporter file (exporter.py):
class Exporter(object):
def __init__(self):
...
......
def browseFolder(self):
...
.......
do something
...........
return path
Here is my load/test file (loadExporter.py):
import ui as interface
import exporter as exporter
from PySide import QtCore, QtGui
app = QtGui.QApplication.instance()
if app is None:
app = QtGui.QApplication(sys.argv)
Dialog = QtGui.QDialog()
myExporter = exporter.Exporter()
myUI = interface.Ui_Dialog()
myUI.setupUi(Dialog, myExporter)
Dialog.show()
app.exec_()
It's not necessarily bad to have them in separate files. Having a separate file for certain widgets is a good thing especially if those widgets can be reused.
I would have my main file have a QMainWindow class.
class MyWindow(QtGui.QMainWindow):
pass
if __name__ == "__main__":
QtGui.QApplication([])
mywindow = MyWindow()
mywindow.show()
sys.exit(QtGui.qApp.exec_())
Wrapping the app functionality in if __name__ == "__main__" prevents this code from being run when another file tries to import this file.
A signal (self.browserBtn.clicked) calls a callback method. Everything is an object in python.
def my_func():
pass
new_func_name = my_func # my_func can be reassigned like any variable
my_func is an object. self.browseBtn.clicked.connect(my_func) passes my_func as a variable to be called later.
When self.browserBtn.clicked.emit() happens (on user click) it is the same as calling the connected functions my_func(). Other signals may pass values to callback functions self.lineEdit.textChanged.connect(my_func) calls 'my_func(new_text)'
You want your function to call everything for you.
def open_file(filename=None):
"""Open a file."""
# If a filename isn't given ask the user for a file
if filename is None:
filename, ext = QtGUi.QFileDialog.getOpenFileName(None, "Open File", ".../My Documents/")
# You may have to use the ext to make a proper filename
# Open the file
with open(filename, "r") as file:
file.read()
self.browserBtn.clicked.connect(open_file)
Structure
...
import mywidget
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super().__init__()
...
self.show_file = QtGui.QLineEdit()
self.setCentralWidget(self.show_file)
self.exporter = mywidget.Exporter()
self.browserBtn.clicked.connect(self.open_file)
def open_file(self, filename=None):
"""Open a file."""
path = self.exporter.browseFolder()
# Do something with the path
self.show_file.setText(path)
if __name__ == "__main__":
QtGui.QApplication([])
mywindow = MyWindow()
mywindow.show()
sys.exit(QtGui.qApp.exec_())
I don't think I fully understand what you're trying to achieve, but may I suggest the following solution.
exporter.py
# implementation
dialog.py (Main UI)
import PyQt4.QtGui as qg
from exporter import Exporter
class Dialog(qg.QDialog):
def __init__(self):
super().__init__()
self.path = None
self.setup_widgets()
def setup_widgets(self):
self.browse.clicked.connect(self.on_browse_clicked)
def on_browse_clicked(self):
self.path = Exporter().browse_folder()
main.py
import sys
import PyQt4.QtGui as qg
from dialog import Dialog
if __name__ == '__main__':
app = qg.QApplication(sys.argv)
dialog = Dialog()
dialog.show()
sys.exit(app.exec_())
This way you still have three files, but dialog.py imports exporter.py instead of main.py importing both of them.
As for returning values from signals, I don't know; I think signals are voids (do not return values).
Signals pass arguments to slots via their formal parameters. Usually slots have the same or fewer parameters than their signal counterparts'. It's slots that return values.

Ui_MainWindow has no attribute 'show'

I'm writing a program which will have multiple windows. I have a main program (attached) that calls the Ui files (that have been converted into .py). The main window and customise window open correctly (the first two listed) but neither the third or fourth windows open correctly, giving me the error
'Ui_MainWindow' object has no attribute 'show'
The main program;
from PyQt4 import QtCore, QtGui
import sys
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainwin = main_menu_ui.Ui_MainWindow()
mainwin.show()
sys.exit(app.exec_())
def openCustomise(self):
customiseOpen = question_set_menu_ui.Ui_MainWindow()
customiseOpen.show()
sys.exit(app.exec_())
def openQuiz(self):
quizOpen = quiz_window_ui.Ui_MainWindow()
quizOpen.show()
sys.exit(app.exec_())
def addNewSet(self):
addNewOpen = question_set_edit_ui.Ui_MainWindow()
addNewOpen.show()
sys.exit(app.exec_())
Sorry if I'm missing something obvious, I'm learning Qt/Python for college.
The auto-generated UI class that you are importing extends object and doesn't have a show method (open up the .py file for yourself and verify this).
In general, you should structure your GUIs like this:
from PyQt4 import QtCore, QtGui
import sys
from layout_file import main_menu_ui
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = main_menu_ui()
self.ui.setupUi(self)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainwin = MyForm()
mainwin.show()
sys.exit(app.exec_())
You import your UI from your autogenerated UI file. You have a class that contains your GUI logic. It then sets up your UI layout from your imported UI in its __init__() method.

How to call multiple Dialogs in PyQt?

I have a main dialog and on that dialog there is a button. When the button is clicked, I want to open an another dialog.
Main Dialog code (Function which is called when the button is clicked in the main dialog):
def add_host(self):
x=add_host.Ui_Dialog1()
x.main()
default function:
if __name__ == "__main__":
import sys
global app
app = QtGui.QApplication(sys.argv)
Dialog = QtGui.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
Secondary dialog (add_host.py) code snapshot:
def main(self):
app1 = QtGui.QApplication(sys.argv)
Dialog1 = QtGui.QDialog()
ui1 = Ui_Dialog1()
ui1.setupUi1(Dialog1)
Dialog1.show()
sys.exit(app.exec_())
So when I run the code, it opens the secondary dialog but when I close it, it just freezes, and I get this error message:
File "testbot.py", line 175, in add_host
x.main()
File "/home/ppp/ppp/add_host.py", line 74, in main
sys.exit(app.exec_())
NameError: global name 'app' is not defined
Which does make sense, but I have no idea how to resolve it. I try several combinations without success, including adding and deleting app.exec_().
You cannot create multiple QApplications inside of the same script and thread. You are only supposed to have one...
This should be more like:
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
Dialog = QtGui.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
No global app. Although you should be doing your setupUI from within the class of your dialog.
Then when you want to show another dialog from with your app...say main(), you just create it and call show()
Here is a really basic example:
class Dialog(QDialog)
def __init__(self, parent):
super(Dialog, self).__init__(parent)
self.otherDialog = QDialog(parent=self)
self.otherDialog.show()
if __name__ == "__main__":
app = QApplication([])
dialog = Dialog()
dialog.show()
app.exec_()
You create a single QApplication and start its event loop by calling exec_(). From that point on, your main application is free to create more QWidgets. You never create another QApplication again at this point.
Also, I dont understand this part of your code:
def add_host(self):
x=add_host.Ui_Dialog1()
x.main()
The fact that you are calling a main() method on your UI object makes me think you are modifying the UI file and adding functionality to it, which you should not be doing. That ui file is subject to be overwritten every time you make changes in QT Designer and save out a new one. You should only be importing it and using its setupUI() method to apply it to your own custom classes.
A note about organization of your modules
When you are designing a PyQT application, you will always have a single entry point that will be called to start your app. This is the single and only location that should be creating your QApp and starting the event loop, and is usually done with in a if __name__ == "__main__" block to ensure its only done when its the main script. It should not be done within methods of your objects. For all your other modules where you define other Dialog, Widgets, etc, these should simply be classes that you import. As long as you have a running QApp, you are free to create and show these widgets.
Your code sample is a bit confusing - I don't understand why you have two mains, etc - anyway, maybe it's just a typo in add_host.py (app1.exec_() instead of app.exec_())
def main(self):
app1 = QtGui.QApplication(sys.argv)
...
sys.exit(app1.exec_())

Categories