【问题标题】:Cut, Copy, Paste, Select All Functions - How to simply allow default handling after clicking on menu item?剪切、复制、粘贴、选择所有功能 - 单击菜单项后如何简单地允许默认处理?
【发布时间】:2026-01-30 08:55:01
【问题描述】:

“Ctrl+C”和“Ctrl+V”快捷键(以及“右键菜单”)在任何 GTK 应用程序中默认可用,例如只有 SourceView 的简单 hello world 应用程序(见下文)。 但是,如果我添加一个菜单项“Edit->Copy”并为其分配“Ctrl+C”加速器和相应的回调函数,那么它显然会停止工作,因为我正在用自己的方法拦截信号。那么,如何在我的自定义方法中触发默认的剪切/复制/粘贴/select_all 功能?

注意:返回 False 适用于粘贴功能,但不适用于复制/剪切/全选

简单示例 - 在这种情况下,所有功能(剪切/复制/粘贴/全选)都可以正常工作。

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '3.0') 
from gi.repository import Gtk, Gdk, Pango, GObject, GtkSource

class MyOwnApp(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Hello World")
        self.set_default_size(500, 500)

        self.vbox = Gtk.VBox()

        editor = GtkSource.View.new()
        editor.set_show_line_numbers(True)
        editor.set_auto_indent(True)
        editor_buffer = editor.get_buffer()
        self.vbox.pack_start(editor, False, False, 0)

        self.add(self.vbox)

 win = MyOwnApp()
 win.connect("destroy", Gtk.main_quit)
 win.show_all()
 Gtk.main()

如果我添加带有回调的菜单项,它们将不再起作用。

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '3.0')
from gi.repository import Gtk, Gdk, Pango, GObject, GtkSource

class MyOwnApp(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Hello World")

        self.set_default_size(900, 900)

        box_outer = Gtk.VBox()

        # MENUBAR setup
        menuBar = Gtk.MenuBar()
        # Set accelerators
        agr = Gtk.AccelGroup()
        self.add_accel_group(agr)

        # File menu
        file_menu_dropdown = Gtk.MenuItem("File")
        menuBar.append(file_menu_dropdown)
        file_menu = Gtk.Menu()
        file_menu_dropdown.set_submenu(file_menu)

        # File menu Items
        file_exit = Gtk.MenuItem("Exit")
        key, mod = Gtk.accelerator_parse("<Control>Q")
        file_exit.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        file_exit.connect("activate", self.quit)

        file_menu.append(file_exit)

        # Edit menu
        edit_menu_dropdown = Gtk.MenuItem("Edit")
        menuBar.append(edit_menu_dropdown)
        edit_menu = Gtk.Menu()
        edit_menu_dropdown.set_submenu(edit_menu)

        # Edit menu Items
        edit_cut = Gtk.MenuItem("Cut")
        key, mod = Gtk.accelerator_parse("<Control>X")
        edit_cut.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_cut.connect("activate", self.on_toolbutton_cut_clicked)

        edit_copy = Gtk.MenuItem("Copy")
        key, mod = Gtk.accelerator_parse("<Control>C")
        edit_copy.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_copy.connect("activate", self.on_toolbutton_copy_clicked)

        edit_paste = Gtk.MenuItem("Paste")
        key, mod = Gtk.accelerator_parse("<Control>V")
        edit_paste.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_paste.connect("activate", self.on_toolbutton_paste_clicked)

        edit_select_all = Gtk.MenuItem("Select All")
        key, mod = Gtk.accelerator_parse("<Control>A")
        edit_select_all.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_select_all.connect("activate", self.on_toolbutton_select_all_clicked)

        edit_menu.append(edit_select_all)
        edit_menu.append(edit_cut)
        edit_menu.append(edit_copy)
        edit_menu.append(edit_paste)

        box_outer.pack_start(menuBar, False, False, 0)

        # SourceView
        editor = GtkSource.View.new()
        editor.set_show_line_numbers(True)
        editor.set_auto_indent(True)
        editor_buffer = editor.get_buffer()
        box_outer.pack_start(editor, True, True, 0)

        self.add(box_outer)

    def quit(self,widget=None):
        Gtk.main_quit()

    def on_toolbutton_select_all_clicked(self, widget):
        return False

    def on_toolbutton_cut_clicked(self, widget):
        return False

    def on_toolbutton_copy_clicked(self, widget):
        return False

    def on_toolbutton_paste_clicked(self, widget):
        return False

win = MyOwnApp()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

【问题讨论】:

    标签: python clipboard gtk3 pygtk pygobject


    【解决方案1】:

    。在 editor_buffer(GtkSourceBuffer) 中调用特定于操作的函数,模拟(剪切/复制/粘贴/全选)的默认处理。

        def on_toolbutton_select_all_clicked(self, widget):
        print("Select all")
        if self.editor.is_focus():
            self.editor_buffer.select_range(self.editor_buffer.get_start_iter(), self.editor_buffer.get_end_iter())
        else:
            self.entry.select_region(0,-1)
        return True
    
    def on_toolbutton_cut_clicked(self, widget):
        print("Cut")
        if self.editor.is_focus():
            self.editor_buffer.cut_clipboard(self.clipboard,self.editor_buffer)
        else:
            self.entry.emit("cut-clipboard")
        return True
    
    def on_toolbutton_copy_clicked(self, widget):
        if self.editor.is_focus():
            self.editor_buffer.copy_clipboard(self.clipboard)
        else:
            self.entry.emit("copy-clipboard")
        return True
    
    def on_toolbutton_paste_clicked(self, widget):
        if self.editor.is_focus():
            self.editor_buffer.paste_clipboard(self.clipboard, None, self.editor_buffer)
        else:
            self.entry.emit("paste-clipboard")
        return True
    

    有关更多详细信息,您可以查看此 (https://developer.gnome.org/pygtk/stable/class-gtktextbuffer.html)。对于其他发射信号的小部件来说,这个实现也应该适用于 SourceView。

    【讨论】:

    • 感谢您的回答,它部分工作。默认情况下,剪切/复制/粘贴适用于不同的小部件(例如,假设在 gtksourceview 旁边,还有一个入口小部件) - 如果您将光标放在入口小部件内并按 Ctrl+A,则使用您的解决方案,然后将选择 gtksourceview 中的文本而不是条目小部件的文本。复制功能等也有同样的问题。
    • 哦,好吧..你能分享一个我建议的方法不起作用的示例代码吗?我会研究它并尝试为它提供更好的解决方案。
    • 这里是代码的要点(包括您的建议):gist.github.com/gittix09/a0561c63d0d4f16d0e322d1fcfc8910e。例如,尝试在两个小部件(条目和源视图)中编写一些内容,然后(将光标聚焦在条目中)按 Ctrl+A
    • 预期的行为会是什么......当您在 gtkEntry 中键入文本并单击菜单项“edit_select_all”时,它应该在 Gtkentry 或 GtkSource 视图中全选吗?
    • 我不知道这是否有任何适当的解决方案。根据我的搜索,我没有得到任何适当的解决方案。但我可以建议一种方法来实现这一点:- 1)不要连接加速组,这将使默认功能可用 2)并在菜单项的相应回调函数中执行“is_focus”检查它查看当前聚焦的小部件.然后进行相应的处理或尝试发出信号(gtk_widget_event)。如果您在应用程序中有文本小部件日志,这将很难处理
    【解决方案2】:

    经过数小时的研究,我很高兴为所有 GTK 爱好者发布此解决方案!!
    感谢 @SivaGuru 的贡献!

    使用此解决方案,您可以在窗口内的多个小部件中使用剪切/复制/粘贴/selectAll 功能(Gtk.EntryGtkSource.View)。
    关键是这两个小部件对剪切/复制/粘贴/selectAll 功能使用不同的方法,但是(正如预期的那样)它们都具有管理这些基本功能的默认方法。无需重新发明*。
    注意:Gtk.Entry 小部件继承自 Gtk.Editable 接口,该接口具有回退到默认处理剪切/复制/过去/全选的所有必要功能。

    import gi
    gi.require_version('Gtk', '3.0')
    gi.require_version('GtkSource', '3.0')
    from gi.repository import Gtk, Gdk, GtkSource
    
    class MyWindow(Gtk.Window):
    
        def __init__(self):
            Gtk.Window.__init__(self, title="Hello World")
    
            self.set_default_size(900, 900)
    
            box_outer = Gtk.VBox()
    
            # MENUBAR setup
            menuBar = Gtk.MenuBar()
            # Set accelerators
            agr = Gtk.AccelGroup()
            self.add_accel_group(agr)
    
            # File menu
            file_menu_dropdown = Gtk.MenuItem("File")
            menuBar.append(file_menu_dropdown)
            file_menu = Gtk.Menu()
            file_menu_dropdown.set_submenu(file_menu)
    
            #File menu Items
            file_exit = Gtk.MenuItem("Exit")
            key, mod = Gtk.accelerator_parse("<Control>Q")
            file_exit.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            file_exit.connect("activate", self.quit)
    
            file_menu.append(file_exit)
    
            # Edit menu
            edit_menu_dropdown = Gtk.MenuItem("Edit")
            menuBar.append(edit_menu_dropdown)
            edit_menu = Gtk.Menu()
            edit_menu_dropdown.set_submenu(edit_menu)
    
            # Edit menu Items
            edit_cut = Gtk.MenuItem("Cut")
            key, mod = Gtk.accelerator_parse("<Control>X")
            edit_cut.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_cut.connect("activate", self.on_toolbutton_cut_clicked)
    
            edit_copy = Gtk.MenuItem("Copy")
            key, mod = Gtk.accelerator_parse("<Control>C")
            edit_copy.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_copy.connect("activate", self.on_toolbutton_copy_clicked)
    
            edit_paste = Gtk.MenuItem("Paste")
            key, mod = Gtk.accelerator_parse("<Control>V")
            edit_paste.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_paste.connect("activate", self.on_toolbutton_paste_clicked)
    
            edit_select_all = Gtk.MenuItem("Select All")
            key, mod = Gtk.accelerator_parse("<Control>A")
            edit_select_all.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_select_all.connect("activate", self.on_toolbutton_select_all_clicked)
    
            edit_menu.append(edit_select_all)
            edit_menu.append(edit_cut)
            edit_menu.append(edit_copy)
            edit_menu.append(edit_paste)
    
            box_outer.pack_start(menuBar, False, False, 0)
    
            entry = Gtk.Entry()
            box_outer.pack_start(entry, False, False, 0)
    
            editor = GtkSource.View.new()
            editor.set_show_line_numbers(True)
            editor.set_auto_indent(True)
            box_outer.pack_start(editor, True, True, 0)
    
            self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
    
            self.add(box_outer)
    
        def quit(self,widget=None):
            Gtk.main_quit()
    
        def on_toolbutton_select_all_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.select_region(0, -1)
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.select_range(editor_buffer.get_start_iter(), editor_buffer.get_end_iter())
                    else:
                        pass
    
        def on_toolbutton_cut_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.cut_clipboard()
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.cut_clipboard(self.clipboard, editor_buffer)
                    else:
                        pass
    
        def on_toolbutton_copy_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.copy_clipboard()
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.copy_clipboard(self.clipboard)
                    else:
                        pass
    
        def on_toolbutton_paste_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.paste_clipboard()
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.paste_clipboard(self.clipboard, None, editor_buffer)
                    else:
                        pass
    
    win = MyWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()
    

    【讨论】: