mirror of
https://github.com/python/cpython.git
synced 2024-11-23 18:04:37 +08:00
gh-68166: Add support of "vsapi" in ttk.Style.element_create() (GH-111393)
This commit is contained in:
parent
45d648597b
commit
4dcfd02bed
@ -1391,7 +1391,8 @@ option. If you don't know the class name of a widget, use the method
|
|||||||
.. method:: element_create(elementname, etype, *args, **kw)
|
.. method:: element_create(elementname, etype, *args, **kw)
|
||||||
|
|
||||||
Create a new element in the current theme, of the given *etype* which is
|
Create a new element in the current theme, of the given *etype* which is
|
||||||
expected to be either "image" or "from".
|
expected to be either "image", "from" or "vsapi".
|
||||||
|
The latter is only available in Tk 8.6 on Windows.
|
||||||
|
|
||||||
If "image" is used, *args* should contain the default image name followed
|
If "image" is used, *args* should contain the default image name followed
|
||||||
by statespec/value pairs (this is the imagespec), and *kw* may have the
|
by statespec/value pairs (this is the imagespec), and *kw* may have the
|
||||||
@ -1439,6 +1440,63 @@ option. If you don't know the class name of a widget, use the method
|
|||||||
style = ttk.Style(root)
|
style = ttk.Style(root)
|
||||||
style.element_create('plain.background', 'from', 'default')
|
style.element_create('plain.background', 'from', 'default')
|
||||||
|
|
||||||
|
If "vsapi" is used as the value of *etype*, :meth:`element_create`
|
||||||
|
will create a new element in the current theme whose visual appearance
|
||||||
|
is drawn using the Microsoft Visual Styles API which is responsible
|
||||||
|
for the themed styles on Windows XP and Vista.
|
||||||
|
*args* is expected to contain the Visual Styles class and part as
|
||||||
|
given in the Microsoft documentation followed by an optional sequence
|
||||||
|
of tuples of ttk states and the corresponding Visual Styles API state
|
||||||
|
value.
|
||||||
|
*kw* may have the following options:
|
||||||
|
|
||||||
|
padding=padding
|
||||||
|
Specify the element's interior padding.
|
||||||
|
*padding* is a list of up to four integers specifying the left,
|
||||||
|
top, right and bottom padding quantities respectively.
|
||||||
|
If fewer than four elements are specified, bottom defaults to top,
|
||||||
|
right defaults to left, and top defaults to left.
|
||||||
|
In other words, a list of three numbers specify the left, vertical,
|
||||||
|
and right padding; a list of two numbers specify the horizontal
|
||||||
|
and the vertical padding; a single number specifies the same
|
||||||
|
padding all the way around the widget.
|
||||||
|
This option may not be mixed with any other options.
|
||||||
|
|
||||||
|
margins=padding
|
||||||
|
Specifies the elements exterior padding.
|
||||||
|
*padding* is a list of up to four integers specifying the left, top,
|
||||||
|
right and bottom padding quantities respectively.
|
||||||
|
This option may not be mixed with any other options.
|
||||||
|
|
||||||
|
width=width
|
||||||
|
Specifies the width for the element.
|
||||||
|
If this option is set then the Visual Styles API will not be queried
|
||||||
|
for the recommended size or the part.
|
||||||
|
If this option is set then *height* should also be set.
|
||||||
|
The *width* and *height* options cannot be mixed with the *padding*
|
||||||
|
or *margins* options.
|
||||||
|
|
||||||
|
height=height
|
||||||
|
Specifies the height of the element.
|
||||||
|
See the comments for *width*.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
style = ttk.Style(root)
|
||||||
|
style.element_create('pin', 'vsapi', 'EXPLORERBAR', 3, [
|
||||||
|
('pressed', '!selected', 3),
|
||||||
|
('active', '!selected', 2),
|
||||||
|
('pressed', 'selected', 6),
|
||||||
|
('active', 'selected', 5),
|
||||||
|
('selected', 4),
|
||||||
|
('', 1)])
|
||||||
|
style.layout('Explorer.Pin',
|
||||||
|
[('Explorer.Pin.pin', {'sticky': 'news'})])
|
||||||
|
pin = ttk.Checkbutton(style='Explorer.Pin')
|
||||||
|
pin.pack(expand=True, fill='both')
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Added support of the "vsapi" element factory.
|
||||||
|
|
||||||
.. method:: element_names()
|
.. method:: element_names()
|
||||||
|
|
||||||
|
@ -301,6 +301,11 @@ tkinter
|
|||||||
:meth:`!tk_busy_current`, and :meth:`!tk_busy_status`.
|
:meth:`!tk_busy_current`, and :meth:`!tk_busy_status`.
|
||||||
(Contributed by Miguel, klappnase and Serhiy Storchaka in :gh:`72684`.)
|
(Contributed by Miguel, klappnase and Serhiy Storchaka in :gh:`72684`.)
|
||||||
|
|
||||||
|
* Add support of the "vsapi" element type in
|
||||||
|
the :meth:`~tkinter.ttk.Style.element_create` method of
|
||||||
|
:class:`tkinter.ttk.Style`.
|
||||||
|
(Contributed by Serhiy Storchaka in :gh:`68166`.)
|
||||||
|
|
||||||
traceback
|
traceback
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -258,6 +258,55 @@ class StyleTest(AbstractTkTest, unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(TclError, 'bad option'):
|
with self.assertRaisesRegex(TclError, 'bad option'):
|
||||||
style.element_create('block2', 'image', image, spam=1)
|
style.element_create('block2', 'image', image, spam=1)
|
||||||
|
|
||||||
|
def test_element_create_vsapi_1(self):
|
||||||
|
style = self.style
|
||||||
|
if 'xpnative' not in style.theme_names():
|
||||||
|
self.skipTest("requires 'xpnative' theme")
|
||||||
|
style.element_create('smallclose', 'vsapi', 'WINDOW', 19, [
|
||||||
|
('disabled', 4),
|
||||||
|
('pressed', 3),
|
||||||
|
('active', 2),
|
||||||
|
('', 1)])
|
||||||
|
style.layout('CloseButton',
|
||||||
|
[('CloseButton.smallclose', {'sticky': 'news'})])
|
||||||
|
b = ttk.Button(self.root, style='CloseButton')
|
||||||
|
b.pack(expand=True, fill='both')
|
||||||
|
self.assertEqual(b.winfo_reqwidth(), 13)
|
||||||
|
self.assertEqual(b.winfo_reqheight(), 13)
|
||||||
|
|
||||||
|
def test_element_create_vsapi_2(self):
|
||||||
|
style = self.style
|
||||||
|
if 'xpnative' not in style.theme_names():
|
||||||
|
self.skipTest("requires 'xpnative' theme")
|
||||||
|
style.element_create('pin', 'vsapi', 'EXPLORERBAR', 3, [
|
||||||
|
('pressed', '!selected', 3),
|
||||||
|
('active', '!selected', 2),
|
||||||
|
('pressed', 'selected', 6),
|
||||||
|
('active', 'selected', 5),
|
||||||
|
('selected', 4),
|
||||||
|
('', 1)])
|
||||||
|
style.layout('Explorer.Pin',
|
||||||
|
[('Explorer.Pin.pin', {'sticky': 'news'})])
|
||||||
|
pin = ttk.Checkbutton(self.root, style='Explorer.Pin')
|
||||||
|
pin.pack(expand=True, fill='both')
|
||||||
|
self.assertEqual(pin.winfo_reqwidth(), 16)
|
||||||
|
self.assertEqual(pin.winfo_reqheight(), 16)
|
||||||
|
|
||||||
|
def test_element_create_vsapi_3(self):
|
||||||
|
style = self.style
|
||||||
|
if 'xpnative' not in style.theme_names():
|
||||||
|
self.skipTest("requires 'xpnative' theme")
|
||||||
|
style.element_create('headerclose', 'vsapi', 'EXPLORERBAR', 2, [
|
||||||
|
('pressed', 3),
|
||||||
|
('active', 2),
|
||||||
|
('', 1)])
|
||||||
|
style.layout('Explorer.CloseButton',
|
||||||
|
[('Explorer.CloseButton.headerclose', {'sticky': 'news'})])
|
||||||
|
b = ttk.Button(self.root, style='Explorer.CloseButton')
|
||||||
|
b.pack(expand=True, fill='both')
|
||||||
|
self.assertEqual(b.winfo_reqwidth(), 16)
|
||||||
|
self.assertEqual(b.winfo_reqheight(), 16)
|
||||||
|
|
||||||
def test_theme_create(self):
|
def test_theme_create(self):
|
||||||
style = self.style
|
style = self.style
|
||||||
curr_theme = style.theme_use()
|
curr_theme = style.theme_use()
|
||||||
@ -358,6 +407,39 @@ class StyleTest(AbstractTkTest, unittest.TestCase):
|
|||||||
|
|
||||||
style.theme_use(curr_theme)
|
style.theme_use(curr_theme)
|
||||||
|
|
||||||
|
def test_theme_create_vsapi(self):
|
||||||
|
style = self.style
|
||||||
|
if 'xpnative' not in style.theme_names():
|
||||||
|
self.skipTest("requires 'xpnative' theme")
|
||||||
|
curr_theme = style.theme_use()
|
||||||
|
new_theme = 'testtheme5'
|
||||||
|
style.theme_create(new_theme, settings={
|
||||||
|
'pin' : {
|
||||||
|
'element create': ['vsapi', 'EXPLORERBAR', 3, [
|
||||||
|
('pressed', '!selected', 3),
|
||||||
|
('active', '!selected', 2),
|
||||||
|
('pressed', 'selected', 6),
|
||||||
|
('active', 'selected', 5),
|
||||||
|
('selected', 4),
|
||||||
|
('', 1)]],
|
||||||
|
},
|
||||||
|
'Explorer.Pin' : {
|
||||||
|
'layout': [('Explorer.Pin.pin', {'sticky': 'news'})],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
style.theme_use(new_theme)
|
||||||
|
self.assertIn('pin', style.element_names())
|
||||||
|
self.assertEqual(style.layout('Explorer.Pin'),
|
||||||
|
[('Explorer.Pin.pin', {'sticky': 'nswe'})])
|
||||||
|
|
||||||
|
pin = ttk.Checkbutton(self.root, style='Explorer.Pin')
|
||||||
|
pin.pack(expand=True, fill='both')
|
||||||
|
self.assertEqual(pin.winfo_reqwidth(), 16)
|
||||||
|
self.assertEqual(pin.winfo_reqheight(), 16)
|
||||||
|
|
||||||
|
style.theme_use(curr_theme)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -179,7 +179,7 @@ class InternalFunctionsTest(unittest.TestCase):
|
|||||||
# don't format returned values as a tcl script
|
# don't format returned values as a tcl script
|
||||||
# minimum acceptable for image type
|
# minimum acceptable for image type
|
||||||
self.assertEqual(ttk._format_elemcreate('image', False, 'test'),
|
self.assertEqual(ttk._format_elemcreate('image', False, 'test'),
|
||||||
("test ", ()))
|
("test", ()))
|
||||||
# specifying a state spec
|
# specifying a state spec
|
||||||
self.assertEqual(ttk._format_elemcreate('image', False, 'test',
|
self.assertEqual(ttk._format_elemcreate('image', False, 'test',
|
||||||
('', 'a')), ("test {} a", ()))
|
('', 'a')), ("test {} a", ()))
|
||||||
@ -203,17 +203,19 @@ class InternalFunctionsTest(unittest.TestCase):
|
|||||||
# don't format returned values as a tcl script
|
# don't format returned values as a tcl script
|
||||||
# minimum acceptable for vsapi
|
# minimum acceptable for vsapi
|
||||||
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b'),
|
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b'),
|
||||||
("a b ", ()))
|
('a', 'b', ('', 1), ()))
|
||||||
# now with a state spec with multiple states
|
# now with a state spec with multiple states
|
||||||
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
|
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
|
||||||
('a', 'b', 'c')), ("a b {a b} c", ()))
|
[('a', 'b', 'c')]), ('a', 'b', ('a b', 'c'), ()))
|
||||||
# state spec and option
|
# state spec and option
|
||||||
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
|
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
|
||||||
('a', 'b'), opt='x'), ("a b a b", ("-opt", "x")))
|
[('a', 'b')], opt='x'), ('a', 'b', ('a', 'b'), ("-opt", "x")))
|
||||||
# format returned values as a tcl script
|
# format returned values as a tcl script
|
||||||
# state spec with a multivalue and an option
|
# state spec with a multivalue and an option
|
||||||
self.assertEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b',
|
self.assertEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b',
|
||||||
('a', 'b', [1, 2]), opt='x'), ("{a b {a b} {1 2}}", "-opt x"))
|
opt='x'), ("a b {{} 1}", "-opt x"))
|
||||||
|
self.assertEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b',
|
||||||
|
[('a', 'b', [1, 2])], opt='x'), ("a b {{a b} {1 2}}", "-opt x"))
|
||||||
|
|
||||||
# Testing type = from
|
# Testing type = from
|
||||||
# from type expects at least a type name
|
# from type expects at least a type name
|
||||||
@ -222,9 +224,9 @@ class InternalFunctionsTest(unittest.TestCase):
|
|||||||
self.assertEqual(ttk._format_elemcreate('from', False, 'a'),
|
self.assertEqual(ttk._format_elemcreate('from', False, 'a'),
|
||||||
('a', ()))
|
('a', ()))
|
||||||
self.assertEqual(ttk._format_elemcreate('from', False, 'a', 'b'),
|
self.assertEqual(ttk._format_elemcreate('from', False, 'a', 'b'),
|
||||||
('a', ('b', )))
|
('a', ('b',)))
|
||||||
self.assertEqual(ttk._format_elemcreate('from', True, 'a', 'b'),
|
self.assertEqual(ttk._format_elemcreate('from', True, 'a', 'b'),
|
||||||
('{a}', 'b'))
|
('a', 'b'))
|
||||||
|
|
||||||
|
|
||||||
def test_format_layoutlist(self):
|
def test_format_layoutlist(self):
|
||||||
@ -326,6 +328,22 @@ class InternalFunctionsTest(unittest.TestCase):
|
|||||||
"ttk::style element create thing image {name {state1 state2} val} "
|
"ttk::style element create thing image {name {state1 state2} val} "
|
||||||
"-opt {3 2m}")
|
"-opt {3 2m}")
|
||||||
|
|
||||||
|
vsapi = {'pin': {'element create':
|
||||||
|
['vsapi', 'EXPLORERBAR', 3, [
|
||||||
|
('pressed', '!selected', 3),
|
||||||
|
('active', '!selected', 2),
|
||||||
|
('pressed', 'selected', 6),
|
||||||
|
('active', 'selected', 5),
|
||||||
|
('selected', 4),
|
||||||
|
('', 1)]]}}
|
||||||
|
self.assertEqual(ttk._script_from_settings(vsapi),
|
||||||
|
"ttk::style element create pin vsapi EXPLORERBAR 3 {"
|
||||||
|
"{pressed !selected} 3 "
|
||||||
|
"{active !selected} 2 "
|
||||||
|
"{pressed selected} 6 "
|
||||||
|
"{active selected} 5 "
|
||||||
|
"selected 4 "
|
||||||
|
"{} 1} ")
|
||||||
|
|
||||||
def test_tclobj_to_py(self):
|
def test_tclobj_to_py(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -95,40 +95,47 @@ def _format_mapdict(mapdict, script=False):
|
|||||||
|
|
||||||
def _format_elemcreate(etype, script=False, *args, **kw):
|
def _format_elemcreate(etype, script=False, *args, **kw):
|
||||||
"""Formats args and kw according to the given element factory etype."""
|
"""Formats args and kw according to the given element factory etype."""
|
||||||
spec = None
|
specs = ()
|
||||||
opts = ()
|
opts = ()
|
||||||
if etype in ("image", "vsapi"):
|
if etype == "image": # define an element based on an image
|
||||||
if etype == "image": # define an element based on an image
|
# first arg should be the default image name
|
||||||
# first arg should be the default image name
|
iname = args[0]
|
||||||
iname = args[0]
|
# next args, if any, are statespec/value pairs which is almost
|
||||||
# next args, if any, are statespec/value pairs which is almost
|
# a mapdict, but we just need the value
|
||||||
# a mapdict, but we just need the value
|
imagespec = (iname, *_mapdict_values(args[1:]))
|
||||||
imagespec = _join(_mapdict_values(args[1:]))
|
if script:
|
||||||
spec = "%s %s" % (iname, imagespec)
|
specs = (imagespec,)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# define an element whose visual appearance is drawn using the
|
specs = (_join(imagespec),)
|
||||||
# Microsoft Visual Styles API which is responsible for the
|
opts = _format_optdict(kw, script)
|
||||||
# themed styles on Windows XP and Vista.
|
|
||||||
# Availability: Tk 8.6, Windows XP and Vista.
|
|
||||||
class_name, part_id = args[:2]
|
|
||||||
statemap = _join(_mapdict_values(args[2:]))
|
|
||||||
spec = "%s %s %s" % (class_name, part_id, statemap)
|
|
||||||
|
|
||||||
|
if etype == "vsapi":
|
||||||
|
# define an element whose visual appearance is drawn using the
|
||||||
|
# Microsoft Visual Styles API which is responsible for the
|
||||||
|
# themed styles on Windows XP and Vista.
|
||||||
|
# Availability: Tk 8.6, Windows XP and Vista.
|
||||||
|
if len(args) < 3:
|
||||||
|
class_name, part_id = args
|
||||||
|
statemap = (((), 1),)
|
||||||
|
else:
|
||||||
|
class_name, part_id, statemap = args
|
||||||
|
specs = (class_name, part_id, tuple(_mapdict_values(statemap)))
|
||||||
opts = _format_optdict(kw, script)
|
opts = _format_optdict(kw, script)
|
||||||
|
|
||||||
elif etype == "from": # clone an element
|
elif etype == "from": # clone an element
|
||||||
# it expects a themename and optionally an element to clone from,
|
# it expects a themename and optionally an element to clone from,
|
||||||
# otherwise it will clone {} (empty element)
|
# otherwise it will clone {} (empty element)
|
||||||
spec = args[0] # theme name
|
specs = (args[0],) # theme name
|
||||||
if len(args) > 1: # elementfrom specified
|
if len(args) > 1: # elementfrom specified
|
||||||
opts = (_format_optvalue(args[1], script),)
|
opts = (_format_optvalue(args[1], script),)
|
||||||
|
|
||||||
if script:
|
if script:
|
||||||
spec = '{%s}' % spec
|
specs = _join(specs)
|
||||||
opts = ' '.join(opts)
|
opts = ' '.join(opts)
|
||||||
|
return specs, opts
|
||||||
|
else:
|
||||||
|
return *specs, opts
|
||||||
|
|
||||||
return spec, opts
|
|
||||||
|
|
||||||
def _format_layoutlist(layout, indent=0, indent_size=2):
|
def _format_layoutlist(layout, indent=0, indent_size=2):
|
||||||
"""Formats a layout list so we can pass the result to ttk::style
|
"""Formats a layout list so we can pass the result to ttk::style
|
||||||
@ -214,10 +221,10 @@ def _script_from_settings(settings):
|
|||||||
|
|
||||||
elemargs = eopts[1:argc]
|
elemargs = eopts[1:argc]
|
||||||
elemkw = eopts[argc] if argc < len(eopts) and eopts[argc] else {}
|
elemkw = eopts[argc] if argc < len(eopts) and eopts[argc] else {}
|
||||||
spec, opts = _format_elemcreate(etype, True, *elemargs, **elemkw)
|
specs, eopts = _format_elemcreate(etype, True, *elemargs, **elemkw)
|
||||||
|
|
||||||
script.append("ttk::style element create %s %s %s %s" % (
|
script.append("ttk::style element create %s %s %s %s" % (
|
||||||
name, etype, spec, opts))
|
name, etype, specs, eopts))
|
||||||
|
|
||||||
return '\n'.join(script)
|
return '\n'.join(script)
|
||||||
|
|
||||||
@ -434,9 +441,9 @@ class Style(object):
|
|||||||
|
|
||||||
def element_create(self, elementname, etype, *args, **kw):
|
def element_create(self, elementname, etype, *args, **kw):
|
||||||
"""Create a new element in the current theme of given etype."""
|
"""Create a new element in the current theme of given etype."""
|
||||||
spec, opts = _format_elemcreate(etype, False, *args, **kw)
|
*specs, opts = _format_elemcreate(etype, False, *args, **kw)
|
||||||
self.tk.call(self._name, "element", "create", elementname, etype,
|
self.tk.call(self._name, "element", "create", elementname, etype,
|
||||||
spec, *opts)
|
*specs, *opts)
|
||||||
|
|
||||||
|
|
||||||
def element_names(self):
|
def element_names(self):
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
Add support of the "vsapi" element type in
|
||||||
|
:meth:`tkinter.ttk.Style.element_create`.
|
Loading…
Reference in New Issue
Block a user