Update status bar in main window from another script pyqt - python

I'm creating a GUI to run a fatigue analysis program in Python using PyQt. When the user clicks 'Start' in the main window to start the analysis a function in another file is called, which then chugs away (potentially for hours) until complete.
I'd like to find a way to update the status bar in the main window from within the separate fatigue analysis file. I don't care how I achieve this, but I'd really rather not just copy and paste everything from the fatigue analysis file into the GUI file (in which case I'd know how to update the status bar).
So far I've tried starting the GUI using the code below which is global (MainWindow is a class with all the stuff to set up the GUI, and updateStatus is a method within that class to update the status bar of the GUI):
app = QtGui.QApplication(sys.argv)
TopWindow = MainWindow(LastData)
TopWindow.show()
sys.exit(app.exec_())
In the Fatigue Analysis file I have the following code, which is called within a loop:
TopWindow.updateStatus(PercentComplete)
This throws the error below:
NameError: global name 'TopWindow' is not defined
I don't understand why this doesn't work, but it definitely doesn't! Any ideas for a workaround?
Thanks,
Pete

I believe you misunderstood the scope of a variable. Let me explain.
When you write the following code,
def myfunction():
a = 5
print(a) # OK
b = a # This line fails
you will get a failure, because a is local to myfunction, that is to say it only exists inside that function, and the last statement refers to a variable that is not known to python. The same holds for modules. Variables defined in one module are not accessible to the other modules... Unless you really want to do crappy things.
A nice solution is to think your modules as a main one, calling functionalities from the other ones. These functionalities can be classes or functions, in other words, you will pass the reference to your main window as an argument.
Main module
import fatigue_analysis
class MainWindow(QtGui.Window):
# ....
def start_file_compute(self, path_to_file):
# Pass a reference to self (MainWindow instance) to the helper function
fatigue_analysis.start(path_to_file, self)
# ....
fatigue_analysis.py
def start(path_to_file, top_window):
# ....
top_window.updateStatus(PercentComplete)
# ....
That said, you may consider using QtCore.QThread to run your long computation while leaving the GUI responsive. In this case, beware that Qt do not like very much that one thread tries to modify a member of another thread (i.e. you main window). You should use signals to pass information from on thread to another.

You can do the following:
In the MainWindow class, you should first create a statusbar: self.statusbar = self.statusBar()
Then, from the analysis code, pass a reference to the MainWindow object and from there you can update the statusbar as you wish using this:
main_window_object.statusbar.showMessage(PercentComplete)
EDIT: I do not know what the LastData is, but you would need to inherit from the QtGui.QMainWindow ideally.

Related

QLayout Additem - prevent transfer of ownership

I am having problems with the object references when using the QLayout to arrange my widgets in on bigger window next to each other.
I have the following situation
class MyClass(QObject):
widgetCollection = []
def spawn(self):
widget = MyQWidget() #containing a QLineWidget called "nameEdit"
self.widgetCollection.append(widget)
self._window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
listView = QtGui.QListWidget()
for equation in self.wigdetCollection:
equationName = equation.nameEdit.text()
item = QtGui.QListWidgetItem(equationName)
listView.addItem(item)
layout.addWidget(listView)
layout.addWidget(widget)
self._window.setWindowTitle("Equation Editor")
self._window.setLayout(layout)
self._window.show()
def respawn(self):
self.spawn()
Each time I call spawn(), I want to add a new widget to the collection. Addionally, I want to open a new window where there is a ListView with all widget names on the left and the newly created widget on the right.
Now the first call to the spawn()-method works like expected. But the second call throws an exeption:
equationName = widget.nameEdit.text()
RuntimeError: wrapped C/C++ object of type QLineEdit has been deleted
I think it has something to do with the line
layout.addWidget(widget)
I have read somewhere that the layout takes ownership of the widget when added as an item. With that I loose the widget as soon as I am out of the scope of the layout-reference (which is local in this case). So it seems that the widget-item in my collection gets deleted too.
Can someone help? How do I prevent that.
The problem is that self._window is replaced every time spawn() is closed: Python discards the old window widget, and self._window is made to reference a new QWidget instance. Since the old window widget is parent to a list view and qlineedit widget (called -- confusingly -- just "widget" in the code), these will be destroyed in the Qt sense of the term -- i.e. their C++ portion will be destroyed. The list view is not referenced anywhere else in the shown code so the Python portion of the list view will be destroyed too, and this is the way it should be.
HOWEVER, the qlineedit is referenced in the class-wide registry (widgetCollection) and so the Python portion of the qlineedit will NOT be destroyed. This means that every time spawn() is called, the previous instance of QLineEdit (via MyQWidget in class-wide widgetCollection) becomes a zombie: it is dead at the C++ Qt level, but it is still alive at the Python level. A zombie's methods can still be accessed, but in many cases you will see the Qt error about C/C++ having been deleted, and you may get a crash or other undefined behavior.
It is not clear from the code posted what is the intent of the widgetCollection, but since the previous item appended will become a zombie during a spawn(), widgetCollection is useless as shown. The solution to the problem depends on details about how the collection is used, but since one can presume that the collection has a purpose, then the issue is the replacement of the window widget: perhaps the widget collection should be a window collection, then when self._window is replaced, the previous window stays alive so the child widgets stay alive too.
Solved it by myself :-)
My initial call to the spawn()-method was like that:
mc = MyClass()
mc.spawn()
First one okay. Now I wanted to spawn another from within inst1. For that I have used
self.spawn()
in the respawn()-method throwing the above mentioned error.
It has to be the following
def respawn(self):
mc = MyClass()
mc.spawn()
I had to create another instance of MyClass that shares the widgetCollection with all other instances. As desired....

Python Tkinter - How do I change variables from outside the main window class?

Now i understand the concept of instance variables and classes, I've never had a problem with them before and I use them frequently. However when I make my MainWindow class, everything is peachy until i try accessing instance variables.
http://pastebin.com/tDs5EJhi is the full code, but at this point it's just placing labels and frames and whatnot, no actual logic is going on. The window looks fine and nothing bad happens.
My question comes to be when I try changing things inside of the window externally. I figured I could just make an instance of the class and change variables from there (namely instancevariable.ImageCanvas.itemconfig()) like i can normally, but Tkinter isn't being nice about it and I think it's a result of Tkinter's mainloop().
Here's the tidbit of my class MainWindow() that i'm having trouble with (ln 207)
...
self.C4 = Tk.PhotoImage(file="temp.png")
self.card4 = self.CardCanvas.create_image(120,46,image=self.C4, state=Tk.NORMAL)
#self.CardCanvas.itemconfig(4, state=Tk.HIDDEN) # < It works here
...
self.root.mainloop()
window = MainWindow()
window.CardCanvas.itemconfig(4, state=Tk.HIDDEN) # < It doesn't work here
That's how i learned how to edit instance variables. When the window pops up, the itemconfig command doesn't actually apply like it would were it inside the class (or maybe it did and the window just didn't update?) and after closing the window I get this error:
_tkinter.TclError: invalid command name
which I assume is just because it's trying to apply a method to variables that don't exist anymore, now that the window has closed.
So I guess here's my big question - I have a MainWindow class, and from what I can tell, nothing can be changed from outside of the class because the Tk.mainloop() is running and won't stop to let other code after it run, like the itemconfig. How do I go about changing those variables? Code after the instance variable declaration doesn't seem to run until the MainWindow() is closed.
You are correct that code after mainloop doesn't run. It does, but only after the GUI has been destroyed. Tkinter is designed for the call to mainloop be the last (or very nearly last) line of executable code. Once it is called, all other work must be done as reaction to events. That is the essence of GUI programming.
The answer to "how do I go about changing the variables" is simple: do it before you call mainloop, or do it in reaction to an event. For example, do it in a callback to a button, do it in a function bound to an event, or to a time-based event via after, and so on.

Calling a class method in Python/PySide

I spent longer than I'd care to admit think of a suitable 'question' heading for this topic, as my issue is somewhat hard to articulate.
Here is a quick summary of the situation:
I'm writing a basic GUI with Python 3.4 and PySide
I'm using QFileSystemWatcher to monitor a particular file
When the file is changed, QFileSystemWatcher calls a method, which in turn calls a method within a PySide Class
All of the above seems to be working perfectly, except the GUI-specific actions detailed in the PySide Class method aren't being executed (I'll explain in more detail below).
Example code:
#Establishing the PySide GUI Class
class GUI(QMainWindow, Ui_GUI):
def __init__(self, parent=None)
super(GUI, self).__init__(parent)
self.setupUi(self)
QtCore.QObject.connect(self.Button, QtCore.SIGNAL("clicked()"), self.Run)
def Run(self):
print("1")
self.treeWidget1.clear()
self.treeWidget2.clear()
print("2")
self.label1.setText("Text 1")
self.label2.setText("Text 2")
print("3")
for y in range(0, 5):
self.treeWidget1.resizeColumnsToContents()
print("Finished")
#Establish the file monitoring mechanism, *outside* the PySide class
def FileChanged():
Script = GUI()
Script.Run()
Paths = ['path/to/file']
Watch = QtCore.QFileSystemWatcher(Paths)
Watch.fileChanged.connect(FileChanged)
#Setting up the GUI
if __name__ == '__main__':
app = QApplication(sys.argv)
showGUI = GUI()
showGUI.show()
app.exec_()
As I mentioned above, the above code doesn't return any errors. When I change the file (listed in the path), FileChanged does indeed call the Run() method from the GUI class. However, it won't actually do any of the 'stuff', it will only execute the print commands in between the 'stuff'.
If I then click on the 'Button' in the GUI, it will execute Run() correctly, and properly execute all the 'stuff'.
My question: is there something I'm missing here? If it's calling the method correctly, and is able to execute the various 'print' commands, why is it not executing the actual 'stuff'?
Thanks!
EDIT 1: I've removed the -do stuff- tags and put in some example code. All the 'stuff' code relates to updating various PySide QLabels, QTreeWidgets, etc.
EDIT 2: I forget the () at the end of the treeWidget clear commands.
The Script object created in the FileChanged function has local scope, and will be garbage-collected as soon as the function returns.
If the Run slot gets called when the signal fires, it will carry out all of the changes correctly, but you won't get to see any of those changes, because Script will be deleted before it is ever shown.
In order to for the example script to begin to make any sense, it would need to be re-arranged to something like this:
#Setting up the GUI
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
showGUI = GUI()
#Establish the file monitoring mechanism, *outside* the PySide class
def FileChanged():
showGUI.Run()
Paths = ['path/to/file']
Watch = QtCore.QFileSystemWatcher(Paths)
Watch.fileChanged.connect(FileChanged)
showGUI.show()
app.exec_()
Of course, it's possible that your real code is nothing like the example in your question (which has numerous other errors preventing it from being run), and so this might be no help. But if that is the case, you will have to post a fully working, self-contained example that properly demonstrates the problems you are having.

Pygtk client app

I want to create a client frontend in pygtk for my Django project. My general idea is to have one main window, and everytime the user has an action that must change the screen to unload previous widgets and load the new ones. E.g if i have a login page, after user logs in he is presented with a customer screen. I want the new screen to be placed on the same main window, kinda like a page stack, but without the "back" functionality. My first thought was to create a function for every screen, a show_login, a show_customers_screen, etc. Is this a good choice or should i try a better one. And a second question, related to the first. Can i create callbacks inside a function?
e.g
This would be a method of MainWindow
def create_login(self):
....creating widgets here
#UnboundLocalError: local variable 'clear_clb' referenced before assignment
btnlogin.connect('clicked', clear_clb, data=None)
def clear_clb(widget, data=None):
..log in process
I know why i get the error. The thing is that the fields i want this func to clear are local in create_login. Is this the right approach?
Define the clear_clb symbol before referencing it:
def create_login(self):
# create widgets
def clear_clb(widget, data=None):
# log in process
btnlogin.connect('clicked', clear_clb, data=None)
However, the more usual, and in my opinion more readable, way of doing this is to save references to your widgets as attributes of self:
def create_login(self):
# create widgets
self.btnlogin = gtk.Button(...
self.btnlogin.connect('clicked', clear_clb)
def clear_clb(self, widget, data=None):
# log in process

How to make an arbirtary number of windows using Python and Tkinter?

I'm writing an app that doesn't have a main window (it runs inside a Python interpreter in another app), and I thought I had a good solution for getting Tkinter to cooperate--I made the Tkinter.Tk class into a Borg.
class RootWindow(Tk):
""" Invisible window that serves as the default parent
of all others.
"""
groupDict = {}
def __init__(self):
self.__dict__ = self.groupDict
if self.__dict__ == {}: # first instance
Tk.__init__(self)
self.withdraw()
Then I have my Windows, which are subclassed from Tkinter.Toplevel, default to parent=RootWindow(). The result should be that I can create any number of Windows and they'll use the same root.
It works once fine for the first Window, but after that things get all messed up. :(
see pic
What am I doing wrong? Is this even a feasible solution?
Thanks
EDIT: I should add that even though there's other stuff running in the picture, the problem can be duplicated just by using RootWindow as the parent of a Tkinter.Toplevel.
EDIT: I overrode Window.mainloop so everything uses the RootWindow event loop.
def mainloop(self):
self.master.wait_window(self)
Then I create each visible window like this:
test = Window()
test.mainloop()
It seems to work because the windows do show up, but their contents are packed in an odd way that's hard to describe. It alternates between no contents at all and having everything squished horizontally and expanded vertically.
One problem appears to be that you are forgetting to start the event loop. Or, based on further edits of your question, you may be starting more than one main loop. You need exactly one main loop that is run from the root window.
Tkinter certainly allows an arbitrary number of top level windows, but your question doesn't appear to have enough details to show what is wrong unless my first guess is correct.

Categories