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

如何让KP_Down等小键盘按键实现ttk.Treeview的默认滚动效果?

解决ttk.Treeview小键盘按键无法滚动的问题

在tkinter中,<KP_Down>、<KP_Up>、<KP_Next>、<KP_Prior>小键盘按键默认无法像<KeyPress-Down>、<KeyPress-Up>、<KeyPress-Next>、<KeyPress-Prior>普通按键那样滚动ttk.Treeview组件,示例代码如下:

import random
import tkinter as tk
import tkinter.ttk as ttk


class App(ttk.Frame):

    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        self.master = master
        self.tree = None
        self.ysb = None
        self.xsb = None
        self.create_widgets()
        self.create_bindings()

    def create_widgets(self):
        self.tree = ttk.Treeview(
            self.master, height=10, selectmode='extended', takefocus=True,
            columns=("x", "y", "z"), displaycolumns=["x", "y", "z"])
        self.tree.heading('#0', text="Points")
        self.tree.heading('x', text='X', anchor='center')
        self.tree.heading('y', text='Y', anchor='center')
        self.tree.heading('z', text='Z', anchor='center')
        self.tree.column('#0', stretch=True, width=100)
        self.tree.column('x', stretch=True, anchor='center', width=50)
        self.tree.column('y', stretch=True, anchor='center', width=50)
        self.tree.column('z', stretch=True, anchor='center', width=50)

        self.ysb = ttk.Scrollbar(
            self.master, orient='vertical', command=self.tree.yview)
        self.xsb = ttk.Scrollbar(
            self.master, orient='horizontal', command=self.tree.xview)
        self.tree.configure(
            yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)

        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.tree.grid(row=0, column=0, sticky='nsew')
        self.ysb.grid(row=0, column=1, sticky='ns')
        self.grid(row=1, column=0, sticky='ew')

        groups = ["Local", "Remote"]
        for n, group in enumerate(groups):
            self.tree.insert(
                "", 'end', iid=f'G{n}', text=group, open=True)
            for i in range(10):
                self.tree.insert(
                    f'G{n}', 'end', iid=f'G{n}-D{i}',
                    text=f'G{n}-D{i}',
                    values=[
                        random.randrange(0, 20),
                        random.randrange(0, 20),
                        random.randrange(0, 20)
                    ]
                )

    def create_bindings(self):
        self.tree.bind('<KeyPress-Down>', self.kp_down)
        self.tree.bind('<KeyPress-Up>', self.kp_up)
        self.tree.bind('<KeyPress-Next>', self.kp_down)
        self.tree.bind('<KeyPress-Prior>', self.kp_up)
        self.tree.bind('<KP_Down>', self.kp_down)
        self.tree.bind('<KP_Up>', self.kp_up)
        self.tree.bind('<KP_Next>', self.kp_down)
        self.tree.bind('<KP_Prior>', self.kp_up)

    def kp_down(self, event):
        print(f"{event.keysym}")

    def kp_up(self, event):
        print(f"{event.keysym}")


if __name__ == '__main__':
    root = tk.Tk()
    app = App(root)
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)
    app.grid(row=0, column=0, sticky='nsew')
    root.mainloop()

解决方案

我们可以手动实现小键盘按键对应的滚动和选中逻辑,让小键盘的上下键、翻页键和普通按键表现一致。修改kp_down和kp_up函数,分别处理单步滚动和翻页滚动的逻辑:

修改后的完整代码

import random
import tkinter as tk
import tkinter.ttk as ttk


class App(ttk.Frame):

    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        self.master = master
        self.tree = None
        self.ysb = None
        self.xsb = None
        self.create_widgets()
        self.create_bindings()

    def create_widgets(self):
        self.tree = ttk.Treeview(
            self.master, height=10, selectmode='extended', takefocus=True,
            columns=("x", "y", "z"), displaycolumns=["x", "y", "z"])
        self.tree.heading('#0', text="Points")
        self.tree.heading('x', text='X', anchor='center')
        self.tree.heading('y', text='Y', anchor='center')
        self.tree.heading('z', text='Z', anchor='center')
        self.tree.column('#0', stretch=True, width=100)
        self.tree.column('x', stretch=True, anchor='center', width=50)
        self.tree.column('y', stretch=True, anchor='center', width=50)
        self.tree.column('z', stretch=True, anchor='center', width=50)

        self.ysb = ttk.Scrollbar(
            self.master, orient='vertical', command=self.tree.yview)
        self.xsb = ttk.Scrollbar(
            self.master, orient='horizontal', command=self.tree.xview)
        self.tree.configure(
            yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)

        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.tree.grid(row=0, column=0, sticky='nsew')
        self.ysb.grid(row=0, column=1, sticky='ns')
        self.grid(row=1, column=0, sticky='ew')

        groups = ["Local", "Remote"]
        for n, group in enumerate(groups):
            self.tree.insert(
                "", 'end', iid=f'G{n}', text=group, open=True)
            for i in range(10):
                self.tree.insert(
                    f'G{n}', 'end', iid=f'G{n}-D{i}',
                    text=f'G{n}-D{i}',
                    values=[
                        random.randrange(0, 20),
                        random.randrange(0, 20),
                        random.randrange(0, 20)
                    ]
                )

    def create_bindings(self):
        self.tree.bind('<KeyPress-Down>', self.kp_down)
        self.tree.bind('<KeyPress-Up>', self.kp_up)
        self.tree.bind('<KeyPress-Next>', self.kp_down)
        self.tree.bind('<KeyPress-Prior>', self.kp_up)
        self.tree.bind('<KP_Down>', self.kp_down)
        self.tree.bind('<KP_Up>', self.kp_up)
        self.tree.bind('<KP_Next>', self.kp_down)
        self.tree.bind('<KP_Prior>', self.kp_up)

    def kp_down(self, event):
        tree_height = self.tree.cget('height')
        all_items = self.tree.get_children()
        
        if event.keysym in ('Next', 'KP_Next'):
            # 处理翻页向下逻辑
            current_visible = self.tree.identify_row(self.tree.winfo_height() - 1)
            if current_visible and current_visible in all_items:
                current_idx = all_items.index(current_visible)
                target_idx = current_idx + tree_height
                if target_idx < len(all_items):
                    target_item = all_items[target_idx]
                    self.tree.selection_set(target_item)
                    self.tree.focus(target_item)
                    self.tree.see(target_item)
        else:
            # 处理单步向下逻辑
            current_item = self.tree.focus()
            if not current_item and all_items:
                current_item = all_items[0]
            next_item = self.tree.next(current_item)
            if next_item:
                self.tree.selection_set(next_item)
                self.tree.focus(next_item)
                self.tree.see(next_item)
        return "break"

    def kp_up(self, event):
        tree_height = self.tree.cget('height')
        all_items = self.tree.get_children()
        
        if event.keysym in ('Prior', 'KP_Prior'):
            # 处理翻页向上逻辑
            current_visible = self.tree.identify_row(0)
            if current_visible and current_visible in all_items:
                current_idx = all_items.index(current_visible)
                target_idx = current_idx - tree_height
                if target_idx >= 0:
                    target_item = all_items[target_idx]
                    self.tree.selection_set(target_item)
                    self.tree.focus(target_item)
                    self.tree.see(target_item)
        else:
            # 处理单步向上逻辑
            current_item = self.tree.focus()
            if not current_item and all_items:
                current_item = all_items[-1]
            prev_item = self.tree.prev(current_item)
            if prev_item:
                self.tree.selection_set(prev_item)
                self.tree.focus(prev_item)
                self.tree.see(prev_item)
        return "break"


if __name__ == '__main__':
    root = tk.Tk()
    app = App(root)
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)
    app.grid(row=0, column=0, sticky='nsew')
    root.mainloop()

代码说明

  • 单步滚动(KP_Down/KP_Up):获取当前选中项,通过tree.next()/tree.prev()获取下一个/上一个项,然后设置选中、焦点并滚动到视图中。如果没有选中项,默认选中第一个或最后一个可见项。
  • 翻页滚动(KP_Next/KP_Prior):通过tree.identify_row()获取当前可见区域的第一个/最后一个项,计算翻页后的目标索引,跳转到对应项并更新选中和视图。
  • 返回"break"是为了阻止事件继续传递,避免触发其他默认行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 06:57:09