Skip to content

Commit

Permalink
checkbox: initial checkbox support
Browse files Browse the repository at this point in the history
Feature request #1092
  • Loading branch information
jmcnamara committed Jan 26, 2025
1 parent 3524002 commit 8c66333
Showing 28 changed files with 809 additions and 11 deletions.
6 changes: 6 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Release 3.2.2 - January XX 2025
--------------------------------

* Added support for checkboxes xxx


Release 3.2.1 - January 22 2025
--------------------------------

Binary file added dev/docs/source/_images/checkbox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dev/docs/source/_images/checkbox_boolean.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added dev/docs/source/_images/excel_checkbox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions dev/docs/source/example_checkbox.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. SPDX-License-Identifier: BSD-2-Clause
Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
.. _ex_checkbox:

Example: Inserting a checkbox in a Worksheet
============================================

An example of adding checkbox boolean values to a worksheet.

.. image:: _images/checkbox.png

.. literalinclude:: ../../../examples/checkbox.py
1 change: 1 addition & 0 deletions dev/docs/source/examples.rst
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@ directory of the XlsxWriter distribution.
example_headers_footers.rst
example_panes.rst
example_tables.rst
example_checkbox.rst
example_user_types1.rst
example_user_types2.rst
example_user_types3.rst
17 changes: 17 additions & 0 deletions dev/docs/source/format.rst
Original file line number Diff line number Diff line change
@@ -278,6 +278,11 @@ properties that can be applied and the equivalent object method:
+------------+------------------+----------------------+------------------------------+
| | Right color | ``'right_color'`` | :func:`set_right_color()` |
+------------+------------------+----------------------+------------------------------+
| Misc. | Cell border | ``'quote_prefix'`` | :func:`set_quote_prefix()` |
+------------+------------------+----------------------+------------------------------+
| | Checkbox format | ``'checkbox'`` | :func:`set_checkbox()` |
+------------+------------------+----------------------+------------------------------+


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

@@ -1211,3 +1216,15 @@ Set the quote prefix property of a format to ensure a string is treated as a
string after editing. This is the same as prefixing the string with a single
quote in Excel. You don't need to add the quote to the string but you do need
to add the format.


format.set_checkbox()
---------------------

.. py:function:: set_checkbox()
Turn on the checkbox property for the format.

This format property can be used with a cell that contains a boolean value to
display it as a checkbox. This property isn't required very often and it is
generally easier to create a checkbox using the :func:`insert_checkbox` method.
55 changes: 55 additions & 0 deletions dev/docs/source/worksheet.rst
Original file line number Diff line number Diff line change
@@ -1724,6 +1724,61 @@ See :ref:`object_position` for more detailed information about the positioning
and scaling of images within a worksheet.


worksheet.insert_checkbox()
---------------------------

.. py:function:: insert_checkbox(row, col, boolean[, cell_format])
Insert a boolean checkbox in a worksheet cell.

:param row: The cell row (zero indexed).
:param col: The cell column (zero indexed).
:param boolean: The boolean value to display as a checkbox.
:param cell_format: Optional Format object.
:type row: int
:type col: int
:type boolean: bool
:type cell_format: :ref:`Format <format>`

:returns: 0: Success.
:returns: -1: Row or column is out of worksheet bounds.

Checkboxes are a new feature added to `Excel in 2024`_. They are a way of
displaying a boolean value as a checkbox in a cell. The underlying value is
still an Excel ``TRUE/FALSE`` boolean value and can be used in formulas and in
references.

.. image:: _images/excel_checkbox.png

.. _Excel in 2024: https://techcommunity.microsoft.com/blog/excelblog/introducing-checkboxes-in-excel/4173561

The ``insert_checkbox()`` method can be used to replicate this behavior:

.. literalinclude:: ../../../examples/checkbox.py
:lines: 18-36

.. image:: _images/checkbox.png

See :ref:`ex_checkbox` for the complete example.

The checkbox feature is only available in Excel versions from 2024 and later. In
older versions the value will be displayed as a standard Excel ``TRUE`` or
``FALSE`` boolean. In fact Excel stores a checkbox as a normal boolean but with
a special format. If required you can make use of this property to create a
checkbox with :func:`write_boolean` and a cell format that has the
:func:`set_checkbox` property set::

cell_format = workbook.add_format({"checkbox": True})

worksheet.write(2, 2, False, cell_format)
worksheet.write(3, 2, True, cell_format)

.. image:: _images/checkbox_boolean.png

This latter method isn't required very often but it can be occasionally useful
if you are dealing with boolean values in a dataframe.


worksheet.insert_button()
-------------------------

39 changes: 39 additions & 0 deletions examples/checkbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
##############################################################################
#
# An example of adding checkbox boolean values to a worksheet using the the
# XlsxWriter Python module.
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
#
import xlsxwriter

# Create a new Excel file object.
workbook = xlsxwriter.Workbook("checkbox.xlsx")

# Add a worksheet to the workbook.
worksheet = workbook.add_worksheet()

# Create some formats to use in the worksheet.
bold = workbook.add_format({"bold": True})
light_red = workbook.add_format({"bg_color": "#FFC7CE"})
light_green = workbook.add_format({"bg_color": "#C6EFCE"})

# Set the column width for clarity.
worksheet.set_column(0, 0, 30)

# Write some descriptions.
worksheet.write(1, 0, "Some simple checkboxes:", bold)
worksheet.write(4, 0, "Some checkboxes with cell formats:", bold)

# Insert some boolean checkboxes to the worksheet.
worksheet.insert_checkbox(1, 1, False)
worksheet.insert_checkbox(2, 1, True)

# Insert some checkboxes with cell formats.
worksheet.insert_checkbox(4, 1, False, light_red)
worksheet.insert_checkbox(5, 1, True, light_green)

# Close the workbook.
workbook.close()
9 changes: 9 additions & 0 deletions xlsxwriter/contenttypes.py
Original file line number Diff line number Diff line change
@@ -181,6 +181,15 @@ def _add_metadata(self):
("/xl/metadata.xml", APP_DOCUMENT + "spreadsheetml.sheetMetadata+xml")
)

def _add_feature_bag_property(self):
# Add the featurePropertyBag file to the ContentTypes overrides.
self._add_override(
(
"/xl/featurePropertyBag/featurePropertyBag.xml",
"application/vnd.ms-excel.featurepropertybag+xml",
)
)

def _add_rich_value(self):
# Add the richValue files to the ContentTypes overrides.
self._add_override(
121 changes: 121 additions & 0 deletions xlsxwriter/feature_property_bag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
###############################################################################
#
# FeaturePropertyBag - A class for writing the Excel XLSX featurePropertyBag.xml
# file.
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
#

# Package imports.
from . import xmlwriter


class FeaturePropertyBag(xmlwriter.XMLwriter):
"""
A class for writing the Excel XLSX FeaturePropertyBag file.
"""

###########################################################################
#
# Private API.
#
###########################################################################

def _assemble_xml_file(self):
# Assemble and write the XML file.

# Write the XML declaration.
self._xml_declaration()

# Write the FeaturePropertyBags element.
self._write_feature_property_bags()

# Write the Checkbox bag element.
self._write_checkbox_bag()

# Write the XFControls bag element.
self._write_xf_control_bag()

# Write the XFComplement bag element.
self._write_xf_compliment_bag()

# Write the XFComplements bag element.
self._write_xf_compliments_bag()

self._xml_end_tag("FeaturePropertyBags")

# Close the file.
self._xml_close()

###########################################################################
#
# XML methods.
#
###########################################################################

def _write_feature_property_bags(self):
# Write the <FeaturePropertyBags> element.

xmlns = (
"http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"
)

attributes = [("xmlns", xmlns)]

self._xml_start_tag("FeaturePropertyBags", attributes)

def _write_checkbox_bag(self):
# Write the Checkbox <bag> element.
attributes = [("type", "Checkbox")]

self._xml_empty_tag("bag", attributes)

def _write_xf_control_bag(self):
# Write the XFControls<bag> element.
attributes = [("type", "XFControls")]

self._xml_start_tag("bag", attributes)

# Write the bagId element.
self._write_bag_id("CellControl", 0)

self._xml_end_tag("bag")

def _write_xf_compliment_bag(self):
# Write the XFComplement <bag> element.
attributes = [("type", "XFComplement")]

self._xml_start_tag("bag", attributes)

# Write the bagId element.
self._write_bag_id("XFControls", 1)

self._xml_end_tag("bag")

def _write_xf_compliments_bag(self):
# Write the _write_xf_compliment_bag<bag> element.
attributes = [
("type", "XFComplements"),
("extRef", "XFComplementsMapperExtRef"),
]

self._xml_start_tag("bag", attributes)
self._xml_start_tag("a", [("k", "MappedFeaturePropertyBags")])

self._write_bag_id("", 2)

self._xml_end_tag("a")
self._xml_end_tag("bag")

def _write_bag_id(self, key, bag_id):
# Write the <bagId> element.
attributes = []

if key:
attributes = [("k", key)]

self._xml_data_element("bagId", bag_id, attributes)
20 changes: 20 additions & 0 deletions xlsxwriter/format.py
Original file line number Diff line number Diff line change
@@ -109,6 +109,7 @@ def __init__(self, properties=None, xf_indices=None, dxf_indices=None):
self.font_only = 0

self.quote_prefix = False
self.checkbox = False

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

def set_checkbox(self, checkbox=True):
"""
Set the Format property to show a checkbox in a cell.
This format property can be used with a cell that contains a boolean
value to display it as a checkbox. This property isn't required very
often and it is generally easier to create a checkbox using the
``worksheet.insert_checkbox()`` method.
Args:
checkbox: Default is True, turns property on.
Returns:
Nothing.
"""
self.checkbox = checkbox

###########################################################################
#
# Internal Format properties. These aren't documented since they are
@@ -1050,6 +1069,7 @@ def _get_format_key(self):
self._get_alignment_key(),
self.num_format,
self.locked,
self.checkbox,
self.quote_prefix,
self.hidden,
)
22 changes: 22 additions & 0 deletions xlsxwriter/packager.py
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
from .core import Core
from .custom import Custom
from .exceptions import EmptyChartSeries
from .feature_property_bag import FeaturePropertyBag
from .metadata import Metadata
from .relationships import Relationships
from .rich_value import RichValue
@@ -160,6 +161,7 @@ def _create_package(self):
self._write_core_file()
self._write_app_file()
self._write_metadata_file()
self._write_feature_bag_property()
self._write_rich_value_files()

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

def _write_feature_bag_property(self):
# Write the featurePropertyBag.xml file.
if not self.workbook.has_checkboxes:
return

property_bag = FeaturePropertyBag()

property_bag._set_xml_writer(
self._filename("xl/featurePropertyBag/featurePropertyBag.xml")
)
property_bag._assemble_xml_file()

def _write_rich_value_files(self):

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

# Add the metadata file if present.
if self.workbook._has_checkboxes():
content._add_feature_bag_property()

# Add the RichValue file if present.
if self.workbook.embedded_images.has_images():
content._add_rich_value()
@@ -595,6 +613,10 @@ def _write_workbook_rels_file(self):
if self.workbook.embedded_images.has_images():
rels._add_rich_value_relationship()

# Add the checkbox/FeaturePropertyBag file if present.
if self.workbook.has_checkboxes:
rels._add_feature_bag_relationship()

rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels"))
rels._assemble_xml_file()

Loading

0 comments on commit 8c66333

Please sign in to comment.