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. =)
Related
I got a couple of questions regarding qDialogButtonBox. While my code still works, I believed that there are a few parts that can be better refined/ I am not finding much info online
class testDialog(QtGui.QDialog):
def __init_(self, parent=None):
...
self.init_ui()
self.signals_connection()
def init_ui(self):
...
self.buttonBox = QtGui.QDialogButtonBox()
self.buttonBox.addButton("Help", QtGui.QDialogButtonBox.HelpRole)
self.buttonBox.addButton("Apply", QtGui.QDialogButtonBox.AcceptRole)
self.buttonBox.addButton("Cancel", QtGui.QDialogButtonBox.RejectRole)
#
def signals_connection(self):
self.test_random.clicked.connect(self.test_rand)
# Is this the latest/correct way to write it?
self.buttonBox.accepted.connect(self.test_apply)
self.buttonBox.rejected.connect(self.test_cancel)
self.buttonBox.helpRequested.connect(self.test_help)
def test_apply(self):
print "I am clicking on Apply"
def test_cancel(self):
print "I am clicking on Cancel"
self.close()
def test_help(self):
print "I am clicking for Help!"
My questions are as follows:
Under my function - signals_connection(), the lines that I wrote for
the buttonBox (though the code still works) are quite different
for the signal I have wrote for the self.test_random and I am
unable to find any similar online for the qdialogbuttonbox.. There
is another style that I have found - self.connect(self.buttonBox,
QtCore.SIGNAL("accepted()"), self, QtCore.SLOT("accept()")) but I
think that is the old style?? Otherwise what should be the right way
to write it?
In my test_cancel() function, is writing self.close() the best
way to close the application? The way that I run my program is as
follows:
dialog = testDialog();dialog.show()
Lastly, is it possible to add 3 different tool tips to the 3 buttons I have created? I saw that there is a command for it - self.buttonBox.setToolTip("Buttons for life!"), but this will results in all 3 buttons to have the same tool tip. Can I make it as individual?
Yes, that is the correct way to write signal connections (the other syntax you found is indeed the old way of doing it). You can find all the signals in the pyqt documentation for QDialogButtonBox. Different widgets and objects have different signals. QPushButton's and QDialogButtonBox's have different signals.
Yes, close() will close the dialog. The QApplication will exit by default if there are no other windows open. However, if this is a modal dialog, you typically want to close a dialog with either the accept or reject command. This will alert the calling function as to whether the dialog was closed with the Ok/Yes/Apply button or closed with the No/Cancel button.
You can set different tooltips for different buttons in the QDialogButtonBox. You just need to get a reference to the specific button you want to set the tooltip for.
For example
self.buttonBox.button(QDialogButtonBox.Help).setToolTip('Help Tooltip')
self.buttonBox.button(QDialogButtonBox.Ok).setToolTip('Apply Tooltip')
Or you could loop through all the buttons
for button in self.buttonBox.buttons():
if button.text() == 'Help':
button.setToolTip('Help Tooltip')
elif button.text() == 'Apply':
button.setToolTip('Apply Tooltip')
Also, you could connect the accepted and rejected signals from the QDialogButtonBox to the accept and reject slots on the QDialog
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
That way, you won't have to manually connect the Ok and Cancel buttons to your callbacks for closing the dialog.
I'm new to PyQt. I have been searching on how to find the window of my PyQt app which is currently open and bring it to the front. This far all I've found is an example in which pywin32 was used(thus windows specific). I wanted to ask if there is a platform-independent way I can achieve the objective. Any help would be much appreciated.
Here is my code. The activateWindow() function is supposed to bring it to the front.
class TestApp(QtGui.QApplication):
def __init__(self, argv, key):
QtGui.QApplication.__init__(self, argv)
self._activationWindow=None
self._memory = QtCore.QSharedMemory()
self._memory.setKey(key)
if self._memory.attach():
self._running = True
else:
self._running = False
if not self._memory.create(1):
raise RuntimeError(
self._memory.errorString().toLocal8Bit().data())
def isRunning(self):
return self._running
def activationWindow(self):
return self._activationWindow
def setActivationWindow(self, activationWindow):
self._activationWindow = activationWindow
def activateWindow(self):
if not self._activationWindow:
return
self._activationWindow.setWindowState( self._activationWindow.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self._activationWindow.raise_()
self._activationWindow.show()
self._activationWindow.activateWindow()
A complete, platform-indepenent solution is probably going to be beyond reach. Each of the platforms supported by Qt behaves in a different way, and activateWindow seems to be somewhat buggy.
To start with, here's what the Qt docs say about activateWindow:
This function performs the same operation as clicking the mouse on the
title bar of a top-level window. On X11, the result depends on the
Window Manager. If you want to ensure that the window is stacked on
top as well you should also call raise(). Note that the window must be
visible, otherwise activateWindow() has no effect.
and:
On Windows, if you are calling this when the application is not
currently the active one then it will not make it the active window.
It will change the color of the taskbar entry to indicate that the
window has changed in some way. This is because Microsoft does not
allow an application to interrupt what the user is currently doing in
another application.
For more confirming evidence of the difficulties, take a look at these threads on the Qt forum:
Bring window to front -> raise(), show(), activateWindow() don’t work on Windows
activateWindow() does not send window to front
Solved:
Thanks to Aya's answer below I now know that the issue was caused by self.panel = wx.Panel(self, -1) on line 18. I created a panel and didn't attach anything to it. The original issue description is still below for reference.
My Google-fu has failed me. I'm building the text editor that you can find here, written in Python with wxPython:
https://github.com/joshsaintjacque/py-ed/blob/master/pyed.py
The issue that I'm running into is this: when I open a text file (the only functionality built in at this point) that's larger than the viewable area in the TextCtrl the scroll bar remains disabled until the window is re-sized, then it works fine.
I know that the act of re-sizing the window is running some command that I'm neglecting to include in my OpenFile function (or perhaps in init), but I can't figure out what.
Any thoughts anyone has that could lead me in the right direction would be greatly appreciated.
Thanks!
+1 for including a link to the full source code - makes it so much easier to test.
I couldn't reproduce the fault you describe on wxPython 2.8.12 on Win32, but upon running your code, I found a seemingly extraneous wx.Panel object being created on pyed.py line 18...
self.panel = wx.Panel(self, -1)
...which seems to be interfering with the correct operation of the program. After commenting out that line, it seems to work fine.
A couple of other things I noticed: line 56...
self.SetTitle("PyEd - Editing ... " + filename)
...should probably be put in the preceding if-block, otherwise you'll get an error if the user clicks "Cancel" on the wx.FileDialog, and on line 16...
wx.Frame.__init__(self, parent, id, 'PyEd', (-1, -1), wx.Size(640, 480))
...if you use keyword args rather than positional args...
wx.Frame.__init__(self, parent=parent, id=id, title='PyEd', size=wx.Size(640, 480))
...you needn't bother re-specifying the default value for the window position, which is also slightly safer, in case the wxPython developers decide to change the defaults in a future version.
You can also factor out constant values, and the optional creation of the wx.Size object to reduce that line to...
wx.Frame.__init__(self, parent=None, title='PyEd', size=(640, 480))
Finally, with regards to IDs: in most cases you'll probably find they're of little use. Where they come in handy is where you want many similar controls, and it makes more sense to have them handled by a single event handler function.
Consider this example...
def create_buttons(parent):
parent.button1 = wx.Button(label='Button 1')
parent.button2 = wx.Button(label='Button 2')
parent.button3 = wx.Button(label='Button 3')
parent.button1.Bind(wx.EVT_BUTTON, on_button_1)
parent.button2.Bind(wx.EVT_BUTTON, on_button_2)
parent.button3.Bind(wx.EVT_BUTTON, on_button_3)
def on_button_1(event):
print 'You clicked button 1'
def on_button_2(event):
print 'You clicked button 2'
def on_button_3(event):
print 'You clicked button 3'
...which is fine, but if you need, say, 100 buttons, you may prefer to implement it like this...
def create_buttons(parent):
parent.buttons = [wx.Button(id=i, label='Button %d' % i) for i in range(100)]
parent.Bind(wx.EVT_BUTTON, on_button)
def on_button(event):
button_id = event.GetId()
print 'You clicked button %d' % button_id
Oh, and be careful using id as a variable name, because it's also a Python built-in function name.
It looks as if you're not setting the min or max size hints for the window, nor are you calling Self.Fit() to fit the box sizer to the window size (or is it the other way round? I'm rusty on my wxPython...)
Right where you call self.SetSizer(sizer), you should be able to fix this by adding:
self.Fit()
self.SetSizeHintSz(minSize=wx.Size(640, 480))
You may be able to get around the separate call to self.Fit() by using self.SetSizerAndFit()
(edited for spelling.)
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.
Experimenting with a battery monitor icon at the moment in Python using pygtk and egg.trayicon to create an icon to display a battery icon/tooltip.
I seem to be able to add the icon and the tooltip text, but when it then reaches the gtk.main() stage I need a way to modify these so it can then show the updated values.
I've tried gobject.idle_add() and gobject.timeout_add() without much luck, not sure where to go from this.
Anyone got any ideas?
EDIT: Perhaps not the clearest of questions.
I need to loop, fetching information from acpi while running and apply it to widgets inside the gtk container.
EDIT 2: Ok, it's properly down now. The issue was that I wasn't returning anything inside my callback. I just gave it "return 123" and now it's happily chugging away in my system tray, notifying me of my battery percentage :)
This example works for me:
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4
import gobject
import gtk
from egg import trayicon
label = gtk.Label("Over here")
def callback(widget, ev):
label.set_text("You found me")
def timeout():
label.set_text("What are you waiting for?")
tray = trayicon.TrayIcon("TrayIcon")
box = gtk.EventBox()
box.add(label)
tray.add(box)
tray.show_all()
box.connect("button-press-event", callback)
gobject.timeout_add(3000L, timeout)
gtk.main()
Without seeing your code, it's hard to tell what doesn't work.