Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions mytk/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,26 @@ def widget(self):
"""
return self.root

def widget_hierarchy(self, widget=None):
"""Return the tkinter widget tree as a nested dict, using only
tkinter's built-in winfo_* introspection methods.

Args:
widget: The tkinter widget to root the tree at. Defaults to the
application's root window.
"""
if widget is None:
widget = self.root

def node(w):
return {
"class": w.winfo_class(), # e.g. "TButton", "TLabel", "Frame"
"name": w.winfo_name(), # last path component
"path": str(w), # full Tk path, e.g. ".!frame.!button2"
"children": [node(c) for c in w.winfo_children()],
}
return node(widget)

@property
def root(self):
"""Returns the root window widget.
Expand Down
11 changes: 11 additions & 0 deletions mytk/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ def _bind_destroy_cancel(self):
"""No-op fallback. Overridden by EventCapable when present in the MRO."""
pass

@staticmethod
def view_for(widget):
"""Return the mytk View that owns this tkinter widget, or None."""
return getattr(widget, "mytk_view", None)

"""
Placing widgets in other widgets
"""
Expand Down Expand Up @@ -152,6 +157,8 @@ def grid_into(
widget = parent.widget

self._bind_destroy_cancel()
if self.widget is not None:
self.widget.mytk_view = self
self._apply_background_color()

column = 0
Expand Down Expand Up @@ -253,6 +260,8 @@ def pack_into(self, parent, **kwargs):
self.create_widget(master=parent.widget)
self.parent = parent
self._bind_destroy_cancel()
if self.widget is not None:
self.widget.mytk_view = self
self._apply_background_color()

if self.widget is not None:
Expand All @@ -271,6 +280,8 @@ def place_into(self, parent, x, y, width, height):
self.create_widget(master=parent.widget)
self.parent = parent
self._bind_destroy_cancel()
if self.widget is not None:
self.widget.mytk_view = self
self._apply_background_color()

if self.widget is not None:
Expand Down
70 changes: 70 additions & 0 deletions mytk/tests/testWidgetHierarchy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import unittest

import envtest

from mytk import *


class TestWidget(Base):
def create_widget(self, master):
self.parent = master
self.widget = ttk.Frame(master, width=100, height=100)


class TestViewFor(envtest.MyTkTestCase):
def test_view_for_returns_owning_view(self):
widget = TestWidget()
widget.grid_into(self.app.window, row=1, column=0)
self.assertIs(Base.view_for(widget.widget), widget)

def test_view_for_after_pack(self):
# The app window already manages slaves with grid, so pack into a
# fresh empty container to avoid a geometry-manager conflict.
container = View(width=100, height=100)
container.grid_into(self.app.window, row=1, column=0)
widget = TestWidget()
widget.pack_into(container)
self.assertIs(Base.view_for(widget.widget), widget)

def test_view_for_after_place(self):
widget = TestWidget()
widget.place_into(self.app.window, x=0, y=0, width=50, height=50)
self.assertIs(Base.view_for(widget.widget), widget)

def test_view_for_unknown_widget_returns_none(self):
bare = ttk.Frame(self.app.window.widget)
self.assertIsNone(Base.view_for(bare))


class TestWidgetHierarchy(envtest.MyTkTestCase):
def test_hierarchy_defaults_to_root(self):
tree = self.app.widget_hierarchy()
self.assertEqual(tree["path"], ".")
self.assertIn("class", tree)
self.assertIn("name", tree)
self.assertIsInstance(tree["children"], list)

def test_hierarchy_includes_added_widget(self):
widget = TestWidget()
widget.grid_into(self.app.window, row=1, column=0)

tree = self.app.widget_hierarchy()

def paths(node):
yield node["path"]
for child in node["children"]:
yield from paths(child)

self.assertIn(str(widget.widget), list(paths(tree)))

def test_hierarchy_custom_root(self):
widget = TestWidget()
widget.grid_into(self.app.window, row=1, column=0)

tree = self.app.widget_hierarchy(widget.widget)
self.assertEqual(tree["path"], str(widget.widget))
self.assertEqual(tree["class"], widget.widget.winfo_class())


if __name__ == "__main__":
unittest.main()
Loading