# Author: Miguel Martinez Lopez # # Please let me know if you find a bug # Uncomment the next line to see my email # print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex")) """Tkinter Selectors Select widgets using selectors synchronously or asynchronously. This is the provided API: toplevel_titles(widget) window_titles(widget) full_name_of_widgets(root) find_toplevel_by_title(widget, title) find_widgets_by_name(root, name) find_widget_by_name(root, name) find_widgets_by_class(root, class_) find_widget_by_class(root, class_) find(root, selector, callback, async=True) find_all(root, selector, callback, async=True) config(root, selector, **kwargs) config_async(root, selector, **kwargs) config_all(root, selector, **kwargs) config_all_async(root, selector, **kwargs) tk_print(root, name=True, class_= True, indent="\t") """ # Python 2/3 compatibility import sys PY3 = sys.version_info[0] ==3 if PY3: basestring = str unicode = str class SubSelector(unicode): def __add__(self, txt): return self.__class__(unicode(self)+txt) class SubSelector_Name(SubSelector): """Subselector that match the widget name""" class SubSelector_Class(SubSelector): """Subselector that match the widget class""" class Tkinter_Selector(object): def __init__(self, selector_string): if len(selector_string) == 0: raise ValueError("selector can't be zero length") if selector_string[0] == ".": self._anchor_start = True last_char = "." selector_string = selector_string[1:] else: self._anchor_start = False if selector_string[0] == "*": last_char = "*" selector_string = selector_string[1:] else: last_char = None if selector_string[-1] == "*" or selector_string[-1] == ".": raise ValueError("'%s' is not allowed at the end"%selector_string[-1]) selector_obj = [] sequence_of_selector_elements = [] selector_element = None for position, char in enumerate(selector_string): if char == ".": if last_char == "." or last_char == "*": raise ValueError("Not allowed '%s' at position %d"%(last_char, position)) sequence_of_selector_elements.append(selector_element) selector_element = None elif char == "*": if last_char == "." or last_char == "*": raise ValueError("Not allowed '%s' at position %d"%(last_char, position)) sequence_of_selector_elements.append(selector_element) selector_element = None selector_obj.append(sequence_of_selector_elements) sequence_of_selector_elements = [] else: if selector_element is None: if char.isupper(): selector_element = SubSelector_Class(char) else: selector_element = SubSelector_Name(char) else: selector_element += char last_char = char if selector_element is not None: sequence_of_selector_elements.append(selector_element) if len(sequence_of_selector_elements) != 0: selector_obj.append(sequence_of_selector_elements) self._selector_obj = selector_obj self._selector_string = selector_string def match(self, root, callback, async=True): queue = [(root, 0, 0)] if async: def async_match(): self._match(queue, callback) if len(queue) != 0: root.after(0, async_match) async_match() else: while len(queue) != 0: self._match(queue, callback) def _match(self, queue, callback): widget, index_1, index_2 = queue.pop(0) selector_element = self._selector_obj[index_1][index_2] if len(self._selector_obj) == index_1 + 1 and len(self._selector_obj[-1]) == index_2 + 1: for child in self._matched_children(widget, selector_element): try: callback(child) except StopIteration: del queue[:] return else: if len(self._selector_obj[index_1]) == index_2 +1: new_index_1 = index_1 + 1 new_index_2 = 0 else: new_index_1 = index_1 new_index_2 = index_2 + 1 for child in self._matched_children(widget, selector_element): queue.insert(0, (child, new_index_1, new_index_2)) if index_2 == 0 and not (self._anchor_start and index_1 == 0): for child in widget.children.values(): queue.append((child, index_1, 0)) def _matched_children(self, widget, selector_element): for child_name, child in widget.children.items(): if isinstance(selector_element, SubSelector_Name): if child_name == selector_element: yield child else: if child.winfo_class() == selector_element: yield child @property def anchor_start(self): return self._anchor_start def __str__(self): return self._selector_string def iterate(root): queue_of_widgets = root.children.values() while True: if len(queue_of_widgets) == 0: return else: yield queue_of_widgets.pop() queue.extend(widget.children.values()) def get_root(widget): if str(widget) == ".": root = widget else: root = widget.nametowidget(".") return root def find_toplevel_by_title(widget, title): root = get_root(widget) if isinstance(title, re._pattern_type): for child in root.children.values(): if child.winfo_class() == "Toplevel" and title.search(child.wm_title()): return child else: for child in root.children.values(): if child.winfo_class() == "Toplevel" and child.wm_title() == title: return child def toplevel_titles(widget): root = get_root(widget) list_of_titles = [] for child in root.children.values(): if child.winfo_class() == "Toplevel": list_of_titles.append(child.wm_title()) return list_of_titles def window_titles(widget): root = get_root(widget) return [root.wm_title()] + list_toplevel_titles(root) def find_widgets_by_name(root, name): list_of_found_widgets = [] queue = root.children.items() while True: widget_name, widget = queue.pop() if widget_name == name: list_of_found_widgets.append(widget) if len(queue) == 0: return list_of_found_widgets else: queue.extend(widget.children.items()) def find_widget_by_name(root, name): queue = root.children.items() while True: widget_name, widget = queue.pop() if widget_name == name: return widget if len(queue) == 0: return else: queue.extend(widget.children.items()) def find_widgets_by_class(root, class_): list_of_found_widgets = [] for widget in iterate(root): if widget.winfo_class() == class_: list_of_found_widgets.append(widget) return list_of_found_widgets def find_widget_by_class(root, class_): for widget in iterate(root): if widget.winfo_class() == class_: return widget def full_name_of_widgets(root): names = [str(root)] queue_of_widgets = root.children.values() while True: if len(queue_of_widgets) == 0: return names else: names.append(str(queue_of_widgets.pop())) queue_of_widgets.extend(widget.children.values()) def find_all(root, selector, callback, async=True): list_of_found_widgets = [] if isinstance(selector, basestring): selector = Tkinter_Selector(selector) selector.match(root, callback, async) def find(root, selector, callback, async=True): if isinstance(selector, basestring): selector = Tkinter_Selector(selector) def wrapper_callback(widget): callback(widget) raise StopIteration selector.match(root, wrapper_callback, async) def config_extended(root, selector, async, config_kwargs): if isinstance(selector, basestring): selector = Tkinter_Selector(selector) def callback(widget, config_kwargs=config_kwargs): widget.configure(**config_kwargs) raise StopIteration selector.match(root, callback, async) def config_all_extended(root, selector, async, config_kwargs): if isinstance(selector, basestring): selector = Tkinter_Selector(selector) selector.match(root, lambda widget, config_kwargs = config_kwargs: widget.configure(**config_kwargs), async) def config(root, selector, **kwargs): config_extended(root, selector, False, kwargs) def config_async(root, selector, **kwargs): config_extended(root, selector, True, kwargs) def config_all(root, selector, **kwargs): config_all_extended(root, selector, False, kwargs) def config_all_async(root, selector, **kwargs): config_all_extended(root, selector, True, kwargs) def tk_print(root, name=True, class_= True, indent="\t"): """Print a tree of widget names and/or classes""" print(root) if name or class_: _print_widget_tree_from(root, name, class_, 1, indent) def _print_widget_tree_from(root, print_names, print_classes, level, indent): for child_name, child in root.children.items(): if print_names: if print_classes: output = "%s (%s)"%(child_name, child.winfo_class()) else: output = child_name else: output = child.winfo_class() print(indent*level + output) _print_widget_tree_from(child, print_names, print_classes, level+1, indent) if __name__ == "__main__": try: from Tkinter import Tk, Frame, Label, Button, LabelFrame except ImportError: from tkinter import Tk, Frame, Label, Button, LabelFrame root = Tk() container1 = LabelFrame(root, class_="Container1") container1.pack() left = Frame(container1, name="left") left.pack() Label(left, name="label0").pack() center = Frame(container1, name="center") center.pack() area1 = Frame(center, class_="Area1") area1.pack() area2 = Frame(center, class_="Area2") area2.pack() Button(area2, name="my_button").pack() Button(area2).pack() area3 = Frame(center, class_="Area3") area3.pack() area4 = Frame(center, class_="Area4") area4.pack() child_frame = Frame(area4, class_="Child_Frame") child_frame.pack() child_label = Label(area4) child_label.pack() right = Frame(container1, name="right") right.pack() container2 = Frame(root, class_="Container2") container2.pack() tk_print(root) def show(widget): print(widget) find_all(root, "Container1*Label", show, False) find(root, "Container1*label0", show) find_all(root, "center*Button", show) find_all(root, ".Container1.left.label0", show) config_all(root, "Container1*Label", text="this is a label") config(root, "Container1*label0", text="this is a label number 0") config_all(root, "Container1*Button", text="this is a button") config(root, "Container1", text="this is Container 1") root.mainloop()