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

Treeview上层Frame/Canvas背景色无法修改问题求助

问题:Treeview拖拽选中外层组件无法修改背景色

问题描述

希望实现鼠标点击拖拽Treeview时,在选中区域周围生成轮廓。计划通过在Treeview上层创建Frame(或绘制矩形的Canvas)来实现,并通过wm_attribute将其背景色设为透明。目前组件能正确置于Treeview上层,但无法修改其背景色,始终显示白色。使用Windows系统,主要使用ttkbootstrap组件(Treeview上层的组件为tk原生组件),尝试过Canvas和Frame组件,更换过不同背景色均无效。

相关代码

class ShowDatabaseFrame():
    """Creates a treeview to show database results in"""
    def __init__(self, master):
        self.master = master
        output_frame = self.master.notebook_list[get_active_tab_index(self.master)]["output_frame"]
        self.output_table_name = [None, None]    #Name of the shown table [old, new]

        #region Create widgets
        self.master_frame = tb.Frame(output_frame)
        self.output_help_frame = tb.Frame(self.master_frame)
        self.output_table = tb.Treeview(self.output_help_frame,
                                         columns=(0),
                                         show='headings',
                                         selectmode='extended')
        self.output_scroll_y = tb.Scrollbar(self.output_help_frame,
                                             orient="vertical",
                                             command=self.output_table.yview)
        self.output_scroll_x = tb.Scrollbar(self.master_frame,
                                             orient="horizontal",
                                             command=self.output_table.xview)
        #endregion

        #region Place widgets
        self.master_frame.pack(fill="both", expand=True)
        self.output_help_frame.pack(side="top", fill="both", expand=True)
        self.output_table.pack(fill="both", expand=True)
        self.output_scroll_y.pack(side="right", fill="y")
        self.output_scroll_x.pack(side="bottom", fill="x")
        #endregion

        #region Initialize treeview table
        self.output_table.heading(0, text="No table selected")
        self.output_table.insert("", "end", values=[""], iid="cond_row")
        self.output_table.configure(yscrollcommand=self.output_scroll_y.set,
                                    xscrollcommand=self.output_scroll_x.set)
        #endregion

        #Create key bindings
        self.output_table.bind("<Double-1>", self._on_double_click)
        self.output_table.bind("<Button-1>", self.on_click)
        self.output_table.bind("<B1-Motion>", self.on_drag)
        self.output_table.bind("<ButtonRelease-1>", self.on_release)

    def _on_double_click(self, event):
        #Get column and its index
        col_clicked = self.output_table.identify_column(event.x)
        col_index = int(col_clicked[1:])-1

        #Get ID
        selected_iid = self.output_table.focus()

        #Create entry if necessary
        if selected_iid == "cond_row":
            #Get selected values
            selected_values = self.output_table.item(selected_iid)

            #Get dimension and create ntry
            box_dim = self.output_table.bbox(selected_iid, col_clicked)
            self.edit_entry = tb.Entry(self.master_frame)

            #Write variables to the widget to use for later
            self.edit_entry.edit_col_index = col_index
            self.edit_entry.edit_item_iid = selected_iid

            #Bind events
            self.edit_entry.bind("<FocusOut>", self._on_focus_out)
            self.edit_entry.bind("<Return>", self._on_entry)

            #Setup of the entry
            self.edit_entry.place(x=box_dim[0], y=box_dim[1], w=box_dim[2], h=box_dim[3])
            self.edit_entry.insert(0, selected_values.get("values")[col_index])
            self.edit_entry.select_range(0, tk.END)
            self.edit_entry.focus()

    def on_click(self,event):
        """Gets starting position of the selection"""
        #Get ID, column and its index
        col = self.output_table.identify_column(event.x)
        row = self.output_table.identify_row(event.y)

        #Get dimension and create entry
        if self.output_table.identify_region(event.x, event.y) == "cell":
            self.dim_start = self.output_table.bbox(row, col)
            """
            self.outline_canvas = tk.Canvas(self.output_help_frame,
                                            width=self.output_table.winfo_width(),
                                            height=self.output_table.winfo_height(),
                                            bg="green")
            self.outline_canvas.grid_propagate()
            self.outline_canvas.place(x=0,
                                      y=0)#+self.output_table.winfo_rooty())
            self.outline_rectangle = self.outline_canvas.create_rectangle(
                self.dim_start[0],
                self.dim_start[1],
                self.dim_start[0]+self.dim_start[2],
                self.dim_start[1]+self.dim_start[3]
                )
            """
            self.outline = tk.Frame(self.output_table,
                                    width=self.dim_start[2],
                                    height=self.dim_start[3],
                                    bg="#00FF00")
            self.outline.grid_propagate()
            self.outline.place(x=self.dim_start[0],#+self.output_table.winfo_rootx(),
                               y=self.dim_start[1])#+self.output_table.winfo_rooty())

    def on_drag(self,event):
        """Adjusts the size of the selection"""
        #Get ID, column and its index
        col = self.output_table.identify_column(event.x)
        row = self.output_table.identify_row(event.y)

        #Get dimension and create entry
        if self.output_table.identify_region(event.x, event.y) == "cell":
            """
            dim_curr = self.output_table.bbox(row, col)
            self.outline_canvas.coords(self.outline_rectangle,
                                       self.dim_start[0],
                                       self.dim_start[1],
                                       dim_curr[0]+dim_curr[2]-self.dim_start[0],
                                       dim_curr[1]+dim_curr[3]-self.dim_start[1])
            """
            dim_curr = self.output_table.bbox(row, col)
            self.outline.config(width=dim_curr[2]+(dim_curr[0]-self.dim_start[0]),
                                height=dim_curr[3]+(dim_curr[1]-self.dim_start[1]))

    def on_release(self,event):
        """Sets the outline"""
        #Get ID, column and its index
        self.select_start_x = self.output_table.identify_column(event.x)
        self.select_start_y = self.output_table.identify_row(event.y)

    def _on_focus_out(self, event):
        event.widget.destroy()

    def _on_entry(self, event):
        #Get values
        new_text = event.widget.get()
        selected_iid = event.widget.edit_item_iid
        col_index = event.widget.edit_col_index

        #Take the values tuple, change the one value and use the new tuple in the table
        current_values = self.output_table.item(selected_iid).get("values")
        current_values[col_index] = new_text
        self.output_table.item(selected_iid, values=current_values)

        #destroy entry
        event.widget.destroy()

        #Update table
        self.master.fun_sql_query_maker()

原因分析

  • 组件层级与父容器冲突:将tk原生Frame/Canvas作为ttkbootstrap Treeview的子组件时,Treeview的ttk主题会强制控制内部子组件的渲染,导致原生组件的背景属性无法生效。
  • 透明属性使用错误:wm_attribute仅适用于顶层窗口,对Frame/Canvas这类子组件无效,子组件无法通过该方法设置透明。
  • 布局逻辑问题:原代码中计算拖拽后的组件尺寸时,未考虑拖拽方向(比如从右下往左上拖拽),会出现尺寸异常的情况。

解决方案

方案1:使用ttkbootstrap Canvas绘制透明轮廓(推荐)

直接在Treeview的同级容器创建Canvas,绘制仅带边框的矩形,利用Canvas的空填充实现透明背景,完全兼容ttkbootstrap主题:

def on_click(self,event):
    """Gets starting position of the selection"""
    col = self.output_table.identify_column(event.x)
    row = self.output_table.identify_row(event.y)
    if self.output_table.identify_region(event.x, event.y) == "cell":
        self.dim_start = self.output_table.bbox(row, col)
        # 创建ttkbootstrap Canvas,与Treeview同级
        self.outline_canvas = tb.Canvas(self.output_help_frame,
                                        width=self.output_table.winfo_width(),
                                        height=self.output_table.winfo_height(),
                                        highlightthickness=0)
        self.outline_canvas.place(x=0, y=0)
        # 绘制只有绿色边框的透明矩形
        self.outline_rectangle = self.outline_canvas.create_rectangle(
            self.dim_start[0],
            self.dim_start[1],
            self.dim_start[0]+self.dim_start[2],
            self.dim_start[1]+self.dim_start[3],
            outline="#00FF00", width=2, fill=''
        )

def on_drag(self,event):
    """Adjusts the size of the selection"""
    col = self.output_table.identify_column(event.x)
    row = self.output_table.identify_row(event.y)
    if self.output_table.identify_region(event.x, event.y) == "cell":
        dim_curr = self.output_table.bbox(row, col)
        # 计算正确的矩形对角坐标,适配所有拖拽方向
        x0 = min(self.dim_start[0], dim_curr[0])
        y0 = min(self.dim_start[1], dim_curr[1])
        x1 = max(self.dim_start[0]+self.dim_start[2], dim_curr[0]+dim_curr[2])
        y1 = max(self.dim_start[1]+self.dim_start[3], dim_curr[1]+dim_curr[3])
        self.outline_canvas.coords(self.outline_rectangle, x0, y0, x1, y1)

def on_release(self,event):
    """Sets the outline"""
    # 释放鼠标后销毁Canvas,避免遮挡交互
    if hasattr(self, 'outline_canvas'):
        self.outline_canvas.destroy()

方案2:修复tk原生Frame的背景问题

如果坚持使用Frame,需要将其父容器改为Treeview的同级容器,并使用Windows系统专属的透明背景值:

def on_click(self,event):
    """Gets starting position of the selection"""
    col = self.output_table.identify_column(event.x)
    row = self.output_table.identify_row(event.y)
    if self.output_table.identify_region(event.x, event.y) == "cell":
        self.dim_start = self.output_table.bbox(row, col)
        # 父容器改为output_help_frame,与Treeview同级
        self.outline = tk.Frame(self.output_help_frame,
                                width=self.dim_start[2],
                                height=self.dim_start[3],
                                highlightbackground="#00FF00",
                                highlightthickness=2)
        self.outline.place(x=self.dim_start[0], y=self.dim_start[1])
        # Windows系统下设置透明背景
        self.outline.config(bg='systemTransparent')

def on_drag(self,event):
    """Adjusts the size of the selection"""
    col = self.output_table.identify_column(event.x)
    row = self.output_table.identify_row(event.y)
    if self.output_table.identify_region(event.x, event.y) == "cell":
        dim_curr = self.output_table.bbox(row, col)
        # 计算正确的位置和尺寸
        x0 = min(self.dim_start[0], dim_curr[0])
        y0 = min(self.dim_start[1], dim_curr[1])
        width = abs(dim_curr[0] + dim_curr[2] - self.dim_start[0])
        height = abs(dim_curr[1] + dim_curr[3] - self.dim_start[1])
        self.outline.place(x=x0, y=y0, width=width, height=height)

def on_release(self,event):
    """Sets the outline"""
    if hasattr(self, 'outline'):
        self.outline.destroy()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 15:27:02