Enter-Notify-Event Signal not working on gtk.ToolButton - python

On a happy (if not irrevelent) note, this is the absolute last obstacle in this particular project. If I fix this, I have my first significant dot release (1.0), and the project will be going public. Thanks to everyone here on SO for helping me through this project, and my other two (the answers help across the board, as they should).
Now, to the actual question...
I have a toolbar in my application (Python 2.7, PyGTK) which has a number of gtk.ToolButton objects on it. These function just fine. I have working "clicked" events tied to them.
However, I need to also connect them to "enter-notify-event" and "leave-notify-event" signals, so I can display the button's functions in the statusbar.
This is the code I have. I am receiving no errors, and yet, the status bar messages are not appearing:
new_tb = gtk.ToolButton(gtk.STOCK_NEW)
toolbar.insert(new_tb, -1)
new_tb.show()
new_tb.connect("clicked", new_event)
new_tb.connect("enter-notify-event", status_push, "Create a new, empty project.")
new_tb.connect("leave-notify-event", status_pop)
I know the issue is not with the "status_push" and "status_pop" events, as I've connected all my gtk.MenuItem objects to them, and they work swimmingly.
I know that gtk.ToolButton objects are in the Widgets class, so "enter-notify-event" and "leave-notify-event" SHOULD technically work. My only guess is that this particular object does not emit any signals other than "clicked", and thus I'd have to put each in a gtk.EventBox.
What am I doing wrong here? How do I fix this?
Thanks in advance!

Your guess was correct, you should wrap your widget in a gtk.EventBox, here is an example that i hope will be hopeful:
import gtk
def callback(widget, event, data):
print event, data
class Win(gtk.Window):
def __init__(self):
super(Win, self).__init__()
self.connect("destroy", gtk.main_quit)
self.set_position(gtk.WIN_POS_CENTER)
self.set_default_size(250, 200)
tb = gtk.ToolButton(gtk.STOCK_NEW)
# Wrap ``gtk.ToolButton`` in an ``gtk.EventBox``.
ev_box = gtk.EventBox()
ev_box.connect("enter-notify-event", callback, "enter")
ev_box.connect("leave-notify-event", callback, "leave")
ev_box.add(tb)
self.add(ev_box)
if __name__ == '__main__':
Win()
gtk.main()

It appears, based on experimentation and evidence, this is impossible in PyGtk 2.24.

Related

PyQt5: Using DeleteOnClose when switching to new window

I am currently creating a GUI in Python 3.7, using PyQt5 and Qt Designer in the Spyder environment. The GUI has many different windows. Basically I am starting with the UI_Start window and then open the next window when a button is pressed. The GUI is working kind of fine, however after approximately 50 windows the program suddenly doesn't show the next window anymore but also doesn't stop the execution. The weird thing about this issue is that:
the exact same window class has been called a lot of times beforehand and there have never been any issues
the problem does not only occur for one window but it can also occur for another window class (but after the same amount of windows being shown)
I tried to figure out why the .show() command is suddenly not working anymore. I used print statements to see where the program "breaks down". I saw that even the print statements after the .show() command are working but then as the window isn't shown I can't press any button to trigger the next event. So basically the program is hanging.
I am relatively new to programming in Python and creating GUIs but I thought that maybe the problem occurs due to memory leak. This is why I am now trying to open memory space when closing a window by using self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True). However, now I am facing the problem that the next window doesn't show up anymore. So how can I use DeleteOnClose if I want to show a new window afterwards?
Also if anyone has a suggestion for the original problem, please let me know. I am trying to figure out the problem since like a week but have not come any further.
Thank you already!
Some part of my code to work with:
class UI_Start(QtWidgets.QMainWindow):
def __init__(self):
super(UI_Start, self).__init__() # Call the inherited classes __init__ method
uic.loadUi('Screen_Start.ui', self) # Load the .ui file
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) # added newly
self.Start_pushButton_Start.clicked.connect(self.openKommiScreen)
def openKommiScreen(self):
self.close()
self.KommiScreen = UI_Kommi(self)
class UI_Kommi(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(UI_Kommi, self).__init__(parent)
uic.loadUi('Screen_Kommi.ui', self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
global sheetNo
sheetNo = 1
self.WeiterButton = self.findChild(QtWidgets.QPushButton,'pushButton_Weiter')
self.WeiterButton.clicked.connect(self.openScanScreen)
self.show()
def openScanScreen(self):
self.close()
self.ScanScreen = UI_Scan(self)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = UI_Start()
window.show()
sys.exit(app.exec_())
At first I would guess it's a garbage collection problem. The only reference to your new window is stored in your previous one. Which is deleted, so there is no more reference to your window object and python may delete it automatically.
In these cases I often goes for a global variable to store the current windows references.

How does one update a field from outside the __init__() function with pyqt5

I am reading a sensor and want to display its output as a decimal number in a GUI using PyQt5. I have found a number of tutorials that point out the label.setText('myStr') function. This does not work for my setup, however, because I need to update the field based on the input from another function. I'm not very familiar with PyQt5 yet, and I would appreciate any insight into how this problem ought to be approached.
Note: (I am using LCM to acquire data from a Raspberry Pi. I'm not sure that that is relevant to the problem, but it helps explain my code below.)
Here is what I am trying to do:
class Home_Win(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("sensor_interface.ui", self)
self.label_temp.setText('temperature') #Just to verify that I can change it from here
def acquire_sensors(self):
temp = 0 #Make variable available outside nested function
def listen(channel, data):
msg=sensor_data.decode(data)
temp = msg.temperature
lc = lcm.LCM()
subscription = lc.subscribe("sensor_data_0", listen)
while True:
lc.handle()
self.label_temp.setText(str(temp))
Any thoughts on how I can update the GUI to display the readings I am getting from my sensors?
Thanks!
You're almost there. All you need to do is to save the ui in an instance variable in __init__:
self.ui = loadUi("sensor_interface.ui", self)
Then, assuming label_temp is the name of your QLabel widget, just do:
self.ui.label_temp.setText(str(temp))
It turned out that I needed to add repaint(). I also switched to a QLineEdit as this seemed to work better for me. So inside the while loop I now have:
self.ui.lineEdit_temp.setText(str(temp))
self.ui.lineEdit_temp.repaint()
This now outputs live updates to the GUI while reading the data stream.

How to bind the root to call method inside a class

This is the basic layout of my program:
class App(CheckInfo):
def __init__(self, master):
CheckInfo.__init__(self)
master.title("Example")
.....
After that I have i method (inside the class App) that goes like this:
def moveIt(self):
print "It doesnt work"
At the bottom (outside if the class) I have:
root = Tk()
app = App(root)
#root.bind("<Up>",) # I don't know how this works
root.mainloop()
I am trying to make a bind to the root so that at any moment in the window I can press the up key an call the method.
I'm not sure how this is done.
My guesses so far have not made much progress.
I think I might need to put event in: moveIt(self) => moveIt(self,event)
But I have no idea how to put the method as an argument in the bind since:
root.bind("<Up>",moveIt) #doesnt work
root.bind("<Up>",self.moveIt) #obviously not
root.bind("<Up>",root.moveIt) #donsnt make much sense
Any ideas would be appreciated! I hope I have posted all the relevant code, otherwise please ask for any needed clarification.
Thanks in advance.
Your experiments may not be working because a frame by default does not have the keyboard focus. Try adding root.focus() so that keyboard events are directed to the root window.
The other part of your question has to do with how to do the binding. Since moveIt is a method of the class App and 'app' is an instance of that class, what you want is:
root.bind("<Up>", app.moveIt)

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.

Python GTK menu item takes two clicks to activate

Alright, first off I'm not quite sure how to phrase my problem. This could be lack of sleep, or being pretty new to Python and GTK, or a combination. To aid me, I have written a complete bare-bones example with the help of zetcode.com's tutorials.
The problem, as well as I can put it, is a menu item - with no sub-menus - takes two clicks to activate. Unlike a sub-menu item activating on a single click. This is mildly annoying (and likely to confuse future users), but not really causing any problems with my application. I would, however, like to resolve it.
My actual application is being created with the help of Ubuntu Quickly - but the problem exists while using gtkBuilder or straight-gtk.
Here is the bare-bones example:
#!/usr/bin/python
import gtk
class MenuTest(gtk.Window):
def __init__(self):
super(MenuTest, self).__init__()
self.set_title("Menus, how do they work?!")
self.set_size_request(350, 200)
self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(6400, 6400, 6440))
self.set_position(gtk.WIN_POS_CENTER)
mb = gtk.MenuBar()
filemenu = gtk.Menu()
filem = gtk.MenuItem("Some Action")
filem.connect("activate", self.on_file_activate)
mb.append(filem)
vbox = gtk.VBox(False, 2)
vbox.pack_start(mb, False, False, 0)
self.add(vbox)
self.connect("destroy", gtk.main_quit)
self.show_all()
def on_file_activate(self, widget):
md = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, "herp derp, took two clicks to show me")
md.run()
md.destroy()
MenuTest()
gtk.main()
Hopefully someone can help, and not completely confuse this noob at the same time.
You can solve your problem by connecting to the 'button-press-event' signal instead of the 'activate' signal, and making your callback like this:
def on_file_activate(self, widget, event):
if event.button != 1:
return False #only intercept left mouse button
md = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, "herp derp, I only needed one click")
md.run()
md.destroy()
return True
However, why would you want to do that? I'm not surprised that your original code didn't work as expected, because that's not really what menus are for. You'd be better off using a toolbar button, or a regular button. I think misusing a menu as a button is more likely to confuse future users.
I know this is a fairly old thread. But, for the sake of anyone else trying to accomplish this task, the simplest solution is to replace the "activate" signal with the "select" signal. That should fix it. At least, it does on my box.
ie. replace
filem.connect("activate", self.on_file_activate)
with
filem.connect("select", self.on_file_activate)
I would also change the function name for the sake of clarity.
I hope that helps someone. =)

Categories