gtk.Builder() and multiple glade files breaks - python

I have a glade gui, and I want to insert another object using a glade file as well.
When I do it as bellow (this is essentially what I am doing) the whole app hangs and the self.show() and maxes out the CPU at 100%. If I replace the first line of one's init() with self.builder = gtk.Builder() then the app runs, I can set widgets, ie: set contents of entry's, set and change the values of comboboxes. But I cant respond to signals, button clicks never call the handler.
In the real code the object two is set as a page in a note book, and I have multiple other pages, the gtk.main() is in the object that owns the notebook. All these work as expected, it's just the object one that fails.
Any clues? I have tried calling self.builder.connect_signals() for every widget but it still fails to notice them.
class one(gtk.VBox):
def __init__(self, builder):
gtk.VBox.__init__(self)
self.builder = builder # if this is self.builder = gtk.Builder() app runs but widget signals go missing.
self.builder.add_from_file("ui_for_one.glade")
self.show() # Endless loop here?
class two(object): # This is the page in a notebook.
def __init__(self):
self.builder = gtk.Builder()
self.builder.add_from_file("ui_for_two.glade")
self.some_container = self.builder.get_object("some_container")
self.one = one(self.builder)
self.some_container.pack_start(self.one, False, False)

Is there a good reason for using the same gtk.Builder object in two classes?
This might be the cause of your problem. In your one class, you load a glade file but you never do anything with its widgets. Something like this should work:
class one(gtk.VBox):
def __init__(self):
gtk.VBox.__init__(self)
self.builder = gtk.Builder()
self.builder.add_from_file("ui_for_one.glade")
some_widget = self.builder.get_object("some_widget")
self.add(some_widget)
self.builder.connect_signals(self)
# No reason to call self.show() here, that should be done manually.
#Your callback functions here
class two(object): # This is the page in a notebook.
def __init__(self):
self.builder = gtk.Builder()
self.builder.add_from_file("ui_for_two.glade")
self.some_container = self.builder.get_object("some_container")
self.one = one()
self.some_container.pack_start(self.one, False, False)
self.some_container.show_all() #recursively show some_container and all its child widgets
self.builder.connect_signals(self)
For more info, check out these Glade tutorials.

Related

When changing screens with QStackedWidget, how to call a function in the changed screen?

I'm trying to call the init function of the screen I'm changing my screen index to
For an example, i have this code:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from sys import argv as sysArgv
from sys import exit as sysExit
arialLarge = qtg.QFont("Arial", 18)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# Current screen label;
mainWindowLabel = qtw.QLabel("This is the main window", self)
mainWindowLabel.setFont(arialLarge)
mainWindowLabel.move(20, 40)
# Button for going to the HelloWindow screen;
gotoHelloWindowButton = qtw.QPushButton("Go to hello window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()+1))
gotoHelloWindowButton.move(100, 100)
class HelloWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# EG: print hello world when I visit this page
print("hello world")
# Current screen label;
helloWindowLabel = qtw.QLabel("This is the hello window", self)
helloWindowLabel.setFont(arialLarge)
helloWindowLabel.move(20, 40)
# Button for going to the MainWindow screen;
gotoMainWindowButton = qtw.QPushButton("Go to main window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()-1))
gotoMainWindowButton.move(100, 100)
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = qtw.QStackedWidget()
appStack.addWidget(MainWindow())
appStack.setFixedSize(300, 300)
appStack.show()
appStack.addWidget(HelloWindow())
sysExit(app.exec())
If im visiting the HelloWindow from the MainWindow, how can i run the init function of the HelloWindow screen so I can run whatever code I want in there?
I need to be able to do this as on the app im working on as on the mainpage i have dynamically created buttons that all have functions parameters with different indexes to my server, and i need to be able to fetch the data from server based off the clicked button's data index so on the other page I can view the desired data.
The __init__ of a python class is what is called when an instance is created (using SomeClass()), so you should not try (or even think) to call it again, as it could create serious problems and bugs that are hard to track.
I strongly suggest you to read the documentation about classes in Python, as you cannot ignore that aspect in object oriented programming.
If you need to call something everytime the index is changed, then you should better subclass QStackedWidget and control everything from there.
A good solution is to create a standardized function that will be called everytime the page is presented, and ensure that the stack widget correctly calls it.
class FirstPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.nextButton = QtWidgets.QPushButton('Next')
self.doSomething()
def doSomething(self):
...
class SecondPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.prevButton = QtWidgets.QPushButton('Previous')
self.doSomething()
def doSomething(self):
...
class Stack(QtWidgets.QStackedWidget):
def __init__(self):
super().__init__(self)
self.first = FirstPage()
self.first.nextButton.clicked.connect(self.goNext)
self.addWidget(self.first)
self.second = SecondPage()
self.second.prevButton.clicked.connect(self.goPrev)
self.currentChanged.connect(self.initCurrent)
def goNext(self):
self.setCurrentIndex(1)
def goPrev(self):
self.setCurrentIndex(0)
def initCurrent()
if self.currentWidget():
self.currentWidget().doSomething()
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = Stack()
appStack.setFixedSize(300, 300)
appStack.show()
sysExit(app.exec())
Note that adding a QMainWindow to a parent is not a good idea, as Qt main windows are intended to be used as top level windows; also note that using fixed geometries (positions and sizes) is often considered bad practice, and you should use layout managers instead.

Change Button Color in Qt Thread Python

I need to change the color of QPushButton, but an error occurred: "AttributeError: type object 'ProyectoTFM' has no attribute 'ui'".
I don't know hoy to acced to a ui variable from my thread.
This is my code:
import sys
import OpenOPC
import time
import threading
from proyectoQt import *
def actualizarDatosOPC():
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
ProyectoTFM.ui.AP08Button.setStyleSheet("background-color: red")
return
class ProyectoTFM(QtGui.QMainWindow):
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC)
threadQt.start()
def clienteOPC():
opc=OpenOPC.client()
opc.connect('Kepware.KEPServerEX.V6')
global itemsOPC
while 1:
itemsOPC = opc.read(opc.list('PLC.PLC.TAGS'))
time.sleep(5)
return
threads = list()
threadOPC = threading.Thread(target=clienteOPC)
threads.append(threadOPC)
threadOPC.start()
time.sleep(5)
if __name__== "__main__":
app=QtGui.QApplication(sys.argv)
myapp = ProyectoTFM()
myapp.show()
sys.exit(app.exec_())
threadOPC.__delete()
Sorry for my English and thanks.
It is not correct to modify the view from a different thread to the main one, a way to solve the problem without using QThread is to create a signal that connects to some slot that changes the color of the button. To be able to emit the signal from the new thread we must pass the object to him through the parameter args.
def actualizarDatosOPC(obj):
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
obj.sendChangeColor.emit()
return
class ProyectoTFM(QtGui.QMainWindow):
sendChangeColor = QtCore.pyqtSignal()
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
self.sendChangeColor.connect(lambda: self.ui.AP08Button.setStyleSheet("background-color: red"))
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC, args=(self,))
threadQt.start()
Even if you got this to work, you can't modify the UI from a thread directly.
A few things:
You never actually pass the UI to the function actualizarDatosOPC
so it doesn't know it exists.
Is there any reason you can't use PyQt's built in threading tools? If you are going to use PyQt it might make sense to buy into the whole framework.
def startTheThread(self):
self.threadQt = QThread()
d = actualizarDatosOPC(self)
d.moveToThread(self.threadQt)
self.threadQt.start()
def actualizarDatosOPC(widget):
.... widget.AP08Button.setStyleSheet("background-color: red")
If you do choose to go this route, I'd take a look at this thread which has a good example:
How to use QThread correctly in pyqt with moveToThread()?
Additionally, while the way you initialize your Window works, this is the more standard way to do it:
class ProyectoTFM(QMainWindow, Ui_MainWindow):
def __init__(self, parent):
# General Init Stuff
super(Login, self).__init__(parent)
self.setupUi(self)
After that, whenever you want to refer to something in the UI all you need to do is refer to self._____. For example, if you have a button named buttonA, self.buttonA would be the appropriate reference.
Edit:
As mentioned in another answer, the proper way to actually change the button color would be to emit a trigger that to the main thread which could then respond by changing the button color.

Opening a QDialog and saving last state

I am trying to open a QDialog from a QMainWindow, and after closing the `QDialog, if I need to open it again, it has to open and show the same information that had when I close it.
Here is the code of the QMainWindow:
class A (QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#I create a QPushButton to open the QDialog
self.axes1 = self.figure_canvas.figure.add_axes ([0.8, 0.01, 0.19, 0.05])
self.button = QPushButton(self.axes1,"Open Dialog")
self.button.on_clicked(self.OpenDialog)
#This is the method to open the QDialog which is in another module
def OpenDialog(self, event):
text = configurePort.ConfigurePort.retrieve_data(self)
print text
What this code does is create a button in my QMainWindow and when I click it, it opens a QDialog, which is created in another module. And this is the code of the QDialog:
class ConfigurePort(QDialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
uic.loadUi("configurePort.ui", self)
#I create a button to check active ports and show them
self.connect(self.btn_checkconn, SIGNAL("clicked()"), self.check_ports)
#This method calls another class which opens another QDialog
#and I select the port that I want
def check_ports(self):
self.check_serial = CheckPorts(self)
self.check_serial.exec_()
#After selecting the port, when I close the QDialog of the class named above
#the port´s name appears in the first QDialog
#classmethod
def retrieve_data(cls, parent = None):
dlg = cls(parent)
dlg.exec_()
text = dlg.getPortText()
return text
def closeEvent(self, event):
#Here is where I need to write the code to close the QDialog
#and it does not has to be an event
In the method, closeEvent, I need to write the necessary code, so I can close the window, and using the same button that I use to open it, open it again with the last information that it showed when I closed it.
I have tried to use QSettings but it did not worked (maybe I used it wrong). And I tried the show() and hide() classes of PyQt too, but it did not work. Hope you can help me.
----- EDIT -----
I edited the code of above. and I added some methods for a better understanding. So, i open the QDialog called ConfigurePort and it shows this:
The red circle, surrounds the port´s name. It is shown in a QLabel,and I take this text from the QDialog and then print it when I close the QDialog. I acomplish this thanks to a question I asked before, wich is in this link:
Getting data from child using PyQt
The check_port method shown in the code above, opens another QDialog that works great. With this I can select the ports that I need in my pc. So, this does not matter.
So, after closing the QDialog(and selecting for example "COM3", as you can see in the picture), I need to open it again, and see the same information that was shown before I closed it.
I tried to add this lines, using QSettings :
self.settings = QSettings("MyCompany", "MyApp")
if not self.settings.value("windowsState") == None:
self.restoreState(self.settings.value("windowState"))
But as I said before, I think that I did not use it right, but I hope that I solve this using something simpler.
----- EDIT 2 -----
Thank to the help of #Brendan Abel I have this code:
class ConfigurePort(QDialog):
def __init__(self, parent):
super(ConfigurePort, self).__init__(parent)
uic.loadUi("configurePort.ui", self)
self.myValue = 10
self.restoreSettings()
self.connect(self.btn_checkconn, SIGNAL("clicked()"), self.check_ports)
self.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(self.close)
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.closeEvent)
self.iniUi()
def check_ports(self):
pass
def iniUi(self):
pass #I just create some QLabels in here
#classmethod
def retrieve_data(cls, parent = None):
dlg = cls(parent)
dlg.exec_()
text = dlg.getPortText()
return text
def closeEvent(self, event):
self.saveSettings()
super(QDialog,self).closeEvent(event)
def saveSettings(self):
settings = QSettings("MyOrg", "MyApp")
settings.setValue("myValue", self.myValue)
def restoreSettings(self):
settings = QSettings("MyOrg", "MyApp")
self.myValue = settings.value("myValue", self.myValue)
This gives me this error: TypeError: QWidget.closeEvent(QCloseEvent): argument 1 has unexpected type 'bool'
I know that I am missing something, but I can not see it.
There are a couple ways you could persist this data Generally, to persist data across sessions, you use QSettings and load the data in the __init__ and save it in the closeEvent method
Generally it looks something like this. This also assumes your using the v2 version of the QVariant api; otherwise, the results returned from QSettings.value is going to be a QVariant and you'll need to cast it to the appropriate python type. If you're using a recent version of PyQt then you should be on v2, but if not you can force it by sticking this at the top of your file
import sip
sip.setapi('QVariant', 2)
sip.setapi('QString', 2)
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.myvalue = 10
self.restoreSettings()
def closeEvent(self, event):
self.saveSettings()
super(MyDialog, self).closeEvent(event)
def saveSettings(self):
settings = QSettings('myorg', 'myapp')
settings.setValue('myvalue', self.myvalue)
def restoreSettings(self):
settings = QSettings('myorg', 'myapp')
self.myvalue = settings.value('myvalue', self.myvalue)
EDIT:
The error in your code is caused by this:
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.closeEvent)
You shouldn't be calling or connecting to closeEvent directly. Instead, you should connect to .close or .accept
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.accept)
You need to instantiate the ConfigurePort class then the self.configurePortDialog object should keep consistent. You will need to make sure if you have the user enter data that a cancel does not store the data and that an "ok" stores the data, but I not sure what you are putting in your dialog.
class A (QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#I create a QPushButton to open the QDialog
self.button = QPushButton("Open Dialog")
self.button.on_clicked(self.OpenDialog)
self.configurePortDialog = configurePort.ConfigurePort(parent=self)
self.configurePortDialog.accepted.connect(self.get_data)
#This is the method to open the QDialog which is in another module
def OpenDialog(self, event):
self.configurePortDialog.show()
#QtCore.Slot()
def get_data(self)
text = self.configurePortDialog.retrieve_data()
print text

GTK: Problems redrawing widgets on Gtk.ApplicationWindow

I'm creating a GTK GUI in Python for an already existing program. Imagine the following structure:
program
|----gtk
|-----application.py
|-----mainwindow.py
|-----mainwidgets.py
Of course this is simplified. But the GUI works somewhat like this: application.py is a Gtk.Application that creates instances of the objects in mainwidgets, provides back-end functions which are passed as callbacks to mainwindow.py, which places the widgets in a Gtk.ApplicationWindow.
There are a couple of cases where I'm having trouble. I'll go over two which I think are related.
Let's go for the simpler one first. There's a simple button defined in mainwidget, which is used as a statusbar in my program.
class Statusbar(Gtk.Widget):
def __init__(self, on_button_do):
super(Gtk.Widget, self).__init__()
self.callback = on_button_do
self.button_label_int = 0
self.button = Gtk.Button.new_with_label(str(button_label_int)
def increment_button_label(self):
self.button_label_int += 1
self.button.set_label(str(button_label))
Then, when the mainwindow receives a signal, it is instructed to call
statusbar.increment_button_label(). This crashes almost immediately with a segmentation fault, no more info given about the problem, as soon as that signal is received.
class AppWindow(Gtk.ApplicationWindow):
__gsignals__ = {
"new_log": (GObject.SIGNAL_RUN_FIRST, None, ())
}
def __init__(self, statusbar, sidebar, *args, **kwargs):
super(Gtk.ApplicationWindow, self).__init__(*args, **kwargs)
self.statusbar = statusbar
self.sidebar = sidebar
self.notificationBox = Gtk.Box()
self.notificationBox.pack_start(self.statusbar.get_button(), True, True, 0)
# MAINBOX: THE BIGGER BOX OF ALL THE LITTLE BOXES
self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.mainBox.pack_start(self.
self.mainBox.pack_start(self.notificationBox, False, False, 0)
self.add(self.mainBox)
self.show_all()
def do_new_log(self):
"""What should the window do when it gets a new_log signal"""
statusbar.increment_button_label()
Another (related?) problem having to with updating the display of widgets: my sidebar. The sidebar is actually just a Gtk.ListStore and a Gtk.TreeView, nothing fancy. In application.py you'd find code like this:
def do_startup(self):
Gtk.Application.do_startup(self) # deep GTK magic
self.sidebar = Sidebar(self.workspace_manager,
self.changeWorkspace,
CONF.getLastWorkspace())
And then that instance of sidebar is passed to mainwindow.py to be put in a box. Everything works fine for now.
The problem comes when I try to add information to the sidebar. The back-end part of it works, because if I restart the application, I can see the new entry in the sidebar. And the sidebar gets information from this backend:
class Sidebar(Gtk.Widget):
def __init__(self, workspace_manager, callback_to_change_workspace, conf):
super(Gtk.Widget, self).__init__()
self.callback = callback_to_change_workspace
self.ws_manager = workspace_manager
self.lastWorkspace = conf
def createTitle(self):
title = Gtk.Label()
title.set_text("Workspaces")
return title
def workspaceModel(self):
self.workspace_list_info = Gtk.ListStore(str)
for ws in self.ws_manager.getWorkspacesNames():
treeIter = workspace_list_info.append([ws])
if ws == self.lastWorkspace:
self.defaultSelection = treeIter
def workspaceView(self):
self.lst = Gtk.TreeView(self.workspace_list_info)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Workspaces", renderer, text=0)
self.lst.append_column(column)
# select by default the last active workspace
if self.defaultSelection is not None:
self.selectDefault = self.lst.get_selection()
self.selectDefault.select_iter(self.defaultSelection)
self.selection = self.lst.get_selection()
self.selection.connect("changed", self.callback)
What maindindow tries to do is put sidebar.lst into a box. That works fine. The thing is when I add a workspace via a dialog box, I doesn't show up, as I stated above.
Any idea about what could cause these problems? Is this way of organizing my problem not all right for some reason for GTK? I think the code itself is fine: the workspaces are added, after all, and GTK doesn't crash. It just doesn't do it fine. Also the button, at first, is displayed just fine, it even emits its signal. But as soon as I try to change its label, everything explodes.
OK, so this actually had to do with multiple threads and signals. It was solved by overriding the emit signal.
class _IdleObject(GObject.GObject):
"""
Override GObject.GObject to always emit signals in the main thread
by emmitting on an idle handler
"""
def __init__(self):
GObject.GObject.__init__(self)
def emit(self, *args):
GObject.idle_add(GObject.GObject.emit, self, *args)

Best practice with pyGTK and Builder XML files

I usually design GUI with Glade, thus producing a series of Builder XML files (one such file for each application window).
Now my idea is to define a class, e.g. MainWindow, that inherits from gtk.Window and that implements all the signal handlers for the application main window. The problem is that when I retrieve the main window from the containing XML file, it is returned as a gtk.Window instance.
The solution I have adopted so far is the following:
I have defined a class "Window" in the following way
class Window():
def __init__(self, win_name):
builder = gtk.Builder()
self.builder = builder
builder.add_from_file("%s.glade" % win_name)
self.window = builder.get_object(win_name)
builder.connect_signals(self)
def run(self):
return self.window.run()
def show_all(self):
return self.window.show_all()
def destroy(self):
return self.window.destroy()
def child(self, name):
return self.builder.get_object(name)
In the actual application code I have then defined a new class, say MainWindow, that inherits frow Window, and that looks like
class Main(Window):
def __init__(self):
Window.__init__(self, "main")
### Signal handlers #####################################################
def on_mnu_file_quit_activated(self, widget, data = None):
...
The string "main" refers to the main window, called "main", which resides into the XML Builder file "main.glade" (this is a sort of convention I decided to adopt).
So the question is: how can I inherit from gtk.Window directly, by defining, say, the class Foo(gtk.Window), and recast the return value of builder.get_object(win_name) to Foo?

Categories