Access PyQt5 Objects from outside the MainWin Class - python

I am developing an application that reads MS Access DBs to produce extracts and data manipulation into SharePoint. One of my challenges is that I will need to write a lengthy GUI interface with many simple events. To keep the code organized I will be writing subroutines and import into the main program. As a result there will be code that needs to run outside the class and access objects inside the class. I am having a problem determining the proper syntax.
Or is there a way to import directly into the class to avoid the redefining issue?
In the InitUI there is a call to subroutine called TestIt() in that sub routine I print a message to verify the code is getting called. Then I try to set the text value of a Single Line Edit object from the main window. The question what is the proper way to call it? Do I need to redefine the variable.
The main program is this:
from PyQt5 import QtCore, QtGui, QtWidgets
from Main import Ui_MainWin
import sys, time, os, configparser
import pyodbc
from ServerQueue import *
class MainWindow_EXEC():
def __init__(self): # This section has to be laid out this way
app = QtWidgets.QApplication(sys.argv)
win = QtWidgets.QMainWindow()
self.ui = Ui_MainWin()
self.ui.setupUi(win)
self.initUI() # Inits that need to happen before start up
win.setWindowTitle("This is the Title!")
#win.resize(800,600)
win.show()
sys.exit(app.exec_())
def initUI(self):
TestIt()
self.ui.cmbAppCodes.currentIndexChanged.connect(self.selectionchange)
try:
self.conn = pyodbc.connect(r'Driver={Microsoft Access Driver
(*.mdb,*.accdb)};DBQ='+ dbPath + ';',autocommit=True)
self.cursor = self.conn.cursor()
except:
print("Error opening the database!")
qApp.quit()
print(dbPath)
self.loadAppCodes()
def loadAppCodes(self):
self.ui.cmbAppCodes.clear()
sql = "SELECT [App Code], [App Name] from [App List] ORDER BY [App Code];"
self.cursor.execute(sql)
res = self.cursor.fetchall()
for code in res:
self.ui.cmbAppCodes.addItem("{} - {}".format(code[0], code[1]))
def selectionchange(self,i):
App = self.ui.cmbAppCodes.itemText(i)
sql = "SELECT * FROM [LCM Application List] WHERE [App Code] = '" + App[0:3] + "';"
self.cursor.execute(sql)
res = self.cursor.fetchone()
self.ui.lneAPM.setText(res[3])
self.ui.lneTPM.setText(res[21])
self.ui.lneAPO.setText(res[4])
#-----------------------------------------------------------------------------
# Load up the init file
#-----------------------------------------------------------------------------
def iniSettings():
if sys.platform == "win32":
os.system('cls')
Home = os.path.expanduser('~') + "\\"
D = "W"
else:
os.system('clear')
Home = os.path.expanduser('~') + "/"
D = "O"
config = configparser.ConfigParser()
config.read(Home + 'LCM.ini') # Path to LCM.ini file
Configs = config['DEFAULT'] # Read the DEFAULT section
return [Home, Configs['LCMDB'], Configs['CR1'], D]
##########################################################################################
# Main Body
###########################################################################################
if __name__ == "__main__":
Paths = iniSettings() # This is to pull the ini data for paths and what not.
hmPath = Paths[0]
dbPath = Paths[1]
cr1Path = Paths[2]
myOS = Paths[3]
print("Home: {}\nCR1: {}\nDatabase: {}\n".format(hmPath, cr1Path, dbPath))
MainWindow_EXEC() # Becuase the init is laid out this way this is all that is needed
print("Application completed")enter code here
#This file is imported and is called ServerQueue.py
def TestIt():
print("TestIt")
self.ui.lneAPM.setText("Testing")
#-----------------------------------------------------------------------------
# Main Processing Loop
#-----------------------------------------------------------------------------
if __name__ == "__main__":
print("\n ********* \n This file is not meant to be run directly! \n ********* \n")

You are doing everything backwards. You are trying to get the elements of business logic to consume the GUI, but the correct logic is the reverse: The GUI consumes the information from the elements of business logic. Considering the above, the Test () function should not modify the GUI directly, but provide the information to the GUI, one way to implement that is to return the text.
def TestIt():
print("TestIt")
return "Testing"
# ...
def initUI(self):
self.ui.lneAPM.setText(TestIt())
# ...
If the above does not solve the problem then you probably have an XY problem so I recommend you to rewrite your question better providing a better MRE

Related

How to set path for exporting files from Maya using PySide2 ad Python

I'm learning Python while trying to develop helper tools for Maya using Python and PySide. (Quite ambitious but there is no progress without challenges, right)
Basically, I'm writing a tool that would help me export animation as fbx files to a folder I set in any given location on my PC.
I've finished to write the export process. There should be no issue here, I'm stuck with the UI part of it.
Currently this is how my UI is looking. I want to be able to set the path to the place where I want the script to export the files.
When I select the path and press "Select Folder", I want the path of the selected folder to display in the text line of the UI. And "Remember" it so that when I press Export FBX animation or Export rig it would use that path and would save files there.
But I have no clue how to do that. Can anyone help the clueless me how to make this happen?
I would appreciate any help. Thank you )
Here is my current code:
`
import CreatureAnimBakeProcess
reload(CreatureAnimBakeProcess)
from maya import cmds
import os
from PySide2 import QtWidgets, QtCore, QtGui
class CreatureAnimBakeUI(QtWidgets.QDialog):
def __init__(self):
super(CreatureAnimBakeUI, self).__init__()
self.setWindowTitle('Creature Exporter')
self.library = CreatureAnimBakeProcess.CreatureExport()
self.buildUI()
def buildUI(self):
print 'building ui'
layout = QtWidgets.QVBoxLayout(self)
setPathWidget = QtWidgets.QWidget()
setPathLayout = QtWidgets.QVBoxLayout(setPathWidget)
layout.addWidget(setPathWidget)
self.setPathField = QtWidgets.QLineEdit()
setPathLayout.addWidget(self.setPathField)
setBtn = QtWidgets.QPushButton('Set Export Folder')
setBtn.clicked.connect(self.setPath)
setPathLayout.addWidget(setBtn)
#============================================
btnWidget = QtWidgets.QWidget()
btnLayout = QtWidgets.QVBoxLayout(btnWidget)
layout.addWidget(btnWidget)
ExportFBXBtn = QtWidgets.QPushButton('Export FBX Animation')
ExportFBXBtn.clicked.connect(self.exportFbxAnim)
btnLayout.addWidget(ExportFBXBtn)
ExportRIGBtn = QtWidgets.QPushButton('Export RIG')
ExportRIGBtn.clicked.connect(self.exportRIG)
btnLayout.addWidget(ExportRIGBtn)
return
def getDirectory(self):
directory = os.path.join(cmds.internalVar(userAppDir=True), 'Animation')
if not os.path.exists(directory):
os.mkdir(directory)
return
def setPath(self):
directory = self.getDirectory()
pathName = QtWidgets.QFileDialog.getExistingDirectory(self, directory, "Creature Exporter")
return
def exportFbxAnim(self):
pass
def exportRIG(self):
pass
def showUI():
ui = CreatureAnimBakeUI()
ui.show()
return ui
`
You can use self.setPathField.setText(pathName) to show the value, you can also assign it to self.exportPath = pathName so you can re use it.
I am only showing code snippets where I changed, the remainder of your code needs no changes.
def __init__(self):
super(CreatureAnimBakeUI, self).__init__()
# have it assigned to None
self.exportPath = None # changed here
self.setWindowTitle("Creature Exporter")
self.buildUI()
def setPath(self):
directory = self.getDirectory()
pathName = QtWidgets.QFileDialog.getExistingDirectory(
self, directory, "Creature Exporter"
)
if pathName: # changed here
# "remember the value"
self.exportPath = pathName
# show it in the text line of the UI
self.setPathField.setText(pathName)
return
self.exportPath will be None initially, once you select a folder it will have that value saved. So this if checks if the value is not set and forces the user to set a value, if it is already set that value will be used.
def exportFbxAnim(self): # changed here
if self.exportPath is None:
# call self.setPath if self.exportPath is None
self.setPath()
# self.exportPath should have the value you expect
def exportRIG(self): # changed here
if self.exportPath is None:
# call self.setPath if self.exportPath is None
self.setPath()
# self.exportPath should have the value you expect

PyQt5 - stuck in a loop while trying to close ui using macOS

I have created a UI with PyQt5. I can use it on Windows and it works perfectly, but when I try to use it on MacOS I get stuck trying to close it (with self.close()). Using the PyCharm debugger I found out that after self.close() it jumps to app.exec_() and the function that was entered to close it is executed again (for example on_later_button_clicked(self)). I have also already tried sys.exit(app.exec_()).
Here is my code:
import os
import sys
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5.uic import loadUi
from Modules.database import addNeverID
from Modules.supportedWebsites import getWebsites
def Start():
m = askForPartnerUrl()
# m.setFixedSize(500,500)
m.show()
return m
class askForPartnerUrl(QDialog):
def __init__(self):
super(askForPartnerUrl, self).__init__()
loadUi('lib/askForPartnerURL.ui', self)
self.setWindowTitle('Upload')
current_id = getFromFile("id.txt")
self.show_id.setText(current_id)
self.show_origin_url.setText(
'' + getFromFile("origin_url.txt") + '')
self.show_origin_url.setOpenExternalLinks(True)
id_beginns = ["1"]
website_eq = ["1"]
website_guess_str = "Nicht verfügbar!"
for i in range(len(id_beginns)):
if id_beginns[i] in current_id:
website_guess_str = '' + website_eq[i] + ''
self.website_guess.setOpenExternalLinks(True)
break
self.website_guess.setText(website_guess_str)
self.save_button.clicked.connect(self.on_save_button_clicked)
self.later_button.clicked.connect(self.on_later_button_clicked)
self.never_button.clicked.connect(self.on_never_button_clicked)
try:
os.remove('temp/currentObject/partner_url.txt')
except:
pass
#pyqtSlot()
def on_never_button_clicked(self):
addNeverID(getFromFile("id.txt"))
saveToFile("Never-ID", "partner_url.txt")
self.close()
def on_later_button_clicked(self):
saveToFile("Later-ID", "partner_url.txt")
self.close()
def on_save_button_clicked(self):
url_is_valid = False
for i in getWebsites():
if i in self.partner_url_input.text():
url_is_valid = True
break
if url_is_valid:
saveToFile(self.partner_url_input.text(), "partner_url.txt")
self.close()
else:
error_dialog = QtWidgets.QErrorMessage(self)
error_dialog.setWindowTitle("Eingabe nicht verwertbar")
error_dialog.showMessage('Die eingegebene URL ist nicht verwendbar! Bitte prüfe deine Eingabe.')
def showGUI():
app = QApplication(sys.argv)
app.setStyle('Fusion')
app.setWindowIcon(QtGui.QIcon('lib/icon.png'))
window = Start()
app.exec_()
def saveToFile(content, filename):
file = open("temp/currentObject/" + filename, "w+")
file.write(content)
file.close()
def getFromFile(filename):
file = open("temp/currentObject/" + filename)
content = file.read()
file.close()
return content
Many thanks in advance
The reason is that since you're using uic, it automatically enables the auto-connection feature, which automatically detects function names based on object/signals names and connects them, even if the functions do not have Qt slots decorators.
The result is that your slot will be actually called thrice:
without any argument (clicked());
with the checked argument (clicked(bool)): the argument is ignored by Qt since the function doesn't take any, but the function will be called anyway because no slot signature has been specified for it;
again with the checked argument, because you manually connected it in your code;
If you want to keep using the auto connection, use a unique slot decorator for that specific function, otherwise manually connect to a function (possibly with a slot, if you need a specific signature) that does not use the auto connection naming, but don't use both.
class askForPartnerUrl(QDialog):
def __init__(self):
super(askForPartnerUrl, self).__init__()
loadUi('askForPartnerURL.ui', self)
# ...
# remove the following lines:
# self.save_button.clicked.connect(self.on_save_button_clicked)
# self.later_button.clicked.connect(self.on_later_button_clicked)
# self.never_button.clicked.connect(self.on_never_button_clicked)
# manual connection
self.later_button.clicked.connect(self.saveLater)
# using the auto connection; the function doesn't need arguments, so
# you can ignore the argument type signature
#pyqtSlot()
def on_never_button_clicked(self):
addNeverID(getFromFile("id.txt"))
# ...
# with a normal function; in this case no slot decorator is required since
# you don't have arguments
def saveLater(self):
url_is_valid = False
# ...
PS: The reason for which it gets "stuck" is probably due to the way Python deals with the end of the program (which by default happens as soon as the last window is closed in Qt) on MacOS: after the first call to close() PyQt tries to quit the QApplication (free up memory, etc...), but while doing so the original click event is still in the process of firing the signals to the remaining second and third slot, hence the "loop" (but it's not an actual loop, and the third slot never gets called because it's the second one that blocks everything).
Note that this is a big oversimplification, I'm not an expert in memory usage and low level programming, but this is fundamentally what's happening.

Python: How to create a separate module for the GUI class

This code is working fine. MyApp is the class doing all the work and MyGUI is the user interface showing and requesting data from MyApp.
class MyGUI(): # displays results from MyApp and sends request to MyApp (e.g. fetch prices new prices)
def __init__(self):
print("GUI running")
def user_request_price(self,ticker):
self.req_price(ticker)
# methods I request from MyApp
def req_price(self,ticker):
app.get_price(ticker)
# methods I receive from MyApp
def print_price(self,val,price):
print (val,":",price)
class MyApp(): # does a lot of stuff, e.g. fetch prices from a server
def __init__(self):
self.id = 0
self.gui = MyGUI() # start gui
# methods called by GUI
def get_price(self, ticker):
if ticker == "MSFT": price = 20.23
self.output_price(ticker,price)
# methods sent to GUI
def output_price(self,ticker,price):
self.gui.print_price(ticker,price)
if __name__ == "__main__":
app = MyApp()
app.gui.user_request_price("MSFT")
Now I want to put the GUI into a separate module so creating a module file gui.py and import that in the MyApp file:
from gui import *
and that's it. Where I struggle: how does gui.py look like and how can MyGUI() access MyApp methods? Is it wise to do this separation? Any other suggestions for structuring?
The gui.py file would just be
class MyGUI(): # displays results from MyApp and sends request to MyApp (e.g. fetch prices new prices)
def __init__(self):
print("GUI running")
def user_request_price(self,ticker):
self.req_price(ticker)
# methods I request from MyApp
def req_price(self,ticker):
app.get_price(ticker)
# methods I receive from MyApp
def print_price(self,val,price):
print (val,":",price)
Add the import to the top of myapp.py and everything should work fine.
I try and Seperate out my code into seperate files if it makes sense. It makes reading things much clearer.
in your gui.py
from myapp import MyApp
class MyGUI(): # displays results from MyApp and sends request to MyApp (e.g. fetch prices new prices)
app = MyApp()
def __init__(self):
print("GUI running")
def user_request_price(self,ticker):
self.req_price(ticker)
# methods I request from MyApp
def req_price(self,ticker):
app.get_price(ticker)
# methods I receive from MyApp
def print_price(self,val,price):
print (val,":",price)
And in your myapp.py (Notice the first line), And make sure both the files are in same directory or else you must change your import relatively.
from gui import MyGUI
class MyApp(): # does a lot of stuff, e.g. fetch prices from a server
def __init__(self):
self.id = 0
self.gui = MyGUI() # start gui
# methods called by GUI
def get_price(self, ticker):
if ticker == "MSFT": price = 20.23
self.output_price(ticker,price)
# methods sent to GUI
def output_price(self,ticker,price):
self.gui.print_price(ticker,price)
if __name__ == "__main__":
app = MyApp()
app.gui.user_request_price("MSFT")
Finally I did this - seems to be the best approach to have a clear separation and communication between app and gui.
Gui:
import queue
def __init__(self):
threading.Thread.__init__(self)
self.requests = queue.Queue() # request queue for App
self.start()
def queue_request(self,reqId,val):
self.requests.put([reqId,val])
APP:
import threading
import queue
def checkGUIQueue(self):
threading.Timer(1.0, self.checkGUIQueue).start() # check every 1 second
while not self.gui.requests.empty():
(id,value) = self.gui.requests.get()
... process request ...

How are Python command line arguments related to methods?

Everyone at Class too big and hard to add new features is completely unphased by the question, which somehow connects command line options to methods, but I can find no documentation for this. It's not optparse, or argparse, or sys.argv - the question implies some kind of direct relationship between methods and command line options. What am I missing?
There isn't any set-in-stone link between them. The question you link to appears to be a program that can do one of several different things, with command-line arguments switching between them. These things happen to be implemented in the program using methods.
It is implied by the question that they have used something like argparse to write the glue between these; but the use of methods is just an implementation detail of the particular program.
I simply use the class like this, what seems not to be a very good idea, because it is very hard to maintain once u got many commands.
class myprogram(object):
def __init__(self)
self.prepare()
def prepare(self):
# some initializations
self.prepareCommands()
def prepareCommands(self):
self.initCommand("--updateDatabase", self.updateDatabase)
self.initCommand("--getImages", self.getImages)
# and so on
def initCommand(self, cmd, func):
options = sys.argv
for option in options:
if option.find(cmd)!=-1:
return func()
# my commands
def updateDatabase(self):
#...
def getImages(self):
#...
if __name__ == "__main__":
p = myprogram()
EDIT1:
Here a cleaner way I just implemented:
myprogram.py:
from config import * # has settings
from commands import *
from logsys import log
import filesys
class myprogram(object):
def __init__(self):
log(_class=self.__name__, _func='__init__', _level=0)
log(_class=self.__name__, _func='__init__', text="DEBUG LEVEL %s" % settings["debug"], _level=0)
self.settings = settings
self.cmds = commands
def prepare(self):
log(_class=self.__name__, _func='prepare', _level=1)
self.dirs = {}
for key in settings["dir"].keys():
self.dirs[key] = settings["dir"][key]
filesys.checkDir(self.dirs[key])
def initCommands(self):
log(_class=self.__name__, _func='initCommands', _level=1)
options = sys.argv
for option in options:
for cmd in self.cmds.keys():
if option.find(cmd) != -1:
return self.cmds[cmd]()
if __name__ == '__main__':
p = myprogram()
p.prepare()
p.initCommands()
commands.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
commands = {}
#csv
import csvsys
commands["--getCSV"] = csvsys.getCSV
#commands["--getCSVSplitted"] = csvsys.getCSVSplitted
# update & insert
import database
commands["--insertProductSpecification"] = database.insertProductSpecification
# download
import download
commands["--downloadProductSites"] = download.downloadProductSites
commands["--downloadImages"] = download.downloadImages
# parse
import parse
commands["--parseProductSites"] = parse.parseProductSites
EDIT2: I have now updated my question you linked to your question with a more complete example Class too big and hard to add new features

How do I shut down PyQt's QtApplication correctly?

I don't know the first thing about Qt, but I'm trying to be cheeky and borrow code from elsewhere (http://lateral.netmanagers.com.ar/weblog/posts/BB901.html#disqus_thread). ;)
I have a problem. When I run test() the first time, everything works swimmingly. However, when I run it the second time, I get nasty segfaults. I suspect that the problem is that I'm not ending the qt stuff correctly. What should I change about this program to make it work multiple times? Thanks in advance!
from PyQt4 import QtCore, QtGui, QtWebKit
import logging
logging.basicConfig(level=logging.DEBUG)
class Capturer(object):
"""A class to capture webpages as images"""
def __init__(self, url, filename, app):
self.url = url
self.app = app
self.filename = filename
self.saw_initial_layout = False
self.saw_document_complete = False
def loadFinishedSlot(self):
self.saw_document_complete = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def initialLayoutSlot(self):
self.saw_initial_layout = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def capture(self):
"""Captures url as an image to the file specified"""
self.wb = QtWebKit.QWebPage()
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.loadFinished.connect(self.loadFinishedSlot)
self.wb.mainFrame().initialLayoutCompleted.connect(
self.initialLayoutSlot)
logging.debug("Load %s", self.url)
self.wb.mainFrame().load(QtCore.QUrl(self.url))
def doCapture(self):
logging.debug("Beginning capture")
self.wb.setViewportSize(self.wb.mainFrame().contentsSize())
img = QtGui.QImage(self.wb.viewportSize(), QtGui.QImage.Format_ARGB32)
painter = QtGui.QPainter(img)
self.wb.mainFrame().render(painter)
painter.end()
img.save(self.filename)
self.app.quit()
def test():
"""Run a simple capture"""
app = QtGui.QApplication([])
c = Capturer("http://www.google.com", "google.png", app)
c.capture()
logging.debug("About to run exec_")
app.exec_()
DEBUG:root:Load http://www.google.com
QObject::connect: Cannot connect (null)::configurationAdded(QNetworkConfiguration) to QNetworkConfigurationManager::configurationAdded(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationRemoved(QNetworkConfiguration) to QNetworkConfigurationManager::configurationRemoved(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationUpdateComplete() to QNetworkConfigurationManager::updateCompleted()
QObject::connect: Cannot connect (null)::onlineStateChanged(bool) to QNetworkConfigurationManager::onlineStateChanged(bool)
QObject::connect: Cannot connect (null)::configurationChanged(QNetworkConfiguration) to QNetworkConfigurationManager::configurationChanged(QNetworkConfiguration)
Process Python segmentation fault (this last line is comes from emacs)
You need to handle the QApplication outside of the test functions, sort of like a singleton (it's actually appropriate here).
What you can do is to check if QtCore.qApp is something (or if QApplication.instance() returns None or something else) and only then create your qApp, otherwise, use the global one.
It will not be destroyed after your test() function since PyQt stores the app somewhere.
If you want to be sure it's handled correctly, just setup a lazily initialized singleton for it.
A QApplication should only be initialized once!
It can be used by as many Capture instances as you like, but you should start them in the mainloop.
See: https://doc.qt.io/qt-4.8/qapplication.html
You could also try "del app" after "app.exec_", but I am unsure about the results.
(Your original code runs fine on my system)
I would use urllib instead of webkit:
import urllib
class Capturer:
def capture(self, s_url, s_filename):
s_file_out, httpmessage = urllib.urlretrieve(s_url, s_filename, self.report)
def report(self, i_count, i_chunk, i_size):
print('retrived %5d of %5d bytes' % (i_count * i_chunk, i_size))
def test():
c = Capturer()
c.capture("http://www.google.com/google.png", "google1.png")
c.capture("http://www.google.com/google.png", "google2.png")
if __name__ == '__main__':
test()

Categories