Skip to content

Commit 1cb95cd

Browse files
[3.12] gh-68166: Tkinter: Add tests and examples for element_create() (GH-111453) (GH-111857)
* Remove mention of "vsapi" element type from the documentation. * Add tests for element_create() and other ttk.Style methods. * Add examples for element_create() in the documentation. (cherry picked from commit 005d1e8) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 698b4b7 commit 1cb95cd

File tree

3 files changed

+203
-3
lines changed

3 files changed

+203
-3
lines changed

Doc/library/tkinter.ttk.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,8 +1391,7 @@ option. If you don't know the class name of a widget, use the method
13911391
.. method:: element_create(elementname, etype, *args, **kw)
13921392

13931393
Create a new element in the current theme, of the given *etype* which is
1394-
expected to be either "image", "from" or "vsapi". The latter is only
1395-
available in Tk 8.6a for Windows XP and Vista and is not described here.
1394+
expected to be either "image" or "from".
13961395

13971396
If "image" is used, *args* should contain the default image name followed
13981397
by statespec/value pairs (this is the imagespec), and *kw* may have the
@@ -1418,13 +1417,28 @@ option. If you don't know the class name of a widget, use the method
14181417
Specifies a minimum width for the element. If less than zero, the
14191418
base image's width is used as a default.
14201419

1420+
Example::
1421+
1422+
img1 = tkinter.PhotoImage(master=root, file='button.png')
1423+
img1 = tkinter.PhotoImage(master=root, file='button-pressed.png')
1424+
img1 = tkinter.PhotoImage(master=root, file='button-active.png')
1425+
style = ttk.Style(root)
1426+
style.element_create('Button.button', 'image',
1427+
img1, ('pressed', img2), ('active', img3),
1428+
border=(2, 4), sticky='we')
1429+
14211430
If "from" is used as the value of *etype*,
14221431
:meth:`element_create` will clone an existing
14231432
element. *args* is expected to contain a themename, from which
14241433
the element will be cloned, and optionally an element to clone from.
14251434
If this element to clone from is not specified, an empty element will
14261435
be used. *kw* is discarded.
14271436

1437+
Example::
1438+
1439+
style = ttk.Style(root)
1440+
style.element_create('plain.background', 'from', 'default')
1441+
14281442

14291443
.. method:: element_names()
14301444

Lib/test/test_ttk/test_style.py

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
import tkinter
44
from tkinter import ttk
5+
from tkinter import TclError
56
from test import support
67
from test.support import requires
78
from test.test_tkinter.support import AbstractTkTest, get_tk_patchlevel
@@ -122,7 +123,6 @@ def test_theme_use(self):
122123

123124
self.style.theme_use(curr_theme)
124125

125-
126126
def test_configure_custom_copy(self):
127127
style = self.style
128128

@@ -176,6 +176,188 @@ def test_map_custom_copy(self):
176176
for key, value in default.items():
177177
self.assertEqual(style.map(newname, key), value)
178178

179+
def test_element_options(self):
180+
style = self.style
181+
element_names = style.element_names()
182+
self.assertNotIsInstance(element_names, str)
183+
for name in element_names:
184+
self.assertIsInstance(name, str)
185+
element_options = style.element_options(name)
186+
self.assertNotIsInstance(element_options, str)
187+
for optname in element_options:
188+
self.assertIsInstance(optname, str)
189+
190+
def test_element_create_errors(self):
191+
style = self.style
192+
with self.assertRaises(TypeError):
193+
style.element_create('plain.newelem')
194+
with self.assertRaisesRegex(TclError, 'No such element type spam'):
195+
style.element_create('plain.newelem', 'spam')
196+
197+
def test_element_create_from(self):
198+
style = self.style
199+
style.element_create('plain.background', 'from', 'default')
200+
self.assertIn('plain.background', style.element_names())
201+
style.element_create('plain.arrow', 'from', 'default', 'rightarrow')
202+
self.assertIn('plain.arrow', style.element_names())
203+
204+
def test_element_create_from_errors(self):
205+
style = self.style
206+
with self.assertRaises(IndexError):
207+
style.element_create('plain.newelem', 'from')
208+
with self.assertRaisesRegex(TclError, 'theme "spam" doesn\'t exist'):
209+
style.element_create('plain.newelem', 'from', 'spam')
210+
211+
def test_element_create_image(self):
212+
style = self.style
213+
image = tkinter.PhotoImage(master=self.root, width=12, height=10)
214+
style.element_create('block', 'image', image)
215+
self.assertIn('block', style.element_names())
216+
217+
style.layout('TestLabel1', [('block', {'sticky': 'news'})])
218+
a = ttk.Label(self.root, style='TestLabel1')
219+
a.pack(expand=True, fill='both')
220+
self.assertEqual(a.winfo_reqwidth(), 12)
221+
self.assertEqual(a.winfo_reqheight(), 10)
222+
223+
imgfile = support.findfile('python.xbm', subdir='imghdrdata')
224+
img1 = tkinter.BitmapImage(master=self.root, file=imgfile,
225+
foreground='yellow', background='blue')
226+
img2 = tkinter.BitmapImage(master=self.root, file=imgfile,
227+
foreground='blue', background='yellow')
228+
img3 = tkinter.BitmapImage(master=self.root, file=imgfile,
229+
foreground='white', background='black')
230+
style.element_create('Button.button', 'image',
231+
img1, ('pressed', img2), ('active', img3),
232+
border=(2, 4), sticky='we')
233+
self.assertIn('Button.button', style.element_names())
234+
235+
style.layout('Button', [('Button.button', {'sticky': 'news'})])
236+
b = ttk.Button(self.root, style='Button')
237+
b.pack(expand=True, fill='both')
238+
self.assertEqual(b.winfo_reqwidth(), 16)
239+
self.assertEqual(b.winfo_reqheight(), 16)
240+
241+
def test_element_create_image_errors(self):
242+
style = self.style
243+
image = tkinter.PhotoImage(master=self.root, width=10, height=10)
244+
with self.assertRaises(IndexError):
245+
style.element_create('block2', 'image')
246+
with self.assertRaises(TypeError):
247+
style.element_create('block2', 'image', image, 1)
248+
with self.assertRaises(ValueError):
249+
style.element_create('block2', 'image', image, ())
250+
with self.assertRaisesRegex(TclError, 'Invalid state name'):
251+
style.element_create('block2', 'image', image, ('spam', image))
252+
with self.assertRaisesRegex(TclError, 'Invalid state name'):
253+
style.element_create('block2', 'image', image, (1, image))
254+
with self.assertRaises(TypeError):
255+
style.element_create('block2', 'image', image, ('pressed', 1, image))
256+
with self.assertRaises(TypeError):
257+
style.element_create('block2', 'image', image, (1, 'selected', image))
258+
with self.assertRaisesRegex(TclError, 'bad option'):
259+
style.element_create('block2', 'image', image, spam=1)
260+
261+
def test_theme_create(self):
262+
style = self.style
263+
curr_theme = style.theme_use()
264+
curr_layout = style.layout('TLabel')
265+
style.theme_create('testtheme1')
266+
self.assertIn('testtheme1', style.theme_names())
267+
268+
style.theme_create('testtheme2', settings={
269+
'elem' : {'element create': ['from', 'default'],},
270+
'TLabel' : {
271+
'configure': {'padding': 10},
272+
'layout': [('elem', {'sticky': 'we'})],
273+
},
274+
})
275+
self.assertIn('testtheme2', style.theme_names())
276+
277+
style.theme_create('testtheme3', 'testtheme2')
278+
self.assertIn('testtheme3', style.theme_names())
279+
280+
style.theme_use('testtheme1')
281+
self.assertEqual(style.element_names(), ())
282+
self.assertEqual(style.layout('TLabel'), curr_layout)
283+
284+
style.theme_use('testtheme2')
285+
self.assertEqual(style.element_names(), ('elem',))
286+
self.assertEqual(style.lookup('TLabel', 'padding'), '10')
287+
self.assertEqual(style.layout('TLabel'), [('elem', {'sticky': 'we'})])
288+
289+
style.theme_use('testtheme3')
290+
self.assertEqual(style.element_names(), ())
291+
self.assertEqual(style.lookup('TLabel', 'padding'), '')
292+
self.assertEqual(style.layout('TLabel'), [('elem', {'sticky': 'we'})])
293+
294+
style.theme_use(curr_theme)
295+
296+
def test_theme_create_image(self):
297+
style = self.style
298+
curr_theme = style.theme_use()
299+
image = tkinter.PhotoImage(master=self.root, width=10, height=10)
300+
new_theme = 'testtheme4'
301+
style.theme_create(new_theme, settings={
302+
'block' : {
303+
'element create': ['image', image, {'width': 120, 'height': 100}],
304+
},
305+
'TestWidget.block2' : {
306+
'element create': ['image', image],
307+
},
308+
'TestWidget' : {
309+
'configure': {
310+
'anchor': 'left',
311+
'padding': (3, 0, 0, 2),
312+
'foreground': 'yellow',
313+
},
314+
'map': {
315+
'foreground': [
316+
('pressed', 'red'),
317+
('active', 'disabled', 'blue'),
318+
],
319+
},
320+
'layout': [
321+
('TestWidget.block', {'sticky': 'we', 'side': 'left'}),
322+
('TestWidget.border', {
323+
'sticky': 'nsw',
324+
'border': 1,
325+
'children': [
326+
('TestWidget.block2', {'sticky': 'nswe'})
327+
]
328+
})
329+
],
330+
},
331+
})
332+
333+
style.theme_use(new_theme)
334+
self.assertIn('block', style.element_names())
335+
self.assertEqual(style.lookup('TestWidget', 'anchor'), 'left')
336+
self.assertEqual(style.lookup('TestWidget', 'padding'), '3 0 0 2')
337+
self.assertEqual(style.lookup('TestWidget', 'foreground'), 'yellow')
338+
self.assertEqual(style.lookup('TestWidget', 'foreground',
339+
['active']), 'yellow')
340+
self.assertEqual(style.lookup('TestWidget', 'foreground',
341+
['active', 'pressed']), 'red')
342+
self.assertEqual(style.lookup('TestWidget', 'foreground',
343+
['active', 'disabled']), 'blue')
344+
self.assertEqual(style.layout('TestWidget'),
345+
[
346+
('TestWidget.block', {'side': 'left', 'sticky': 'we'}),
347+
('TestWidget.border', {
348+
'sticky': 'nsw',
349+
'border': '1',
350+
'children': [('TestWidget.block2', {'sticky': 'nswe'})]
351+
})
352+
])
353+
354+
b = ttk.Label(self.root, style='TestWidget')
355+
b.pack(expand=True, fill='both')
356+
self.assertEqual(b.winfo_reqwidth(), 134)
357+
self.assertEqual(b.winfo_reqheight(), 100)
358+
359+
style.theme_use(curr_theme)
360+
179361

180362
if __name__ == "__main__":
181363
unittest.main()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Remove mention of not supported "vsapi" element type in
2+
:meth:`tkinter.ttk.Style.element_create`. Add tests for ``element_create()``
3+
and other ``ttk.Style`` methods. Add examples for ``element_create()`` in
4+
the documentation.

0 commit comments

Comments
 (0)