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
相关产品推荐
相关产品推荐

