用Python实现Gtk.StatusIcon弹出菜单

11

我正在尝试将一些小例子从PyGTK移植到新的PyGObject绑定,但是在使用弹出菜单时遇到了一个难题,尽管没有出现错误,但没有菜单在右键单击时显示,这是代码:

from gi.repository import Gtk
class aStatusIcon:
    def __init__(self):
        self.statusicon = Gtk.StatusIcon()
        self.statusicon.set_from_stock(Gtk.STOCK_HOME) 
        self.statusicon.connect("popup-menu", self.right_click_event)

        window = Gtk.Window()
        window.connect("destroy", lambda w: Gtk.main_quit())
        window.show_all()

    def right_click_event(self, icon, button, time):
        menu = Gtk.Menu()

        about = Gtk.MenuItem()
        about.set_label("About")
        quit = Gtk.MenuItem()
        quit.set_label("Quit")

        about.connect("activate", self.show_about_dialog)
        quit.connect("activate", Gtk.main_quit)

        menu.append(about)
        menu.append(quit)

        menu.show_all()

        #menu.popup(None, None, gtk.status_icon_position_menu, button, time, self.statusicon) # previous working pygtk line
        menu.popup(None, None, None, Gtk.StatusIcon.position_menu, button, time) #i assume this is problem line

    def show_about_dialog(self, widget):
        about_dialog = Gtk.AboutDialog()

        about_dialog.set_destroy_with_parent(True)
        about_dialog.set_name("StatusIcon Example")
        about_dialog.set_version("1.0")
        about_dialog.set_authors(["Andrew Steele"])

        about_dialog.run()
        about_dialog.destroy()

aStatusIcon()
Gtk.main()

我猜问题在于我没有告诉菜单关于self.statusicon,但是在任何参数中都不起作用,因为它们都需要一个widget参数或none,而不是statusicon。这里有聪明的人能想出我哪里错了吗?


我在Gtk.StatusIcon的文档中甚至都找不到StatusIcon.position_menu。我看到了gtk.status_icon_position_menu,它明确接受一个StatusIcon参数。这个方法不再可用吗?(相关问题:你是Hairy_Palms吗?你不必回答。) - senderle
StatusIcon.position_menu是调用gtk.status_icon_position_menu的新的gtk3内省方式,如果我像旧方法一样给它一个statusicon,它会抱怨。(相关答案:是的 :) ) - Mike
@Mike,好的,抱歉。我已经有一段时间没有使用pygtk了... 我想我的方法是编写自己的定位函数,该函数将接受一个StatusIcon,调用StatusIcon.get_geometry(),并返回一个(x, y, push_in)元组。但这只是猜测,并假设这些函数没有改变。(顺便问一下,你的代码中menu.popup的签名真的改变了吗?如果是这样,那似乎是一些严重的API破坏。) - senderle
是的,我确实认为我可以做类似的事情,但我确定必须有一种“正确”的方法来做到这一点,menu.popup已经有点“消失”了,它在gi中被映射为self.popup_for_device(None, parent_menu_shell, parent_menu_item, func, data, button, activate_time)与旧的self.popup(parent_menu_shell, parent_menu_item, func, button, activate_time, data=None)相比,或者至少它说它是,这就是问题所在:(因为参数似乎不符合参考文献所述的内容。 - Mike
@Mike,是的,弹出窗口的相对定位有点麻烦——我记得在使用 pygtk 2.12 的时候给我带来了一些麻烦。如果有更好的方法,我还不够聪明去找出来!很抱歉无法提供更多帮助。 - senderle
显示剩余2条评论
2个回答

16

啊,终于解决了!如果还有其他人遇到这个问题,要感谢 Gimpnet #python 上的一位热心帮助者。你必须让菜单保持在作用域内,否则它会被垃圾回收掉,导致没有错误信息,但也没有菜单。这是可工作的代码:

from gi.repository import Gtk

class aStatusIcon:
    def __init__(self):
        self.statusicon = Gtk.StatusIcon()
        self.statusicon.set_from_stock(Gtk.STOCK_HOME)
        self.statusicon.connect("popup-menu", self.right_click_event)

        window = Gtk.Window()
        window.connect("destroy", lambda w: Gtk.main_quit())
        window.show_all()

    def right_click_event(self, icon, button, time):
        self.menu = Gtk.Menu()

        about = Gtk.MenuItem()
        about.set_label("About")
        quit = Gtk.MenuItem()
        quit.set_label("Quit")

        about.connect("activate", self.show_about_dialog)
        quit.connect("activate", Gtk.main_quit)

        self.menu.append(about)
        self.menu.append(quit)

        self.menu.show_all()

        def pos(menu, icon):
                return (Gtk.StatusIcon.position_menu(menu, icon))

        self.menu.popup(None, None, pos, self.statusicon, button, time)

    def show_about_dialog(self, widget):
        about_dialog = Gtk.AboutDialog()

        about_dialog.set_destroy_with_parent(True)
        about_dialog.set_name("StatusIcon Example")
        about_dialog.set_version("1.0")
        about_dialog.set_authors(["Andrew Steele"])

        about_dialog.run()
        about_dialog.destroy()

aStatusIcon()
Gtk.main()

1
似乎在PyGObject中有些东西已经改变了,pos函数现在只从事件中接收两个参数,我会在http://pastebin.com/Rzek336p上放一个更新的答案,因为stackoverflow似乎也搞砸了缩进。 - Mike
我编辑了内联代码以修改你的pastebin。看起来你可能使用了制表符和空格的混合,这可能是为什么SO会搞乱你的格式的原因。 - Laurence Gonsalves
在使用这段代码时,我发现两件事情:首先,self.menu 引用了你的状态图标。这意味着如果用户打开上下文菜单然后关闭它,你将无法删除该图标。可以通过在菜单上获取“deactivate”事件时断开引用(例如:self.menu = None)来解决此问题。其次,你可以将 Gtk.StatusIcon.position_menu 作为第三个参数直接传递给 self.menu.popup。你所拥有的 pos 包装器以相同的顺序和参数传递,只是委托,因此不必要。 - Laurence Gonsalves
@LaurenceGonsalves 我无法解释为什么,但出于某种原因,使用pos包装器可以工作,而不使用则不行。 - erb

0

从上面复制Mike的解决方案,并进行一些小的清理和修复以适应更新的gtk3:

#!/usr/bin/python3
import gi
gi.require_version('Gtk', '3.0')

from gi.repository import Gtk

class MyStatusIconApp:
    def __init__(self):
        self.status_icon = Gtk.StatusIcon()
        self.status_icon.set_from_stock(Gtk.STOCK_HOME)
        self.status_icon.connect("popup-menu", self.right_click_event)

    def right_click_event(self, icon, button, time):
        self.menu = Gtk.Menu()

        about = Gtk.MenuItem()
        about.set_label("About")
        about.connect("activate", self.show_about_dialog)
        self.menu.append(about)

        quit = Gtk.MenuItem()
        quit.set_label("Quit")
        quit.connect("activate", Gtk.main_quit)
        self.menu.append(quit)

        self.menu.show_all()

        self.menu.popup(None, None, None, self.status_icon, button, time)

    def show_about_dialog(self, widget):
        about_dialog = Gtk.AboutDialog()

        about_dialog.set_destroy_with_parent(True)
        about_dialog.set_name("StatusIcon Example")
        about_dialog.set_version("1.0")
        about_dialog.set_authors(["Andrew Steele"])

        about_dialog.run()
        about_dialog.destroy()

app = MyStatusIconApp()
Gtk.main()

(如果gtk再次更改,请随时更新)


我尝试了复制粘贴,但是没有起作用。Python 3.7和GTK 3。 - Phoenix404
@Phoenix404 在我的 Python 3.8.10 上可以工作。 - Robert Siemer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接