Welcome, guest | Sign In | My Account | Store | Cart

Tkinter selectors like beautifulsoup selectors or jquery selectors.

It makes easy to select elements at runtime. The selectors use the same syntax than the option database.

Please let me now if you find a bug.

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, 406 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# 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()
Created by Miguel Martínez López on Sun, 25 Dec 2016 (MIT)
Python recipes (4591)
Miguel Martínez López's recipes (56)

Required Modules

  • (none specified)

Other Information and Tasks