def test_scrollable_frame(): root = tk.Tk() # the row expands root.grid_rowconfigure(0, weight=1) # the first column expands root.grid_columnconfigure(0, weight=1) # the second column is fixed (set only for clarity) root.grid_columnconfigure(1, weight=0) # create the ScrollableFrame, with a set width scroll_width = 200 scroll = ScrollableFrame(root, scroll_width=scroll_width) scroll.grid(row=0, column=1, sticky="ns") # the main windows is here, this does expand 'ew' filler = tk.Frame(root, bg="blue", width=100) filler.grid(row=0, column=0, sticky="nsew") labels = [] for i in range(30): new_label = tk.Label( scroll.scroll_frame, text=f"Label created at row {i}", background="orange", ) new_label.grid(row=i, column=0, sticky="ew") # add bindings for mouse scrolling new_label.bind("<4>", scroll.on_list_scroll) new_label.bind("<5>", scroll.on_list_scroll) new_label.bind("<MouseWheel>", scroll.on_list_scroll) labels.append(new_label) # if the labels do not cover the entire canvas, some of these might work # scroll.bind("<4>", scroll.on_list_scroll) # scroll.bind("<5>", scroll.on_list_scroll) # scroll.bind("<MouseWheel>", scroll.on_list_scroll) # scroll.scroll_canvas.bind("<4>", scroll.on_list_scroll) # scroll.scroll_canvas.bind("<5>", scroll.on_list_scroll) # scroll.scroll_canvas.bind("<MouseWheel>", scroll.on_list_scroll) # scroll.scroll_frame.bind("<4>", scroll.on_list_scroll) # scroll.scroll_frame.bind("<5>", scroll.on_list_scroll) # scroll.scroll_frame.bind("<MouseWheel>", scroll.on_list_scroll) root.mainloop()
def __init__(self, optimizer, parent, controller): tk.Frame.__init__(self, parent) #prepares player dataframe used throughout frame self.player_master = optimizer.player_master.filter( ['position', 'name', 'team', 'salary', 'ppg_projection', 'value']) #declares scrollable frame for excl and lock buttons #borrows from class found online player_frame = ScrollableFrame(self, 420, 400) player_frame.grid(column=0, row=1, padx=10, pady=(0, 10), rowspan=3) #adds excl and lock labels tk.Label(self, text='Excl | Lock').grid(column=0, row=0, sticky='sw', padx=4, pady=(10, 0)) #creates and places lock and exclusion buttons self.excl_buttons = {} self.lock_buttons = {} self.excl_vars = {} self.lock_vars = {} for i, player in enumerate(self.player_master.index): #excl buttons self.excl_vars[player] = tk.IntVar() self.excl_vars[player].set(0) self.excl_buttons[player] = ttk.Checkbutton( player_frame.scrollable_frame, variable=self.excl_vars[player], command=lambda x=player: self.toggle_excl(x)) self.excl_buttons[player].grid(row=i, column=0) #lock buttons self.lock_vars[player] = tk.IntVar() self.lock_vars[player].set(0) self.lock_buttons[player] = ttk.Checkbutton( player_frame.scrollable_frame, variable=self.lock_vars[player], command=lambda x=player: self.toggle_lock(x)) self.lock_buttons[player].grid(row=i, column=1) #adds labels to scrollframe with player info player_labels = {} self.player_text = self.player_master.to_string( header=False, index=False, justify='right').splitlines() for i in range(len(self.player_master.index)): player_labels[i] = tk.Label(player_frame.scrollable_frame, text=self.player_text[i], anchor='w', font=('Consolas', 11), bg='white') player_labels[i].grid(column=3, row=i, sticky='w') #adds text box for exluded players tk.Label(self, text="Excluded:", font=('Calibri', 11)).grid(row=0, column=1, sticky='sw') self.excl_text = tk.Text(self, height=10, width=50, state=tk.DISABLED) self.excl_text.grid(row=1, column=1, sticky='ns', padx=(0, 10)) e_scroll = ttk.Scrollbar(self, command=self.excl_text.yview) e_scroll.grid(row=1, column=1, sticky='nse', padx=(0, 10)) self.excl_text['yscrollcommand'] = e_scroll.set #adds text box for locked players tk.Label(self, text="Locked:", font=('Calibri', 11)).grid(row=2, column=1, sticky='sw') self.lock_text = tk.Text(self, height=10, width=50, state=tk.DISABLED) self.lock_text.grid(row=3, column=1, sticky='ns', pady=(0, 10), padx=(0, 10)) l_scroll = ttk.Scrollbar(self, command=self.lock_text.yview) l_scroll.grid(row=3, column=1, sticky='nse', pady=(0, 10), padx=(0, 10)) self.lock_text['yscrollcommand'] = l_scroll.set #dataframes to be converted to string in toggle functions self.excl_df = pd.DataFrame() self.lock_df = pd.DataFrame()
class SelectionListFrame(ThumbButtonList): def __init__(self, parent, name, palette, sidebar_width, *args, **kwargs): """Do things in build_selection_list_frame""" logg = logging.getLogger(f"c.{__class__.__name__}.init") logg.info("Start init") self.name = name self.palette = palette self.sidebar_width = sidebar_width # frame background color self.back_col = self.palette.get_colors(f"background.{self.name}") # header background color self.back_col_header = ( self.palette.get_colors("background.header.selection_list"), ) # ScrollableFrame colors self.back_col_scrollable = self.palette.get_colors( "background.scrollable.selection_list") self.hover_col_scrollable = self.palette.get_colors( "hover.scrollable.selection_list") self.slider_col_scrollable = self.palette.get_colors( "slider.scrollable.selection_list") # ThumbButton colors self.back_col_thumbbtn = self.palette.get_colors( "background.thumbbtn.selection_list") self.hover_back_col_thumbbtn = self.palette.get_colors( "hover.thumbbtn.selection_list") self.back_col_bis_thumbbtn = self.palette.get_colors( "backgroundbis.thumbbtn.selection_list") super().__init__(parent, background=self.back_col, *args, **kwargs) self.selection_list_frame_header = tk.Label( self, text="Selection list:", background=self.back_col_header) # ScrollableFrame that holds the ThumbButtons self.selection_list_scrollable = ScrollableFrame( self, scroll_width=self.sidebar_width, back_col=self.back_col_scrollable, hover_back_col=self.hover_col_scrollable, slider_col=self.slider_col_scrollable, ) # setup grid in selection_list_frame self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) # grid static elements self.selection_list_frame_header.grid(row=0, column=0, sticky="ew") self.selection_list_scrollable.grid(row=1, column=0, sticky="nsew") # dicts for runtime elements self.selection_list_thumbbtn = {} def update_selection_list(self, selection_list_info): """Receives a dict of PhotoInfo object and creates ThumbButton There is also info on whether the pic is still selected selection_list_info = { pic : (PhotoInfo, is_selected) } """ logg = logging.getLogger( f"c.{__class__.__name__}.update_selection_list") # logg.setLevel("TRACE") logg.info("Updating selection_list ThumbButtons") for pic in self.selection_list_thumbbtn: self.selection_list_thumbbtn[pic].grid_forget() for ri, pic in enumerate(selection_list_info): # extract values photo_info = selection_list_info[pic][0] is_selected = selection_list_info[pic][1] # create the new ThumbButton if pic not in self.selection_list_thumbbtn: self.selection_list_thumbbtn[pic] = ThumbButton( self.selection_list_scrollable.scroll_frame, photo_info, back_col=self.back_col_thumbbtn, hover_back_col=self.hover_back_col_thumbbtn, back_col_bis=self.back_col_bis_thumbbtn, ) # bind enter/leave event to highlight self.selection_list_thumbbtn[pic].bind("<Enter>", self.on_thumbbtn_enter) self.selection_list_thumbbtn[pic].bind("<Leave>", self.on_thumbbtn_leave) # bind scroll function to ThumbButton elements self.selection_list_thumbbtn[pic].register_scroll_func( self.selection_list_scrollable.on_list_scroll) # add event for doubleclick self.selection_list_thumbbtn[pic].bind_doubleclick( self.on_selection_list_doubleclick) # set selected photo to FIRST, de-selected to BIS color_mode if is_selected: self.selection_list_thumbbtn[pic].set_back_col_mode("FIRST") else: self.selection_list_thumbbtn[pic].set_back_col_mode("BIS") self.selection_list_thumbbtn[pic].grid(row=ri, column=0, sticky="ew") def on_selection_list_doubleclick(self, event): logg = logging.getLogger( f"c.{__class__.__name__}.on_selection_list_doubleclick") # logg.setLevel("TRACE") logg.debug("Doublecliked something in selection list") logg.trace( f"Event {event} fired by {event.widget} master {event.widget.master}" ) self.selection_doubleclicked = event.widget.master.photo_info.photo_name_full logg.trace(f"selection_doubleclicked {self.selection_doubleclicked}") self.event_generate("<<thumbbtn_selection_doubleclick>>")
class PhotoListFrame(ThumbButtonList): def __init__(self, parent, name, palette, sidebar_width, *args, **kwargs): """Do things in build_photo_list_frame""" logg = logging.getLogger(f"c.{__class__.__name__}.init") logg.info("Start init") self.name = name self.palette = palette self.back_col = self.palette.get_colors(f"background.{self.name}") self.back_col_header = ( self.palette.get_colors("background.header.photo_list"), ) self.back_col_scrollable = self.palette.get_colors( "background.scrollable.photo_list") self.hover_col_scrollable = self.palette.get_colors( "hover.scrollable.photo_list") self.slider_col_scrollable = self.palette.get_colors( "slider.scrollable.photo_list") self.back_col_thumbbtn = self.palette.get_colors( "background.thumbbtn.photo_list") self.hover_back_col_thumbbtn = self.palette.get_colors( "hover.thumbbtn.photo_list") self.back_col_bis_thumbbtn = self.palette.get_colors( "backgroundbis.thumbbtn.photo_list") super().__init__(parent, background=self.back_col, *args, **kwargs) # save width of sidebar, needed to explicitly set ScrollableFrame self.sidebar_width = sidebar_width self.photo_list_frame_header = tk.Label( self, text="Photo list:", background=self.back_col_header) # TODO reimplement the whole thing: manually change the TB you show # ask for new PhotoInfo when needed, so that they do not have to be all # loaded; bind configure of photo_list_frame to compute how many TB # have to be available; bind scroll (from the Scrollbar as well?) to a # call in the model to update the list of TB, and show just them # this is mainly needed because this frame can only hold a puny 1000ish # photo list; that's like one week of vacation. # ScrollableFrame that holds the ThumbButtons self.photo_list_scrollable = ScrollableFrame( self, scroll_width=self.sidebar_width, back_col=self.back_col_scrollable, hover_back_col=self.hover_col_scrollable, slider_col=self.slider_col_scrollable, ) # setup grid in photo_list_frame (this widget) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) # grid static elements self.photo_list_frame_header.grid(row=0, column=0, sticky="ew") self.photo_list_scrollable.grid(row=1, column=0, sticky="nsew") # dicts for runtime elements self.photo_list_thumbbtn = {} self.current_photo_prim = "" def update_photo_list(self, photo_list_info): """Receives a dict of PhotoInfo object and creates ThumbButton photo_list_info = { pic : PhotoInfo } """ logg = logging.getLogger(f"c.{__class__.__name__}.update_photo_list") # logg.setLevel("TRACE") logg.info("Updating photo_list ThumbButton") for pic in self.photo_list_thumbbtn: self.photo_list_thumbbtn[pic].grid_forget() for ri, pic in enumerate(photo_list_info): # create the new ThumbButton if pic not in self.photo_list_thumbbtn: self.photo_list_thumbbtn[pic] = ThumbButton( self.photo_list_scrollable.scroll_frame, photo_list_info[pic], back_col=self.back_col_thumbbtn, hover_back_col=self.hover_back_col_thumbbtn, back_col_bis=self.back_col_bis_thumbbtn, ) # bind enter/leave event to highlight self.photo_list_thumbbtn[pic].bind("<Enter>", self.on_thumbbtn_enter) self.photo_list_thumbbtn[pic].bind("<Leave>", self.on_thumbbtn_leave) # bind scroll function to ThumbButton elements self.photo_list_thumbbtn[pic].register_scroll_func( self.photo_list_scrollable.on_list_scroll) # add event for doubleclick self.photo_list_thumbbtn[pic].bind_doubleclick( self.on_photo_list_doubleclick) # highlight current photo primary if pic == self.current_photo_prim: logg.trace(f"Setting color mode BIS for '{pic}' ThumbButton") self.photo_list_thumbbtn[pic].set_back_col_mode("BIS") else: self.photo_list_thumbbtn[pic].set_back_col_mode("FIRST") # grid the ThumbButton self.photo_list_thumbbtn[pic].grid(row=ri, column=0, sticky="ew") logg.trace(f"Loaded thbtn {self.photo_list_thumbbtn.keys()}") def on_photo_list_doubleclick(self, event): """Handle double clicks on photo list, go to that pic MAYBE better binding of this event in ThumbButton, bind the double click to some virtual events raised, then bind the virtual event on the ThumbButton top widget, so that the callback can access event.widget directly on the ThumbButton, instead of event.widget.master """ logg = logging.getLogger( f"c.{__class__.__name__}.on_photo_list_doubleclick") # logg.setLevel("TRACE") logg.debug("Doublecliked something") logg.trace( f"Event {event} fired by {event.widget} master {event.widget.master}" ) self.photo_doubleclicked = event.widget.master.photo_info.photo_name_full logg.trace(f"photo_doubleclicked {self.photo_doubleclicked}") self.event_generate("<<thumbbtn_photo_doubleclick>>") def update_current_photo_prim(self, pic): logg = logging.getLogger( f"c.{__class__.__name__}.update_current_photo_prim") # logg.setLevel("TRACE") logg.trace("Update current_photo_prim") if self.current_photo_prim != "": self.photo_list_thumbbtn[ self.current_photo_prim].set_back_col_mode("FIRST") self.photo_list_thumbbtn[pic].set_back_col_mode("BIS") self.current_photo_prim = pic
class Program(tk.Frame): def __init__(self, master, on_run, camera_direction, **kwargs): tk.Frame.__init__(self, master, **kwargs) self.on_run = on_run self.camera_direction = camera_direction self.cycles = [] self.create_widgets() def create_widgets(self): self.cycles_frame = ScrollableFrame(self, background=BG) self.cycles_list = tk.Frame( self.cycles_frame.scrollable_frame, background=BG) self.cycles_list.grid(row=0, column=0, sticky=tk.E+tk.W) self.cycles_list.columnconfigure(0, weight=1) cycle = Cycle(self.cycles_list, borderwidth=1, relief='raised') self.cycles.append(cycle) cycle.grid(row=0, column=0, pady=(0, 16), sticky=tk.E+tk.W) cycles_buttons = tk.Frame( self.cycles_frame.scrollable_frame, background=BG) cycles_buttons.grid(row=1, column=0, pady=(16, 24)) tk.Button( cycles_buttons, text='+ Step', highlightbackground=BG, command=self.add_step, ).grid(row=0, column=0) self.run_button = tk.Button( cycles_buttons, text='Run Program', highlightbackground=BG, font=tkFont.Font(weight='bold'), command=self.run_program) self.run_button.grid(row=0, column=1, ipady=6) self.cycles_frame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W) self.cycles_frame.scrollable_frame.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) def add_step(self): cycle_index = len(self.cycles) cycle = Cycle( self.cycles_list, cycle_index, borderwidth=1, relief='raised') cycle.grid(row=cycle_index, column=0, pady=(0, 16), sticky=tk.E+tk.W) rm = tk.Label( cycle, text='✕', foreground='#833', width=2, justify=tk.CENTER) rm.place(relx=1, anchor=tk.NE) rm.bind('<Button-1>', lambda _: self.remove_step(cycle)) self.cycles.append(cycle) def remove_step(self, cycle): self.cycles = [a for a in self.cycles if a is not cycle] cycle.destroy() for i, cycle in enumerate(self.cycles): cycle.grid(row=i, column=0, sticky=tk.E+tk.W) def run_program(self): program = [] for cycle in self.cycles: for _ in range(int(cycle.cycles_count.get())): for a in cycle.actions: n = int(a.action.frames.get()) if a.action.name == 'Camera': if self.camera_direction.get() == 'rw': n *= -1 program.append((n, 0)) else: if a.action.direction.get() == 'rw': n *= -1 program.append((0, n)) self.on_run(program) def disable(self): self.run_button.config(state=tk.DISABLED, text='Program running...') def enable(self): self.run_button.config(state=tk.NORMAL, text='Run program')