I created this test code to simulate a Tk window with a left panel that is resizable. How it works:
- When the mouse pointer move over the ttk.Separator, a resizable arrow indicator will appear where the mouse pointer is.
- Pressing the left mouse button and moving the mouse pointer, the width of the left panel will resize corresponding to the x position of the mouse pointer.
- The resizable arrow indicator should also move in sync with the mouse pointer.
I am able to perform steps 1 & 2. However, for step 3, I have an issue. The resizable arrow indicator in step 1 does not disappear while the resizable arrow indicator position in step 3 does follow the mouse pointer occasionally: there appears to be a competition btw these two steps.
How do I fix this issue?
Test code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.ttk as ttk
class App(ttk.Frame):
def __init__(self, master):
self.master = master
self.master.title('App')
self.master.geometry('1500x140')
self.mouse_pointer_x = tk.IntVar()
self.mouse_pointer_y = tk.IntVar()
super().__init__(master, style='App.TFrame', borderwidth=20)
self._set_style()
self._create_widgets()
self.bind('<Motion>', self._store_mouse_pointer_coordinate)
def _set_style(self):
self.style = ttk.Style()
self.style.configure('App.TFrame', background='pink')
self.style.configure('lframe.TFrame', background='green')
self.style.configure('rframe.TFrame', background='orange')
self.style.configure('TSeparator', background='red')
def _create_widgets(self):
self.lframe = ttk.Frame(self, style='lframe.TFrame', borderwidth=20)
self.rframe = ttk.Frame(self, style='rframe.TFrame', borderwidth=20)
self.divider = ttk.Separator(self, orient=tk.VERTICAL)
self.lframe.grid(row=0, column=0, sticky='nsew')
self.divider.grid(row=0, column=1, sticky='nsew', padx=20)
self.rframe.grid(row=0, column=2, sticky='nsew')
harrow = './resize-arrow-24.png'
self.icon_harrow = tk.PhotoImage(file=harrow)
self.harrow = ttk.Label(self, image=self.icon_harrow)
self.harrow.place(x=0, y=0)
self.harrow.place_forget()
self.ltv = self._create_treeview(self.lframe)
self.rtv = self._create_treeview(self.rframe)
self.ltv.grid(row=0, column=0, sticky='nsew')
self.rtv.grid(row=0, column=0, sticky='nsew')
self.divider.bind('<Enter>', self._show_divider)
self.divider.bind('<Leave>', self._hide_divider)
self.divider.bind("<B1-Motion>", self._button1_press_move)
def _create_treeview(self, parent):
# Create Treeview
SearchCols = ('#01', '#02', '#03', '#04', '#05', '#06')
tv = ttk.Treeview(parent, columns=SearchCols, height=2,
displaycolumn=['#05', '#06', '#01',
'#02', '#03', '#04'],
style='search.Treeview',
selectmode='extended', takefocus=True)
# Setup column & it's headings
tv.column('#0', stretch=0, minwidth=100, width=100, anchor='w')
tv.column('#01', stretch=0, anchor='n', width=70)
tv.column('#02', stretch=0, anchor='n', width=80)
tv.column('#03', stretch=0, anchor='n', width=75)
tv.column('#04', stretch=0, anchor='w')
tv.column('#05', stretch=0, anchor='e', width=80)
tv.column('#06', stretch=0, anchor='n', width=70)
tv.heading('#0', text=' Directory ', anchor='w')
tv.heading('#01', text='#01', anchor='center')
tv.heading('#02', text='#02', anchor='center')
tv.heading('#03', text='#03', anchor='center')
tv.heading('#04', text='#04', anchor='w')
tv.heading('#05', text='#05', anchor='center')
tv.heading('#06', text='#06', anchor='center')
# #0, #01, #02 denotes the 0, 1st, 2nd columns
return tv
# Event Handlers
def _store_mouse_pointer_coordinate(self, event):
self.mouse_pointer_x.set(event.x)
self.mouse_pointer_y.set(event.y)
print(self.mouse_pointer_x.get(), self.mouse_pointer_y.get())
def _show_divider(self, event):
x = self.mouse_pointer_x.get()
y = self.mouse_pointer_y.get()
self.harrow.place_configure(x=x-31, y=y-36)
self.harrow.lower(belowThis=self.divider)
def _hide_divider(self, event):
self.harrow.place_forget()
def _button1_press_move(self, event):
# Configure self.lframe
new_width = self.lframe.winfo_pointerx() - self.lframe['borderwidth']*2
self.lframe['width'] = new_width
print(f'self.lframe["width"]={self.lframe["width"]}')
self.lframe.grid_propagate(0)
# Configure self.harrow
new_height = event.y
self.harrow.place_forget()
self.harrow.place_configure(x=new_width 9, y=new_height-4)
self.harrow.lower(belowThis=self.divider)
self.update_idletasks()
if __name__ == '__main__':
root = tk.Tk()
root.resizable(width=False, height=False)
root.title('App')
root.geometry('1300x400 0 24')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
app = App(root)
app.grid(row=0, column=0, sticky='nsew')
root.mainloop()
CodePudding user response:
To resolve my resizable arrow indicator issue, I had to introduce event handlers to unbind and rebind events <Enter>
and <Leave>
when B1 is pressed and released on the ttk.Separator
widget. See revised test code below. See Revised test code.
An enhancement to this script is to transform the mouse pointer appearance into a resizable arrow indicator when it enters the widget ttk.Separator
as mentioned by @Atlas435 in the comment section of my question. This can be done by using the cursor option of the ttk.Separator
widget. This approach also eliminates needing to implement the solution mentioned above and makes the code more concise. See Improved revised test code
From hindsight, I released I had written a python class to create an alternative vertically oriented ttk.PanedWindow
widget using ttk.Frame
and ttk.Separator
widgets. @BryanOakley and @HenryYik thanks for pointing this fact to me.
Revised test code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.ttk as ttk
class App(ttk.Frame):
def __init__(self, master):
self.master = master
self.mouse_pointer_x = tk.IntVar()
self.mouse_pointer_y = tk.IntVar()
super().__init__(master, style='App.TFrame', borderwidth=20)
self._set_style()
self._create_widgets()
self.bind('<Motion>', self._store_mouse_pointer_coordinate)
def _set_style(self):
self.style = ttk.Style()
self.style.configure('App.TFrame', background='pink')
self.style.configure('lframe.TFrame', background='green')
self.style.configure('rframe.TFrame', background='orange')
self.style.configure('TSeparator', background='red')
def _create_widgets(self):
self.lframe = ttk.Frame(self, style='lframe.TFrame', borderwidth=20)
self.rframe = ttk.Frame(self, style='rframe.TFrame', borderwidth=20)
self.divider = ttk.Separator(self, orient=tk.VERTICAL)
self.lframe.grid(row=0, column=0, sticky='nsew')
self.divider.grid(row=0, column=1, sticky='nsew', padx=20)
self.rframe.grid(row=0, column=2, sticky='nsew')
harrow = './resize-arrow-24.png'
self.icon_harrow = tk.PhotoImage(file=harrow)
self.harrow = ttk.Label(self, image=self.icon_harrow)
self.harrow.place(x=0, y=0)
self.harrow.place_forget()
self.ltv = self._create_treeview(self.lframe)
self.rtv = self._create_treeview(self.rframe)
self.ltv.grid(row=0, column=0, sticky='nsew')
self.rtv.grid(row=0, column=0, sticky='nsew')
self.divider_bind_enter = self.divider.bind('<Enter>',
self._show_divider)
self.divider_bind_leave = self.divider.bind('<Leave>',
self._hide_divider)
self.divider.bind("<ButtonPress-1>", self._divider_B1_press)
self.divider.bind("<ButtonRelease-1>", self._divider_B1_release)
self.divider.bind("<B1-Motion>", self._divider_B1_press_move)
def _create_treeview(self, parent):
# Create Treeview
SearchCols = ('#01', '#02', '#03', '#04', '#05', '#06')
tv = ttk.Treeview(parent, columns=SearchCols, height=2,
displaycolumn=['#05', '#06', '#01',
'#02', '#03', '#04'],
style='search.Treeview',
selectmode='extended', takefocus=True)
# Setup column & it's headings
tv.column('#0', stretch=0, minwidth=100, width=100, anchor='w')
tv.column('#01', stretch=0, anchor='n', width=70)
tv.column('#02', stretch=0, anchor='n', width=80)
tv.column('#03', stretch=0, anchor='n', width=75)
tv.column('#04', stretch=0, anchor='w')
tv.column('#05', stretch=0, anchor='e', width=80)
tv.column('#06', stretch=0, anchor='n', width=70)
tv.heading('#0', text=' Directory ', anchor='w')
tv.heading('#01', text='#01', anchor='center')
tv.heading('#02', text='#02', anchor='center')
tv.heading('#03', text='#03', anchor='center')
tv.heading('#04', text='#04', anchor='w')
tv.heading('#05', text='#05', anchor='center')
tv.heading('#06', text='#06', anchor='center')
# #0, #01, #02 denotes the 0, 1st, 2nd columns
return tv
# Event Handlers
def _store_mouse_pointer_coordinate(self, event):
self.mouse_pointer_x.set(event.x)
self.mouse_pointer_y.set(event.y)
print(self.mouse_pointer_x.get(), self.mouse_pointer_y.get())
def _show_divider(self, event):
x = self.mouse_pointer_x.get()
y = self.mouse_pointer_y.get()
self.harrow.place_configure(x=x-31, y=y-33)
self.harrow.lower(belowThis=self.divider)
def _hide_divider(self, event):
self.harrow.place_forget()
def _divider_B1_press(self, event):
print('unbind self.divider <Enter> & <Leave> events')
self.divider.unbind('<Enter>', self.divider_bind_enter)
self.divider.unbind('<Leave>', self.divider_bind_leave)
def _divider_B1_release(self, event):
print('bind self.divider <Enter> & <Leave> events')
self.divider_bind_enter = self.divider.bind('<Enter>',
self._show_divider)
self.divider_bind_leave = self.divider.bind('<Leave>',
self._hide_divider)
def _divider_B1_press_move(self, event):
# Configure self.lframe
new_width = self.lframe.winfo_pointerx() - self.lframe['borderwidth']*2
self.lframe['width'] = new_width
print(f'self.lframe["width"]={self.lframe["width"]}')
self.lframe.grid_propagate(0)
# Configure self.harrow
new_height = event.y
self.harrow.place_forget()
self.harrow.place_configure(x=new_width 9, y=new_height-6)
self.harrow.lower(belowThis=self.divider)
self.update_idletasks()
if __name__ == '__main__':
root = tk.Tk()
#root.resizable(width=False, height=False)
root.title('App')
root.geometry('1500x140 0 24')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
app = App(root)
app.grid(row=0, column=0, sticky='nsew')
root.mainloop()
Improved revised test code (a VerticalPanedWindow widget):
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.ttk as ttk
class VerticalPanedWindow(ttk.Frame):
'''A ttk styled Vertical PanedWindow.'''
def __init__(self, master, bg='pink', borderwidth=0,
divider_fg='red', divider_padx=1,
lframe_bg='green', lframe_borderwidth=0,
rframe_bg='orange', rframe_borderwidth=0, ):
self.master = master
self.bg = bg
self.borderwidth = borderwidth
self.divider_fg = divider_fg
self.divider_padx = divider_padx
self.lframe_bg = lframe_bg
self.lframe_borderwidth = lframe_borderwidth
self.rframe_bg = rframe_bg
self.rframe_borderwidth = rframe_borderwidth
self.mouse_pointer_x = tk.IntVar()
self.mouse_pointer_y = tk.IntVar()
super().__init__(master, style='App.TFrame', borderwidth=borderwidth)
self._set_style()
self._create_widgets()
self.bind('<Motion>', self._store_mouse_pointer_coordinate)
def _set_style(self):
self.style = ttk.Style()
self.style.configure('App.TFrame', background=self.bg)
self.style.configure('lframe.TFrame', background=self.lframe_bg)
self.style.configure('rframe.TFrame', background=self.rframe_bg)
self.style.configure('TSeparator', background=self.divider_fg)
def _create_widgets(self):
self.lframe = ttk.Frame(self, style='lframe.TFrame',
borderwidth=self.lframe_borderwidth)
self.rframe = ttk.Frame(self, style='rframe.TFrame',
borderwidth=self.rframe_borderwidth)
self.divider = ttk.Separator(self, orient=tk.VERTICAL,
cursor='sb_h_double_arrow')
self.lframe.grid(row=0, column=0, sticky='nsew')
self.rframe.grid(row=0, column=2, sticky='nsew')
self.divider.grid(row=0, column=1, sticky='nsew',
padx=self.divider_padx)
self.divider.bind("<B1-Motion>", self._divider_B1_press_move)
# Event Handlers
def _store_mouse_pointer_coordinate(self, event):
self.mouse_pointer_x.set(event.x)
self.mouse_pointer_y.set(event.y)
print(self.mouse_pointer_x.get(), self.mouse_pointer_y.get())
def _divider_B1_press_move(self, event):
# Configure self.lframe
eventx = event.x
mpx = self.mouse_pointer_x.get()
new_x = mpx eventx
self.mouse_pointer_x.set(new_x)
self.lframe['width'] = new_x
self.lframe.grid_propagate(0)
print(f' {eventx} {mpx} {new_x} {self.lframe["width"]}')
def _create_treeview(parent):
# Create Treeview
SearchCols = ('#01', '#02', '#03', '#04', '#05', '#06')
tv = ttk.Treeview(parent, columns=SearchCols, height=2,
displaycolumn=['#05', '#06', '#01',
'#02', '#03', '#04'],
style='search.Treeview',
selectmode='extended', takefocus=True)
# Setup column & it's headings
tv.column('#0', stretch=0, minwidth=100, width=100, anchor='w')
tv.column('#01', stretch=0, anchor='n', width=70)
tv.column('#02', stretch=0, anchor='n', width=80)
tv.column('#03', stretch=0, anchor='n', width=75)
tv.column('#04', stretch=0, anchor='w')
tv.column('#05', stretch=0, anchor='e', width=80)
tv.column('#06', stretch=0, anchor='n', width=70)
tv.heading('#0', text=' Directory ', anchor='w')
tv.heading('#01', text='#01', anchor='center')
tv.heading('#02', text='#02', anchor='center')
tv.heading('#03', text='#03', anchor='center')
tv.heading('#04', text='#04', anchor='w')
tv.heading('#05', text='#05', anchor='center')
tv.heading('#06', text='#06', anchor='center')
# #0, #01, #02 denotes the 0, 1st, 2nd columns
return tv
if __name__ == '__main__':
root = tk.Tk()
# root.resizable(width=False, height=False)
root.title('VerticalPanedWindow')
root.geometry('1500x140')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
# Use customs colors and borderwidth values
# color = '#240240237'
# app = VerticalPanedWindow(
# root, bg=color, borderwidth=20,
# divider_fg=color, divider_padx=20,
# lframe_bg=color, lframe_borderwidth=20,
# rframe_bg=color, rframe_borderwidth=20, )
# Use customs borderwidth values
app = VerticalPanedWindow(root, borderwidth=20, divider_padx=20,
lframe_borderwidth=20, rframe_borderwidth=20)
# Use default options value
# app = VerticalPanedWindow(root)
app.grid(row=0, column=0, sticky='nsew')
ltv = _create_treeview(app.lframe)
rtv = _create_treeview(app.rframe)
ltv.grid(row=0, column=0, sticky='nsew')
rtv.grid(row=0, column=0, sticky='nsew')
root.mainloop()