You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python GTK3 TreeView实现行内容鼠标选择与复制功能

解决GTK TreeView行选择、右键复制及文本高亮自动复制问题

一、启用TreeView行选择

原代码未配置行选择模式,导致无法选中行。只需添加TreeSelection配置即可:

# 在创建treeview后添加
self.selection = self.treeview.get_selection()
self.selection.set_mode(Gtk.SelectionMode.SINGLE)  # 单选模式,可改为MULTIPLE支持多选

二、实现右键复制行内容功能

步骤1:创建右键菜单并绑定事件

在__init__方法中添加右键菜单及事件绑定:

# 创建右键菜单
self.popup_menu = Gtk.Menu()
copy_item = Gtk.MenuItem(label="复制行内容")
copy_item.connect("activate", self.on_copy_row)
self.popup_menu.append(copy_item)
self.popup_menu.show_all()

# 绑定右键点击事件
self.treeview.connect("button-press-event", self.on_treeview_button_press)

步骤2:实现右键点击与复制逻辑

添加以下两个方法:

def on_treeview_button_press(self, widget, event):
    # 检测右键点击(GTK3中右键对应button=3)
    if event.button == 3:
        self.popup_menu.popup(None, None, None, None, event.button, event.time)
        return True
    return False

def on_copy_row(self, widget):
    # 获取选中行数据
    model, iter = self.selection.get_selected()
    if iter:
        # 拼接行内所有列数据(用制表符分隔)
        row_data = "\t".join([model[iter][0], model[iter][1], model[iter][2]])
        # 复制到系统剪贴板
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(row_data, -1)
        # 显示复制提示
        self.show_copy_tip("行内容已复制")

三、实现文本高亮自动复制及提示

步骤1:允许单元格文本选择

修改单元格渲染器配置,开启文本可选:

# 创建renderer时添加
renderer = Gtk.CellRendererText()
renderer.set_property("selectable", True)  # 允许文本选中

步骤2:监听鼠标释放事件,自动复制高亮文本

绑定button-release-event信号并实现复制逻辑:

# 在__init__中绑定信号
self.treeview.connect("button-release-event", self.on_treeview_button_release)

# 添加复制方法
def on_treeview_button_release(self, widget, event):
    # 获取鼠标位置对应的单元格
    x, y = int(event.x), int(event.y)
    path, column, cell_x, cell_y = self.treeview.get_path_at_pos(x, y)
    if path and column:
        # 获取单元格渲染器及选中文本范围
        renderer = column.get_cells()[0]
        selection = renderer.get_selection()
        if selection:
            start, end = selection
            model = self.treeview.get_model()
            iter = model.get_iter(path)
            full_text = model[iter][column.get_sort_column_id()]
            selected_text = full_text[start:end]
            if selected_text:
                # 复制到剪贴板并提示
                clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
                clipboard.set_text(selected_text, -1)
                self.show_copy_tip("内容已复制")

四、实现复制提示功能

添加临时提示标签,显示3秒后自动隐藏:

# 在__init__中创建提示标签
self.copy_tip = Gtk.Label(label="")
self.copy_tip.set_halign(Gtk.Align.CENTER)
self.copy_tip.set_valign(Gtk.Align.TOP)
self.copy_tip.set_margin_top(10)
self.grid.attach(self.copy_tip, 0, 11, 5, 1)
self.copy_tip.hide()

# 添加提示显示与隐藏方法
def show_copy_tip(self, message):
    self.copy_tip.set_text(message)
    self.copy_tip.show()
    # 3秒后自动隐藏提示
    Gtk.timeout_add(3000, self.hide_copy_tip)

def hide_copy_tip(self):
    self.copy_tip.hide()
    return False

完整修改后的代码

import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk

# list of tuples for each variable, containing the environment variable name, its path, and the security category
variable_list = [
    ("$ENV1", "/usr/share/background/image.png", "Image"),
    ("$ENV2", "/home/user/script.py", "Program"),
    ("$ENV3", "/usr/share/icons/icon.png", "Image")
]


class TreeViewFilterWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Environment Variable View")
        self.set_border_width(10)

        # Setting up the self.grid in which the elements are to be positioned
        self.grid = Gtk.Grid()
        self.grid.set_column_homogeneous(True)
        self.grid.set_row_homogeneous(True)
        self.add(self.grid)

        # Creating the ListStore model
        self.variable_liststore = Gtk.ListStore(str, str, str)
        for variable_ref in variable_list:
            self.variable_liststore.append(list(variable_ref))
        self.current_filter_category = None

        # Creating the filter, feeding it with the liststore model
        self.category_filter = self.variable_liststore.filter_new()
        # setting the filter function, note that we're not using the
        self.category_filter.set_visible_func(self.category_filter_func)

        # creating the treeview, making it use the filter as a model, and adding the columns
        self.treeview = Gtk.TreeView(model=self.category_filter)
        # 启用行选择,设置为单选模式
        self.selection = self.treeview.get_selection()
        self.selection.set_mode(Gtk.SelectionMode.SINGLE)

        for i, column_title in enumerate(
            ["Variable", "Path", "Category"]
        ):
            renderer = Gtk.CellRendererText()
            # 设置单元格文本可选中
            renderer.set_property("selectable", True)
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            self.treeview.append_column(column)

        # 创建右键菜单
        self.popup_menu = Gtk.Menu()
        copy_item = Gtk.MenuItem(label="复制行内容")
        copy_item.connect("activate", self.on_copy_row)
        self.popup_menu.append(copy_item)
        self.popup_menu.show_all()

        # 绑定右键点击事件
        self.treeview.connect("button-press-event", self.on_treeview_button_press)
        # 绑定鼠标释放事件,检测文本高亮复制
        self.treeview.connect("button-release-event", self.on_treeview_button_release)

        # 创建复制提示标签
        self.copy_tip = Gtk.Label(label="")
        self.copy_tip.set_halign(Gtk.Align.CENTER)
        self.copy_tip.set_valign(Gtk.Align.TOP)
        self.copy_tip.set_margin_top(10)
        self.grid.attach(self.copy_tip, 0, 11, 5, 1)
        self.copy_tip.hide()

        # creating buttons to filter by Category, and setting up their events
        self.buttons = list()
        for var_category in ["Image", "Program"]:
            button = Gtk.Button(label=var_category)
            self.buttons.append(button)
            button.connect("clicked", self.on_selection_button_clicked)

        # setting up the layout, putting the treeview in a scrollwindow, and the buttons in a row
        self.scrollable_treelist = Gtk.ScrolledWindow()
        self.scrollable_treelist.set_vexpand(True)
        self.grid.attach(self.scrollable_treelist, 0, 0, 5, 10)
        self.grid.attach_next_to(
            self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1
        )
        for i, button in enumerate(self.buttons[1:]):
            self.grid.attach_next_to(
                button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1
            )
        self.scrollable_treelist.add(self.treeview)

        self.show_all()

    def category_filter_func(self, model, iter, data):
        """Tests if the Category in the row is the one in the filter"""
        if (
            self.current_filter_category is None
            or self.current_filter_category == "None"
        ):
            return True
        else:
            return model[iter][2] == self.current_filter_category

    def on_selection_button_clicked(self, widget):
        """Called on any of the button clicks"""
        # we set the current category filter to the button's label
        self.current_filter_category = widget.get_label()
        print("%s category selected!" % self.current_filter_category)
        # we update the filter, which updates in turn the view
        self.category_filter.refilter()

    def on_treeview_button_press(self, widget, event):
        # 检测右键点击(GTK3中右键是button=3)
        if event.button == 3:
            self.popup_menu.popup(None, None, None, None, event.button, event.time)
            return True
        return False

    def on_copy_row(self, widget):
        # 获取选中行
        model, iter = self.selection.get_selected()
        if iter:
            # 获取行内所有数据,用制表符分隔
            row_data = "\t".join([model[iter][0], model[iter][1], model[iter][2]])
            # 获取剪贴板
            clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            clipboard.set_text(row_data, -1)
            # 显示复制提示
            self.show_copy_tip("行内容已复制")

    def on_treeview_button_release(self, widget, event):
        # 获取当前鼠标位置对应的单元格
        x, y = int(event.x), int(event.y)
        path, column, cell_x, cell_y = self.treeview.get_path_at_pos(x, y)
        if path and column:
            # 获取单元格的渲染器
            renderer = column.get_cells()[0]
            # 获取选中的文本范围
            selection = renderer.get_selection()
            if selection:
                start, end = selection
                model = self.treeview.get_model()
                iter = model.get_iter(path)
                full_text = model[iter][column.get_sort_column_id()]
                selected_text = full_text[start:end]
                if selected_text:
                    clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
                    clipboard.set_text(selected_text, -1)
                    self.show_copy_tip("内容已复制")

    def show_copy_tip(self, message):
        self.copy_tip.set_text(message)
        self.copy_tip.show()
        # 3秒后隐藏提示
        Gtk.timeout_add(3000, self.hide_copy_tip)

    def hide_copy_tip(self):
        self.copy_tip.hide()
        return False


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

关键说明

  • 行选择:通过TreeSelection配置选择模式,支持单选/多选。
  • 右键复制:监听右键点击事件弹出菜单,获取选中行数据后复制到剪贴板。
  • 文本高亮复制:开启单元格文本可选,监听鼠标释放事件检测选中文本,自动复制并提示。
  • 提示功能:用Label组件临时显示复制状态,3秒后自动隐藏,避免界面冗余。

内容的提问来源于stack exchange,提问作者Develobeer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 13:05:30