Skip to content

Commit 8c66333

Browse files
committed
checkbox: initial checkbox support
Feature request #1092
1 parent 3524002 commit 8c66333

28 files changed

+809
-11
lines changed

Changes

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Release 3.2.2 - January XX 2025
2+
--------------------------------
3+
4+
* Added support for checkboxes xxx
5+
6+
17
Release 3.2.1 - January 22 2025
28
--------------------------------
39

dev/docs/source/_images/checkbox.png

82.6 KB
Loading
78 KB
Loading
84.6 KB
Loading

dev/docs/source/example_checkbox.rst

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.. SPDX-License-Identifier: BSD-2-Clause
2+
Copyright (c) 2013-2025, John McNamara, [email protected]
3+
4+
.. _ex_checkbox:
5+
6+
Example: Inserting a checkbox in a Worksheet
7+
============================================
8+
9+
An example of adding checkbox boolean values to a worksheet.
10+
11+
.. image:: _images/checkbox.png
12+
13+
.. literalinclude:: ../../../examples/checkbox.py

dev/docs/source/examples.rst

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ directory of the XlsxWriter distribution.
3838
example_headers_footers.rst
3939
example_panes.rst
4040
example_tables.rst
41+
example_checkbox.rst
4142
example_user_types1.rst
4243
example_user_types2.rst
4344
example_user_types3.rst

dev/docs/source/format.rst

+17
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,11 @@ properties that can be applied and the equivalent object method:
278278
+------------+------------------+----------------------+------------------------------+
279279
| | Right color | ``'right_color'`` | :func:`set_right_color()` |
280280
+------------+------------------+----------------------+------------------------------+
281+
| Misc. | Cell border | ``'quote_prefix'`` | :func:`set_quote_prefix()` |
282+
+------------+------------------+----------------------+------------------------------+
283+
| | Checkbox format | ``'checkbox'`` | :func:`set_checkbox()` |
284+
+------------+------------------+----------------------+------------------------------+
285+
281286

282287
The format properties and methods are explained in the following sections.
283288

@@ -1211,3 +1216,15 @@ Set the quote prefix property of a format to ensure a string is treated as a
12111216
string after editing. This is the same as prefixing the string with a single
12121217
quote in Excel. You don't need to add the quote to the string but you do need
12131218
to add the format.
1219+
1220+
1221+
format.set_checkbox()
1222+
---------------------
1223+
1224+
.. py:function:: set_checkbox()
1225+
1226+
Turn on the checkbox property for the format.
1227+
1228+
This format property can be used with a cell that contains a boolean value to
1229+
display it as a checkbox. This property isn't required very often and it is
1230+
generally easier to create a checkbox using the :func:`insert_checkbox` method.

dev/docs/source/worksheet.rst

+55
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,61 @@ See :ref:`object_position` for more detailed information about the positioning
17241724
and scaling of images within a worksheet.
17251725

17261726

1727+
worksheet.insert_checkbox()
1728+
---------------------------
1729+
1730+
.. py:function:: insert_checkbox(row, col, boolean[, cell_format])
1731+
1732+
Insert a boolean checkbox in a worksheet cell.
1733+
1734+
:param row: The cell row (zero indexed).
1735+
:param col: The cell column (zero indexed).
1736+
:param boolean: The boolean value to display as a checkbox.
1737+
:param cell_format: Optional Format object.
1738+
:type row: int
1739+
:type col: int
1740+
:type boolean: bool
1741+
:type cell_format: :ref:`Format <format>`
1742+
1743+
:returns: 0: Success.
1744+
:returns: -1: Row or column is out of worksheet bounds.
1745+
1746+
Checkboxes are a new feature added to `Excel in 2024`_. They are a way of
1747+
displaying a boolean value as a checkbox in a cell. The underlying value is
1748+
still an Excel ``TRUE/FALSE`` boolean value and can be used in formulas and in
1749+
references.
1750+
1751+
.. image:: _images/excel_checkbox.png
1752+
1753+
.. _Excel in 2024: https://techcommunity.microsoft.com/blog/excelblog/introducing-checkboxes-in-excel/4173561
1754+
1755+
The ``insert_checkbox()`` method can be used to replicate this behavior:
1756+
1757+
.. literalinclude:: ../../../examples/checkbox.py
1758+
:lines: 18-36
1759+
1760+
.. image:: _images/checkbox.png
1761+
1762+
See :ref:`ex_checkbox` for the complete example.
1763+
1764+
The checkbox feature is only available in Excel versions from 2024 and later. In
1765+
older versions the value will be displayed as a standard Excel ``TRUE`` or
1766+
``FALSE`` boolean. In fact Excel stores a checkbox as a normal boolean but with
1767+
a special format. If required you can make use of this property to create a
1768+
checkbox with :func:`write_boolean` and a cell format that has the
1769+
:func:`set_checkbox` property set::
1770+
1771+
cell_format = workbook.add_format({"checkbox": True})
1772+
1773+
worksheet.write(2, 2, False, cell_format)
1774+
worksheet.write(3, 2, True, cell_format)
1775+
1776+
.. image:: _images/checkbox_boolean.png
1777+
1778+
This latter method isn't required very often but it can be occasionally useful
1779+
if you are dealing with boolean values in a dataframe.
1780+
1781+
17271782
worksheet.insert_button()
17281783
-------------------------
17291784

examples/checkbox.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
##############################################################################
2+
#
3+
# An example of adding checkbox boolean values to a worksheet using the the
4+
# XlsxWriter Python module.
5+
#
6+
# SPDX-License-Identifier: BSD-2-Clause
7+
#
8+
# Copyright (c) 2013-2025, John McNamara, [email protected]
9+
#
10+
import xlsxwriter
11+
12+
# Create a new Excel file object.
13+
workbook = xlsxwriter.Workbook("checkbox.xlsx")
14+
15+
# Add a worksheet to the workbook.
16+
worksheet = workbook.add_worksheet()
17+
18+
# Create some formats to use in the worksheet.
19+
bold = workbook.add_format({"bold": True})
20+
light_red = workbook.add_format({"bg_color": "#FFC7CE"})
21+
light_green = workbook.add_format({"bg_color": "#C6EFCE"})
22+
23+
# Set the column width for clarity.
24+
worksheet.set_column(0, 0, 30)
25+
26+
# Write some descriptions.
27+
worksheet.write(1, 0, "Some simple checkboxes:", bold)
28+
worksheet.write(4, 0, "Some checkboxes with cell formats:", bold)
29+
30+
# Insert some boolean checkboxes to the worksheet.
31+
worksheet.insert_checkbox(1, 1, False)
32+
worksheet.insert_checkbox(2, 1, True)
33+
34+
# Insert some checkboxes with cell formats.
35+
worksheet.insert_checkbox(4, 1, False, light_red)
36+
worksheet.insert_checkbox(5, 1, True, light_green)
37+
38+
# Close the workbook.
39+
workbook.close()

xlsxwriter/contenttypes.py

+9
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ def _add_metadata(self):
181181
("/xl/metadata.xml", APP_DOCUMENT + "spreadsheetml.sheetMetadata+xml")
182182
)
183183

184+
def _add_feature_bag_property(self):
185+
# Add the featurePropertyBag file to the ContentTypes overrides.
186+
self._add_override(
187+
(
188+
"/xl/featurePropertyBag/featurePropertyBag.xml",
189+
"application/vnd.ms-excel.featurepropertybag+xml",
190+
)
191+
)
192+
184193
def _add_rich_value(self):
185194
# Add the richValue files to the ContentTypes overrides.
186195
self._add_override(

xlsxwriter/feature_property_bag.py

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
###############################################################################
2+
#
3+
# FeaturePropertyBag - A class for writing the Excel XLSX featurePropertyBag.xml
4+
# file.
5+
#
6+
# SPDX-License-Identifier: BSD-2-Clause
7+
#
8+
# Copyright (c) 2013-2025, John McNamara, [email protected]
9+
#
10+
11+
# Package imports.
12+
from . import xmlwriter
13+
14+
15+
class FeaturePropertyBag(xmlwriter.XMLwriter):
16+
"""
17+
A class for writing the Excel XLSX FeaturePropertyBag file.
18+
19+
20+
"""
21+
22+
###########################################################################
23+
#
24+
# Private API.
25+
#
26+
###########################################################################
27+
28+
def _assemble_xml_file(self):
29+
# Assemble and write the XML file.
30+
31+
# Write the XML declaration.
32+
self._xml_declaration()
33+
34+
# Write the FeaturePropertyBags element.
35+
self._write_feature_property_bags()
36+
37+
# Write the Checkbox bag element.
38+
self._write_checkbox_bag()
39+
40+
# Write the XFControls bag element.
41+
self._write_xf_control_bag()
42+
43+
# Write the XFComplement bag element.
44+
self._write_xf_compliment_bag()
45+
46+
# Write the XFComplements bag element.
47+
self._write_xf_compliments_bag()
48+
49+
self._xml_end_tag("FeaturePropertyBags")
50+
51+
# Close the file.
52+
self._xml_close()
53+
54+
###########################################################################
55+
#
56+
# XML methods.
57+
#
58+
###########################################################################
59+
60+
def _write_feature_property_bags(self):
61+
# Write the <FeaturePropertyBags> element.
62+
63+
xmlns = (
64+
"http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"
65+
)
66+
67+
attributes = [("xmlns", xmlns)]
68+
69+
self._xml_start_tag("FeaturePropertyBags", attributes)
70+
71+
def _write_checkbox_bag(self):
72+
# Write the Checkbox <bag> element.
73+
attributes = [("type", "Checkbox")]
74+
75+
self._xml_empty_tag("bag", attributes)
76+
77+
def _write_xf_control_bag(self):
78+
# Write the XFControls<bag> element.
79+
attributes = [("type", "XFControls")]
80+
81+
self._xml_start_tag("bag", attributes)
82+
83+
# Write the bagId element.
84+
self._write_bag_id("CellControl", 0)
85+
86+
self._xml_end_tag("bag")
87+
88+
def _write_xf_compliment_bag(self):
89+
# Write the XFComplement <bag> element.
90+
attributes = [("type", "XFComplement")]
91+
92+
self._xml_start_tag("bag", attributes)
93+
94+
# Write the bagId element.
95+
self._write_bag_id("XFControls", 1)
96+
97+
self._xml_end_tag("bag")
98+
99+
def _write_xf_compliments_bag(self):
100+
# Write the _write_xf_compliment_bag<bag> element.
101+
attributes = [
102+
("type", "XFComplements"),
103+
("extRef", "XFComplementsMapperExtRef"),
104+
]
105+
106+
self._xml_start_tag("bag", attributes)
107+
self._xml_start_tag("a", [("k", "MappedFeaturePropertyBags")])
108+
109+
self._write_bag_id("", 2)
110+
111+
self._xml_end_tag("a")
112+
self._xml_end_tag("bag")
113+
114+
def _write_bag_id(self, key, bag_id):
115+
# Write the <bagId> element.
116+
attributes = []
117+
118+
if key:
119+
attributes = [("k", key)]
120+
121+
self._xml_data_element("bagId", bag_id, attributes)

xlsxwriter/format.py

+20
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def __init__(self, properties=None, xf_indices=None, dxf_indices=None):
109109
self.font_only = 0
110110

111111
self.quote_prefix = False
112+
self.checkbox = False
112113

113114
# Convert properties in the constructor to method calls.
114115
for key, value in properties.items():
@@ -660,6 +661,24 @@ def set_quote_prefix(self, quote_prefix=True):
660661
"""
661662
self.quote_prefix = quote_prefix
662663

664+
def set_checkbox(self, checkbox=True):
665+
"""
666+
Set the Format property to show a checkbox in a cell.
667+
668+
This format property can be used with a cell that contains a boolean
669+
value to display it as a checkbox. This property isn't required very
670+
often and it is generally easier to create a checkbox using the
671+
``worksheet.insert_checkbox()`` method.
672+
673+
Args:
674+
checkbox: Default is True, turns property on.
675+
676+
Returns:
677+
Nothing.
678+
679+
"""
680+
self.checkbox = checkbox
681+
663682
###########################################################################
664683
#
665684
# Internal Format properties. These aren't documented since they are
@@ -1050,6 +1069,7 @@ def _get_format_key(self):
10501069
self._get_alignment_key(),
10511070
self.num_format,
10521071
self.locked,
1072+
self.checkbox,
10531073
self.quote_prefix,
10541074
self.hidden,
10551075
)

xlsxwriter/packager.py

+22
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .core import Core
2222
from .custom import Custom
2323
from .exceptions import EmptyChartSeries
24+
from .feature_property_bag import FeaturePropertyBag
2425
from .metadata import Metadata
2526
from .relationships import Relationships
2627
from .rich_value import RichValue
@@ -160,6 +161,7 @@ def _create_package(self):
160161
self._write_core_file()
161162
self._write_app_file()
162163
self._write_metadata_file()
164+
self._write_feature_bag_property()
163165
self._write_rich_value_files()
164166

165167
return self.filenames
@@ -369,6 +371,18 @@ def _write_metadata_file(self):
369371
metadata._set_xml_writer(self._filename("xl/metadata.xml"))
370372
metadata._assemble_xml_file()
371373

374+
def _write_feature_bag_property(self):
375+
# Write the featurePropertyBag.xml file.
376+
if not self.workbook.has_checkboxes:
377+
return
378+
379+
property_bag = FeaturePropertyBag()
380+
381+
property_bag._set_xml_writer(
382+
self._filename("xl/featurePropertyBag/featurePropertyBag.xml")
383+
)
384+
property_bag._assemble_xml_file()
385+
372386
def _write_rich_value_files(self):
373387

374388
if not self.workbook.embedded_images.has_images():
@@ -472,6 +486,10 @@ def _write_content_types_file(self):
472486
if self.workbook.has_metadata:
473487
content._add_metadata()
474488

489+
# Add the metadata file if present.
490+
if self.workbook._has_checkboxes():
491+
content._add_feature_bag_property()
492+
475493
# Add the RichValue file if present.
476494
if self.workbook.embedded_images.has_images():
477495
content._add_rich_value()
@@ -595,6 +613,10 @@ def _write_workbook_rels_file(self):
595613
if self.workbook.embedded_images.has_images():
596614
rels._add_rich_value_relationship()
597615

616+
# Add the checkbox/FeaturePropertyBag file if present.
617+
if self.workbook.has_checkboxes:
618+
rels._add_feature_bag_relationship()
619+
598620
rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels"))
599621
rels._assemble_xml_file()
600622

0 commit comments

Comments
 (0)