class GuiMaker(Frame):
#more code
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH) # make frame stretchable
self.start() # for subclass: set menu/toolBar
self.makeMenuBar() # done here: build menu-bar
self.makeToolBar() # done here: build tool-bar
self.makeWidgets() # for subclass: add middle part
#more code
class TextEditor:
#more code
def start(self):
#more code
How come self.start() will call TextEditor's start if self refers to GuiMaker or else how come self refers to TextEditor?
Does GuiMaker inherit from TextEditor somewhere? In other words is Frame a descendant of TextEditor? That would cause TextEditor's start method to be called.
Other than that, I don't see any way for the code (as written) to have GuiMaker.start call TextEditor.start
Related
I wrote a GUI program with Python tkinter. To achieve some function, in a Toplevel window, some event triggered a method of it which would call the Treeview widget's item(ID, tags=(some_tag)) of the Tk window to change the style of Treeview's content. But it doesn't work even if the snippet containing .item() have been run and no error occurs. My corresponding code snippet is as follows(some irrelevant part is omitted).
class Main_window(Tk):
# some_code_omitted...
def create_widgets():
# some_code_omitted...
self.tv1 = ttk.Treeview()
class A_Toplevel(Toplevel):
def __init__(self, parent):
self.parent = parent
# some_code_omitted...
def some_foo(self, event):
self.parent.tv1.item(ID, tags=(some_tag))
After some attempt, I found it seems that only when tv.item() is called in the Main_window, it works. later I wrote a method in Main_window to call tv.item(). But when the instance of A_Toplevel call it, it still doesn't work at all.
class Main_window(Tk):
# some_code_omitted...
def create_widgets():
# some_code_omitted...
self.tv1 = ttk.Treeview()
def a_foo(self, ):
self.tv1.item(ID, tags=(some_tag))
class A_Toplevel(Toplevel):
def __init__(self, parent):
self.parent = parent
# some_code_omitted...
def some_foo(self, event):
self.parent.a_foo()
What's wrong and how can I solve this problem?
Oh! Some progress.
Today, I found a way to solve it occasionally with threading module. Codes are as follows:
def a_foo_thr(self, ID, some_tag):
thr = threading.Thread(target=self.a_foo, args=(ID, some_tag))
thr.start()
def a_foo(self, ID, some_tag):
self.tv1.item(ID, some_tag)
But I have no idea why it succeeded, even whether it could make some unexpected problem.
I am working on developing a GUI with PyQt5. This is my first step into OOP, and I'm trying to teach myself as I go. I'm struggling with understanding when classes inherit methods/attributes etc and what methods they have available -- I guess it is a scope-related question? I have produced a MWE to my GUI below. In total, there will be many more pages and signals/slots.
What I want:
The stack should initialize with the "MainMenu" widget/object showing (left image below). Clicking on "Next Page" button should switch the stack order to put the "OtherPage" widget/object on top (right image below). I am creating each page as a class, thinking this would be a good way to organize my project. Is this good or bad practice?
What happens now:
The GUI works (initializes) if the line nextPg.clicked.connect(self.drawOtherPage()) is commented out, but of course then clicking on the button does nothing. I can switch the initial stack order so that "other" widget is on top of the stack and it shows up fine, so I think that class is also working. When the above line is included in the code, the following error is thrown:
in __init__
nextPg.clicked.connect(self.drawOtherPage())
AttributeError: 'MainMenu' object has no attribute 'drawOtherPage'
What I've tried
I thought that the call to super() was supposed to allow the child class (in this case MainMenu) to inherit the methods from the parent class (RootInit). Therefore, I would think this should make the drawOtherPage method available to the button connect signal. Obviously, the error isa result of the method not being available.
What am I doing wrong? Should I be creating these "page" widgets in methods instead? Do they need to be under the RootInit class or can they live in the top level of the .py file? I'm trying to follow best practices as the project will become pretty large in the end. Fortunately, most of it should be pages with variations based on what buttons were clicked to get there -- I therefore thought classes would be helpful. Please be harsh on the code and my python/PyQt vernacular, trying to learn -- thanks!
import sys, os
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore
class RootInit(QMainWindow):
# root window
def __init__(self, parent=None):
QMainWindow.__init__(self)
self.root = QWidget()
self.stack = QStackedWidget()
rootLayout = QVBoxLayout()
rootLayout.addWidget(self.stack)
self.root.setLayout(rootLayout)
self.setCentralWidget(self.root)
self.initializeGUI()
def initializeGUI(self):
self.main = MainMenu(self) # build MainMenu (class)
self.other = OtherPage(self) # build OtherPage (class)
self.stack.addWidget(self.main)
self.stack.addWidget(self.other)
def drawMain(self):
self.stack.setCurrentIndex(0)
def drawOtherPage(self):
self.stack.setCurrentIndex(1)
class MainMenu(QWidget):
# class for main menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
super().__init__()
mainLayout = QGridLayout() # layout for entire main menu
quitBtn = QPushButton("Quit")
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
nextPg = QPushButton("Next page")
nextPg.clicked.connect(self.drawOtherPage())
mainLayout.addWidget(quitBtn, 0, 0)
mainLayout.addWidget(nextPg, 0, 1)
self.setLayout(mainLayout)
class OtherPage(QWidget):
# class for another menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
label = QLabel("test label")
layout = QGridLayout() #
layout.addWidget(label, 0, 0)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
root = RootInit()
root.setWindowTitle("Title")
root.show()
sys.exit(app.exec_())
Your code has the following errors:
The variable self refers to the same instance of the class, in your case self refers to an instance of MainMenu, and if we observe MainMenu it does not have any drawOtherPage() method.
Another mistake in your case is to call the parent's constructor twice:
class MainMenu(QWidget):
# class for main menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
super().__init__()
In the first constructor you are assigning a parent, and in the second, you are not. To clarify in python there are several ways to call the parent's constructor:
QWidget.__init__(self, parent)
super(MainMenu, self).__init__(parent)
super().__init__(parent)
so you should only use one of them.
Another error is that a signal is connected through the name of a function, the function must not be evaluated using parentheses
and for the last use of functions or methods that involve several objects should be done in a place where both objects can access, in your case you can take advantage of what you are going to RootInit as parent of MainMenu: self.main = MainMenu(self), and access the connection to that element through the method parent().
All of the above entails modifying the MainMenu class to the following:
class MainMenu(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
mainLayout = QGridLayout() # layout for entire main menu
quitBtn = QPushButton("Quit")
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
nextPg = QPushButton("Next page")
nextPg.clicked.connect(self.parent().drawOtherPage)
mainLayout.addWidget(quitBtn, 0, 0)
mainLayout.addWidget(nextPg, 0, 1)
self.setLayout(mainLayout)
I've programmed using tkinter before, but usually did a long procedural GUI class that implemented other non GUI classes I've created. This time I wanted to do it using more OOP making it more modular.
I ran into a problem, I've searched for answers and haven't found any, which usually means it's either really easy or I'm really wrong. I created an inherited classes from tk.LabelFrame and created GUI widgets in them. I also have methods to manipulate the widgets in the classes but I can't figure out how to execute a function in another inherited class, partly because I can't figure out how to correctly instantiate an object from the other class (which have tkinter ('parent') objects as parameters).
Would I do this by overloading constructors? I've seen something about #classmethods and *args, **kwargs but haven't acted on them as I'm not sure if that's the right route either. There's some debate about the best/correct way to implement an overloaded constructor in python. I'm stumped as to what is the most apropos for what I'm trying to accomplish...
Thanks
#python 2.7 on win7
import Tkinter as tk
class Testing(tk.LabelFrame):
buttonwidth = 10
def __init__(self, parent):
self.parent=parent
#results = Results(???) #<-- Don't know how to instantiate Results object
tk.LabelFrame.__init__(self, self.parent,
text="Test Operations",
padx=10,
pady=10,
)
self.taskButton = tk.Button(
self,
text="Do A Task",
width=self.buttonWidth,
command=self.doATask,
)
self.taskButton.pack()
def doATask(self):
#want to execute function in Results.getResult() but don't know how
#results.getResults() #<--what I want to do
print("place holder")
class Results(tk.LabelFrame):
def __init__(self, parent):
self.parent = parent
tk.LabelFrame.__init__(self, self.parent, text="Visual Results")
self.resultLbl = tk.Label(self, text="Result")
self.resultLbl.pack()
def getResult(self):
self.resultLbl.configure(bg='yellow')
class Application(tk.Frame):
def __init__(self, parent):
self.parent = parent
tk.Frame.__init__(self, self.parent)
self.Testing = Testing(self.parent)
self.Results = Results(self.parent)
self.Testing.pack(fill=tk.X)
self.Results.pack(fill=tk.X)
if __name__ == "__main__":
root = tk.Tk()
root.title("Modular GUI App")
Application(root).pack()
root.mainloop()
I'd recommend sticking to instance variables, which are created for each individual object, unlike class variables which are shared among all of a class's instantiations - just prepend those variable names with self. (e.g. self.results). Also, stick to naming conventions so you don't have a Testing class and a Testing object of that class.
You instantiate objects according to their __init__. The Results class has an __init__ defined as def __init__(self, parent):, so it needs a parent. If you want it to have the same parent as the Testing object that created it, simply do results = Results(parent). However, you don't want to do this (see below).
A problem that I encountered after making the above change was that the Application class instantiated its own Results object, and that was what was actually being displayed, not the one created by the Testing object. Refer back to that object instead of creating a new one. Pass the Application object to each of these classes so they can refer to each other. Now, having said that, it's generally better to have each class know as little about other classes as possible, so that making a change in one class doesn't require any changes in other classes.
The following code will make the label yellow when you click the button.
import Tkinter as tk
class Testing(tk.LabelFrame):
def __init__(self, parent, main):
self.buttonWidth = 10
self.parent=parent
self.main = main # save the instantiating class
tk.LabelFrame.__init__(self, self.parent,
text="Test Operations",
padx=10,
pady=10
)
self.taskButton = tk.Button(
self,
text="Do A Task",
width=self.buttonWidth,
command=self.doATask,
)
self.taskButton.pack()
def doATask(self):
#want to execute function in Results.getResult() but don't know how
self.main.results.getResult() #<--what you can do
class Results(tk.LabelFrame):
def __init__(self, parent, main):
self.parent = parent
self.main = main # save the instantiating class
tk.LabelFrame.__init__(self, self.parent, text="Visual Results")
self.resultLbl = tk.Label(self, text="Result")
self.resultLbl.pack()
def getResult(self):
self.resultLbl.config(bg='yellow')
class Application(tk.Frame):
def __init__(self, parent):
self.parent = parent
tk.Frame.__init__(self, self.parent)
self.testing = Testing(self.parent, self)
self.results = Results(self.parent, self)
self.testing.pack(fill=tk.X)
self.results.pack(fill=tk.X)
if __name__ == "__main__":
root = tk.Tk()
root.title("Modular GUI App")
Application(root).pack()
root.mainloop()
This worked for me
Results(None,None).getResult()
goodluck!
I need refresh(update) GUI widget(pySide) in Nuke(compositing software) after load or save nuke script.
callback: nuke.addOnScriptSave() and nuke.addOnScriptLoad()
import nuke
from PySide import QtGui, QtCore
from nukescripts import panels
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
def scriptName(self):
sName = (nuke.root().name()).split('/')#split name from root
return sName
def initUI(self):
self.lbl1 = QtGui.QLabel("script name : " , self)
layout = QtGui.QHBoxLayout()#main layout
layout.addWidget(self.lbl1)
self.setLayout(layout)
self.updateInfo()
def updateInfo(self):
scriptName = self.scriptName()
self.lbl1.setText("script name : " + scriptName[-1].split('.')[0])#set name
panels.registerWidgetAsPanel('Info', 'Info_script', 'infoscript')
The simple solution is to add the callback registration to your widget's __init__:
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
nuke.addOnScriptSave(self.updateInfo)
if nuke.root().name() == 'Root' and not nuke.modified():
# No reason to add a scriptLoad callback if opening a
# script would spawn a new Nuke process.
nuke.addOnScriptLoad(self.updateInfo)
# Rest of the class definition omitted for brevity
This approach does have a notable downside: Because Nuke's callback registry will now contain at least one reference to the widget's updateInfo method, the widget can never be garbage-collected by Python. This will probably never be too big of a deal in real life, since you will probably only ever create a very small number of panel instances, but if you ever created, say, 1000 instances, you would have 1000 callbacks registered that would never be unregistered (even if the panels were closed).
Unfortunately, Nuke doesn't really give you any hooks to implement when your widget is being removed from the UI, so it's tough to have a sure-fire way of knowing it's time to unregister your callbacks. The closest you can get is probably using a showEvent/hideEvent combo and some guesswork, like so:
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
self.callbacksRegistered = False
def addCallbacks(self):
nuke.addOnScriptSave(self.updateInfo)
if nuke.root().name() == 'Root' and not nuke.modified():
# No reason to add a scriptLoad callback if opening a
# script would spawn a new Nuke process.
nuke.addOnScriptLoad(self.updateInfo)
def removeCallbacks(self):
self.removeOnScriptSave(self.updateInfo)
self.removeOnScriptLoad(self.updateInfo)
def showEvent(self, event):
if not (self.callbacksRegistered or event.spontaneous()):
self.addCallbacks()
self.callbacksRegistered = True
def hideEvent(self, event):
if self.callbacksRegistered and not event.spontaneous():
# "Spontaneous" hide events are sent when the Nuke
# window is minimized, which we don't really care
# about.
self.removeCallbacks()
self.callbacksRegistered = False
# Rest of the class definition omitted for brevity
The callback registration is moved into the showEvent method, and the callbacks are unregistered when the widget is hidden by something other than an OS-level window operation. This is pretty reliable, except that your callbacks will also be unregistered when you change to another tab in the widget's pane (if it's docked). They will obviously be re-added when your tab is activated again though. This is a pretty minor penalty, but I still think it's worth pointing out that while this approach is pretty close to ideal, it isn't quite perfect.
Anyway, I'll leave it up to you to decide which of these two you like better, but I hope this helps.
I have a MainWindow that looks like this:
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.showMaximized()
menu=mainMenu.MainMenu()
classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(menu)
self.stackedWidget.addWidget(classification)
self.stackedWidget.setCurrentWidget(menu)
self.stackedWidget.showFullScreen()
#connections
menu.pushButton.clicked.connect(self.showClassification)
classification.backButton.clicked.connect(self.showMainWindow)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
def showMainWindow(self):
self.stackedWidget.setCurrentIndex(2)
The MainWindows waits for signal from the rest of the dialogs. Now, the Classification dialog has another StackedWidget in it, since it works as a main window for an important part of the application. It looks like:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
choose=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(choose)
self.stackedWidget.setCurrentWidget(choose)
Now, I want to reload the data inside ChoosePatient every time the button "Show Classification" from MainMenu is clicked, but now the data is loaded only once in the line classification=classificationMain.ClassificationMain() of MainWindow.
I was thinking I had to connect a slot inside ChoosePatient with the click of "Show Classification" button inside MainMenu, but I would need an instance of MainMenu, which is not possible.
How can a method of ChoosePatient can be execute every time the button in the "parent" window is clicked? (also, please tell me if this is not the right way to work with pyqt windows)
You need to save references to your composed widgets, and also to expose some public methods to the parents:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
self.chooseWidget=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(self.chooseWidget)
self.stackedWidget.setCurrentWidget(self.chooseWidget)
def reloadPatients(self):
# whatever your operation should be on the ChoosePatient
self.chooseWidget.reload()
# MAIN WINDOW
def __init__(self, parent = None):
...
self.classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(self.classification)
...
#connections
menu.pushButton.clicked.connect(self.showClassification)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.reloadPatients()
You could also just skip the reloadPatients method and connect to the ChoosePatient directly if you want:
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.chooseWidget.reload()
My personal opinion is to make your custom classes wrap up the internal functionality nicely so that you only need to interface with it over the custom class, and not dig into its internals. That way you can change how it works inside without breaking the main window.