# Author: Miguel Martinez Lopez # Version: 0.8 try: import Tkinter as tk import ttk import tkFont from Tkconstants import * except ImportError: import tkinter as tk import tkinter.ttk as ttk from tkinter import font as tkFont from tkinter.constants import * # Python 2 and 3 compatibility try: xrange except NameError: xrange = range # Page label states NOT_SELECTED = 0 SELECTED = 1 NORMAL_STATE = 0 ACTIVE_STATE = 1 def config_style(label, style, is_selected, is_active): label.config(**style[is_selected, is_active]) class Page_Label(tk.Label, object): def __init__(self, master, page_number, style, on_click, is_selected, is_active, is_displayed=True): tk.Label.__init__(self, master, width=0, text=page_number) if is_selected: current_style = style[SELECTED, NORMAL_STATE] else: current_style = style[NOT_SELECTED, NORMAL_STATE] self.config(**current_style) self._style = style self.bind("<1>", lambda event: on_click(self)) self.bind("", self._on_enter) self.bind("", self._on_leave) self._page_number = page_number self.is_selected = is_selected self.is_active = is_active self.is_displayed = is_displayed @property def page_number(self): return self._page_number @page_number.setter def page_number(self, number): self._page_number = number self.config(text=str(number)) def _on_enter(self, event): if self.is_selected: self.change_state(SELECTED, ACTIVE_STATE) else: self.change_state(NOT_SELECTED, ACTIVE_STATE) def _on_leave(self, event): if self.is_selected: self.change_state(SELECTED, NORMAL_STATE) else: self.change_state(NOT_SELECTED, NORMAL_STATE) def change_state(self, is_selected, is_active): self.is_selected = is_selected self.is_active = is_active config_style(self, self._style, is_selected, is_active) class Page_Control(tk.Frame): def _navigation_control(self, pagination, style, control_name, text): onclick_control = getattr(pagination, "%s_page"%control_name) label = tk.Label(self, text=text, width=0) config_style(label, style, NOT_SELECTED, NORMAL_STATE) label.bind("<1>", lambda event: onclick_control()) label.bind("", lambda event, label=label: config_style(label, style, NOT_SELECTED, ACTIVE_STATE)) label.bind("", lambda event, label=label: config_style(label, style, NOT_SELECTED, NORMAL_STATE)) return label class Left_Control(Page_Control): def __init__(self, pagination, style, first_button, prev_button, spacing): Page_Control.__init__(self, pagination) if first_button is None: if prev_button is not None: self._previous_label = self._navigation_control(pagination, style, "prev", prev_button) self._previous_label.pack(side=LEFT) else: self._first_label = self._navigation_control(pagination, style, "first", first_button) self._first_label.pack(side=LEFT) if prev_button is not None: self._previous_label = self._navigation_control(pagination, style, "prev", prev_button) self._previous_label.pack(side=LEFT, padx=(spacing,0)) class Right_Control(Page_Control): def __init__(self, pagination, style, last_button, next_button, spacing): Page_Control.__init__(self, pagination) if last_button is None: if prev_button is not None: self._next_label = self._navigation_control(pagination, style, "next", next_button) self._next_label.pack(side=RIGHT) else: self._last_label = self._navigation_control(pagination, style, "last", last_button) self._last_label.pack(side=RIGHT) if next_button is not None: self._next_label = self._navigation_control(pagination, style, "next", next_button) self._next_label.pack(side=RIGHT, padx=(0, spacing)) class Pagination(tk.Frame): def __init__(self, master, displayed_pages, total_pages, background=None, current_page=None, start_page=1, prev_button= "Prev", next_button="Next", first_button="First", last_button="Last", hide_controls_at_edge=False, command =None, pagination_style=None): if pagination_style is None: raise Exception("No pagination style defined") self._start_page = start_page self._end_page = min(total_pages, start_page + displayed_pages - 1) if current_page is None: current_page = start_page else: if not self._start_page <= current_page <= self._end_page: raise ValueError("Not valid selected page") tk.Frame.__init__(self, master, background=background) self._command = command self._hide_controls_at_edge = hide_controls_at_edge self._list_of_page_labels = [] self._total_pages = total_pages self._displayed_pages = displayed_pages self._left_controls = None self._right_controls = None self._current_page = current_page self._label_spacing = pagination_style.get("button_spacing", 0) self._style_config = style_config = {} for selected_state in (NOT_SELECTED, SELECTED): for active_state in (NORMAL_STATE, ACTIVE_STATE): style_config[selected_state, active_state] = self._create_configuration_of_state(pagination_style, selected_state, active_state) self._render_pagination(current_page, prev_button, next_button, first_button, last_button, displayed_pages, self._start_page, self._end_page, self._label_spacing, hide_controls_at_edge) def _render_pagination(self, current_page, prev_button, next_button, first_button, last_button, displayed_pages, start_page, end_page, spacing, hide_controls_at_edge): if prev_button is not None or first_button is not None: self._left_controls = Left_Control(self, self._style_config, first_button, prev_button, spacing) if hide_controls_at_edge and start_page == 1: self._left_controls.is_displayed = False else: self._left_controls.pack(side=LEFT, padx=(0, self._label_spacing)) self._left_controls.is_displayed = True self._page_frame = tk.Frame(self) self._page_frame.pack(side=LEFT) for page_number in range(start_page, start_page + displayed_pages): is_selected = page_number == self._current_page if page_number <= end_page: page_label = Page_Label(self._page_frame, page_number, self._style_config, self._on_click_page, is_selected, False, is_displayed=True) if page_number == start_page: page_label.pack(side=LEFT) else: page_label.pack(side=LEFT, padx=(self._label_spacing, 0)) else: page_label = Page_Label(self._page_frame, page_number, self._style_config, self._on_click_page, is_selected, False, is_displayed=False) self._list_of_page_labels.append(page_label) if next_button is not None or last_button is not None: self._right_controls = Right_Control(self, self._style_config, last_button, next_button, spacing) if hide_controls_at_edge and end_page == total_pages: self._right_controls.is_displayed = False else: self._right_controls.pack(side=LEFT, padx=(self._label_spacing, 0)) self._right_controls.is_displayed = True def _update_labels(self): if self._hide_controls_at_edge: if self._left_controls is not None: if self._start_page == 1: if self._left_controls.is_displayed: self._left_controls.is_displayed = False self._left_controls.pack_forget() else: if not self._left_controls.is_displayed: self._left_controls.is_displayed = True self._left_controls.pack(side=LEFT, padx=(0, self._label_spacing), before=self._page_frame) if self._right_controls is not None: if self._end_page == self._total_pages: if self._right_controls.is_displayed: self._right_controls.is_displayed = False self._right_controls.pack_forget() else: if not self._right_controls.is_displayed: self._right_controls.is_displayed = True self._right_controls.pack(side=LEFT, padx=(self._label_spacing, 0), after=self._page_frame) for i, page_number in enumerate(range(self._start_page, self._end_page+1)): page_label = self._list_of_page_labels[i] page_label.page_number = page_number if self._current_page == page_number: if not page_label.is_selected: page_label.change_state(SELECTED, NORMAL_STATE) else: if page_label.is_selected: page_label.change_state(NOT_SELECTED, NORMAL_STATE) if not page_label.is_displayed: page_label.is_displayed = True if i == 0: page_label.pack(side=LEFT) else: page_label.pack_configure(side=LEFT, padx=(self._label_spacing, 0)) for i in range(self._end_page-self._start_page+1, self._displayed_pages): page_label = self._list_of_page_labels[i] if page_label.is_displayed: page_label.pack_forget() page_label.is_displayed = False def _create_configuration_of_state(self, pagination_style, selected_state, active_state): config = {} if "font" in pagination_style: config["font"] = pagination_style["font"] if "font_family" in pagination_style: font_family = pagination_style["font_family"] else: font_family = None if "font_size" in pagination_style: font_size = pagination_style["font_size"] else: font_size = None if "font_weight" in pagination_style: font_weight = pagination_style["font_weight"] else: font_weight = None if "button_padx" in pagination_style: config["padx"] = pagination_style["button_padx"] if "button_pady" in pagination_style: config["pady"] = pagination_style["button_pady"] if selected_state == SELECTED: state_style = pagination_style["selected_button"] else: state_style = pagination_style["normal_button"] if active_state == ACTIVE_STATE: if "activebackground" in state_style: config["background"] = state_style["activebackground"] if "activeforeground" in state_style: config["foreground"] = state_style["activeforeground"] else: if "background" in state_style: config["background"] = state_style["background"] if "foreground" in state_style: config["foreground"] = state_style["foreground"] if "padx" in state_style: config["padx"] = state_style["padx"] if "pady" in state_style: config["pady"] = state_style["pady"] if "font" in state_style: config["font"] = state_style["font"] else: font_family = state_style.get("font_family", font_family) font_size = state_style.get("font_size", font_size) font_weight = state_style.get("font_weight", font_weight) kw = {} if font_family is not None: kw["family"] = font_family if font_size is not None: kw["size"] = font_size if font_weight is not None: kw["weight"] = font_weight config["font"] = tkFont.Font(**kw) return config def select_page(self, page_number, start_page=None): self._current_page = page_number if start_page is None: if page_number < self._start_page: self._start_page = page_number self._end_page = self._start_page + self._displayed_pages - 1 else: end_page = start_page + self._displayed_pages - 1 if not start_page <= page_number <= end_page: raise ValueError("Page number not visible") self._start_page = start_page self._end_page = end_page self._update_labels() def prev_page(self): if self._current_page == 1: return if self._current_page == self._start_page: self._start_page -= 1 self._end_page -= 1 self._current_page -= 1 if self._command is not None: self._command(self._current_page) self._update_labels() def next_page(self): if self._current_page == self._total_pages: return if self._current_page == self._end_page: self._start_page += 1 self._end_page += 1 self._current_page += 1 if self._command is not None: self._command(self._current_page) self._update_labels() def first_page(self): if self._current_page == 1: return self._start_page = 1 self._end_page = min(self._total_pages, self._displayed_pages) self._current_page = 1 if self._command is not None: self._command(self._current_page) self._update_labels() def last_page(self): if self._current_page == self._total_pages: return self._end_page = self._total_pages self._start_page = max(self._end_page- self._displayed_pages + 1, 1) self._current_page = self._total_pages if self._command is not None: self._command(self._current_page) self._update_labels() @property def current_page(self): return self._current_page page = current_page @property def total_pages(self): return self._total_pages @total_pages.setter def total_pages(self, num): self._total_pages = num _end_page = self._end_page self._start_page = max(min(self._total_pages - self._displayed_pages+1, self._start_page), 1) self._end_page = min(self._start_page + self._displayed_pages -1 , self._total_pages) if _end_page == self._end_page: return elif _end_page < self._end_page: for page_number in range(_end_page+1, self._end_page +1): i = page_number - self._start_page page_label = self._list_of_page_labels[i] page_label.page_number = page_number if i == 0: page_label.pack(side=LEFT) else: page_label.pack_configure(side=LEFT, padx=(self._label_spacing, 0)) page_label.is_displayed = True else: for i in range(self._end_page - _end_page): page_label = self._list_of_page_labels[i] page_label.pack_forget() page_label.is_displayed = False def _on_click_page(self, new_page): if new_page.page_number == self._current_page: return old_page = self._list_of_page_labels[self._current_page - self._start_page] old_page.change_state(NOT_SELECTED, NORMAL_STATE) new_page.change_state(SELECTED, ACTIVE_STATE) self._current_page = new_page.page_number if self._command is not None: self._command(self._current_page) def update(self, total_pages, current_page, start_page=1): end_page = min(total_pages, start_page + self._displayed_pages - 1) if not start_page <= current_page <= end_page: raise ValueError("Not valid selected page") self._start_page = start_page self._end_page = end_page self._total_pages = total_pages self._current_page = current_page self._update_labels() pagination_style1 = { "button_spacing": 3, "button_padx":12, "button_pady": 6, "normal_button": { "font": ("Verdana", 10), "foreground": "#337ab7", "activeforeground":"#23527c", "background": "white", "activebackground": "#eee" }, "selected_button": { "font":("Verdana", 10, "bold"), "foreground":"#fff", "activeforeground":"#fff", "background":"#337ab7", "activebackground":"#337ab7" } } pagination_style2 = { "button_spacing": 3, "button_padx":12, "button_pady":6, "normal_button": { "font": ("Verdana", 10), "foreground": "black", "activeforeground":"black", "background": "white", "activebackground": "#ccc" }, "selected_button": { "font":("Verdana", 10, "bold"), "foreground":"white", "activeforeground":"#fff", "background":"#f44336", "activebackground":"#f44336" } } pagination_style3 = { "button_spacing": 3, "button_padx":12, "button_pady":6, "normal_button": { "font": ("Verdana", 10), "foreground": "#717171", "activeforeground":"#717171", "background": "#e9e9e9", "activebackground": "#fefefe" }, "selected_button": { "font":("Verdana", 10, "bold"), "foreground":"#f0f0f0", "activeforeground":"#f0f0f0", "background":"#616161", "activebackground":"#616161" } } pagination_style4 = { "button_spacing": 3, "button_padx":12, "button_pady":6, "normal_button": { "font": ("Verdana", 10), "foreground": "#feffff", "activeforeground":"#feffff", "background": "#3e4347", "activebackground": "#3d4f5d" }, "selected_button": { "font":("Verdana", 10, "bold"), "foreground":"#feffff", "activeforeground":"#feffff", "background":"#2f3237", "activebackground":"#2f3237" } } pagination_style5 = { "button_spacing": 3, "button_padx":12, "button_pady":6, "normal_button": { "font": ("Verdana", 10), "foreground": "#2E4057", "activeforeground":"#2E4057", "background": "white", "activebackground": "white" }, "selected_button": { "font":("Verdana", 10, "bold"), "foreground":"white", "activeforeground":"white", "background":"#64a281", "activebackground":"#64a281" } } if __name__ == "__main__": try: from Tkinter import Tk from tkMessageBox import showinfo except ImportError: from tkinter import Tk from tkinter.messagebox import showinfo root = Tk() page = tk.Frame() page.pack() def print_page(page_number): print("page number %s"%page_number) row = tk.Frame(page) row.pack(padx=10, pady=4, fill=X) tk.Label(row, text="Pagination").pack(anchor=W) pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style1) pagination.pack(pady=10, anchor=W) pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style2) pagination.pack(pady=10, anchor=W) pagination = Pagination(row, 5, 50, command=print_page, pagination_style=pagination_style3) pagination.pack(pady=10, anchor=W) pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style4) pagination.pack(pady=10, anchor=W) pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style5) pagination.pack(pady=10, anchor=W) root.mainloop()