{"id":1684,"date":"2023-07-06T14:04:56","date_gmt":"2023-07-06T14:04:56","guid":{"rendered":"https:\/\/www.pyxll.com\/blog\/?p=1684"},"modified":"2023-07-06T18:13:18","modified_gmt":"2023-07-06T18:13:18","slug":"custom-fill-formatter","status":"publish","type":"post","link":"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/","title":{"rendered":"Writing a Custom Excel Function Formatter: &#8220;Fill Down&#8221; Formatting"},"content":{"rendered":"\n<p>PyXLL makes returning arrays and DataFrames of data from Python to Excel simple. Python functions can return entire tables of data directly into Excel as user defined function (or UDF). All we require to do that is PyXLL&#8217;s @xl_func decorator.<\/p>\n\n\n\n<p style=\"font-style:italic;font-weight:400\">Python Array Functions in Excel: <a href=\"https:\/\/www.pyxll.com\/docs\/userguide\/udfs\/arrayfuncs.html\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.pyxll.com\/docs\/userguide\/udfs\/arrayfuncs.html<\/a><\/p>\n\n\n\n<p>But what if you want to format your data in Excel? Often you will want to return columns in a specific format, as percentages or with date formatting, for example. PyXLL has a solution to that! Use a <em>formatter<\/em> when declaring your worksheet function and PyXLL will format the result when returning Excel.<\/p>\n\n\n\n<p style=\"font-style:italic;font-weight:400\">Cell Formatting: <a href=\"https:\/\/www.pyxll.com\/docs\/userguide\/formatting\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.pyxll.com\/docs\/userguide\/formatting\/index.html<\/a><\/p>\n\n\n\n<p>PyXLL includes several standard formatters for common cases such as formatting DataFrames. Formatters are also composable. Try adding them together if you want to apply multiple formatters at the same time.<\/p>\n\n\n\n<p>And if none of the standard formatters, or combinations of standard formatters, are capable of doing what you need? Write a custom formatter! In the rest of this article we will explore how to write a custom formatter.<\/p>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_83 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<label for=\"ez-toc-cssicon-toggle-item-69ffdca8bdca8\" class=\"ez-toc-cssicon-toggle-label\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/label><input type=\"checkbox\"  id=\"ez-toc-cssicon-toggle-item-69ffdca8bdca8\"  aria-label=\"Toggle\" \/><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#The_Challenge_Writing_a_%E2%80%9CFill_Down%E2%80%9D_Function_Formatter\" >The Challenge: Writing a &#8220;Fill Down&#8221; Function Formatter<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#Starting_Off_Writing_a_test_Excel_dynamic_array_function\" >Starting Off: Writing a test Excel dynamic array function<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#Lets_get_started_Write_a_custom_function_formatter\" >Let&#8217;s get started! Write a custom function formatter<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#The_main_bit_Writing_our_%E2%80%9Cfill_down%E2%80%9D_formatter\" >The main bit: Writing our &#8220;fill down&#8221; formatter<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#Improvement_1_Getting_the_header_right\" >Improvement 1: Getting the header right<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#Improvement_2_Clearing_old_formatting\" >Improvement 2: Clearing old formatting<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#The_Final_Code_Our_complete_%E2%80%98Fill_Down_custom_Excel_formatter\" >The Final Code: Our complete &#8216;Fill Down&#8217; custom Excel formatter!<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/#References\" >References<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\" id=\"h-the-challenge-writing-a-fill-down-function-formatter\"><span class=\"ez-toc-section\" id=\"The_Challenge_Writing_a_%E2%80%9CFill_Down%E2%80%9D_Function_Formatter\"><\/span>The Challenge: Writing a &#8220;Fill Down&#8221; Function Formatter<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>We will have a Python function that returns a table of data to Excel as a user defined function. This table will have a row of column headers as the first row, followed by a table of data. As the inputs to the function change, the size of the table will also change.<\/p>\n\n\n\n<p>We want to allow the Excel user to be able to format the headers and the data in Excel. When the table updates or changes size, we want the formatting the user has applied to be copied (or filled) to the rest of the table.<\/p>\n\n\n\n<p>Using any of the standard formatters, the formatting specified by the formatter would overwrite any formatting set by the user when the table updates. If we don&#8217;t use a formatter, when the table increases in size the user has to apply the formatting to the newly added rows.<\/p>\n\n\n\n<p>To solve this, we will write a customer formatter that copies any formatting set by the user to the entire table in Excel. If the table expands, we want the formatter to copy to existing formatting to the new range. If the table contracts, the we want to clear the formatting from the cells where the previous table was.<\/p>\n\n\n\n<p style=\"font-style:italic;font-weight:400\">Custom Formatters: <a href=\"https:\/\/www.pyxll.com\/docs\/userguide\/formatting\/customformatters.html\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.pyxll.com\/docs\/userguide\/formatting\/customformatters.html<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-starting-off-writing-a-test-excel-dynamic-array-function\"><span class=\"ez-toc-section\" id=\"Starting_Off_Writing_a_test_Excel_dynamic_array_function\"><\/span>Starting Off: Writing a test Excel dynamic array function<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Before we write our customer formatter we need a test function in order to try it out. For this we&#8217;ll write a function that returns a list of lists (list of rows of values), where the number of rows and columns are inputs to our function.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom pyxll import xl_func\n\n@xl_func(&quot;int rows, int columns: var&#x5B;]&#x5B;]&quot;, auto_resize=True)\ndef fill_formatter_example(rows, columns):\n    &quot;&quot;&quot;Function that returns a table of data for\n    the purpose of demonstrating the FillFormatter class.\n    &quot;&quot;&quot;\n    header = &#x5B;f&quot;COL{i+1}&quot; for i in range(columns)]\n    rows = &#x5B;\n        &#x5B;c + r * columns for c in range(columns)]\n        for r in range(rows)\n    ]\n    return &#x5B;header] + rows\n\n<\/pre><\/div>\n\n\n<p>The above function returns a list of data and when called from Excel looks as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image.png\"><img decoding=\"async\" width=\"1024\" height=\"518\" data-attachment-id=\"1690\" data-permalink=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/image-19\/\" data-orig-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image.png\" data-orig-size=\"1168,591\" data-comments-opened=\"0\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"image\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1024x518.png\" src=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1024x518.png\" alt=\"\" class=\"wp-image-1690\" srcset=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1024x518.png 1024w, https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-300x152.png 300w, https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-768x389.png 768w, https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image.png 1168w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>No formatting yet, it looks very plain!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-let-s-get-started-write-a-custom-function-formatter\"><span class=\"ez-toc-section\" id=\"Lets_get_started_Write_a_custom_function_formatter\"><\/span>Let&#8217;s get started! Write a custom function formatter<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>To add formatting to our function we could use one of PyXLL&#8217;s existing formatters, but here we&#8217;re going to write our own formatter.<\/p>\n\n\n\n<p>To write a customer formatter we create a new Python class derived from PyXLL&#8217;s &#8220;Formatter&#8221; class. The Formatter class has a few methods we can override to customize how the formatting works.<\/p>\n\n\n\n<p>In our case, we&#8217;re going to override the <a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#pyxll.Formatter.clear\" target=\"_blank\" rel=\"noreferrer noopener\">Formatter.clear<\/a> and the <a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#pyxll.Formatter.apply\" target=\"_blank\" rel=\"noreferrer noopener\">Formatter.apply<\/a> methods. The <a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#pyxll.Formatter.clear\" target=\"_blank\" rel=\"noreferrer noopener\">Formatter.clear<\/a> method gets called before <a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#pyxll.Formatter.apply\" target=\"_blank\" rel=\"noreferrer noopener\">Formatter.apply<\/a> to clear any previous styles. The <a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#pyxll.Formatter.apply\" target=\"_blank\" rel=\"noreferrer noopener\">Formatter.apply<\/a> method is what applies the formatting.<\/p>\n\n\n\n<p>For simple styles we could use the <a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#pyxll.Formatter.apply_style\" target=\"_blank\" rel=\"noreferrer noopener\">Formatter.apply_style<\/a> method. This is a convenient way to apply a style, but it cannot be used to copy and paste styles as we will need later. Instead, we will use the Excel Object Model API directly. This is the same API that you can use in VBA to automate Excel, and the entire API is also available to Python.<\/p>\n\n\n\n<p style=\"font-style:italic;font-weight:400\">Python as a VBA Replacement: <a href=\"https:\/\/www.pyxll.com\/docs\/userguide\/vba.html\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.pyxll.com\/docs\/userguide\/vba.html<\/a><\/p>\n\n\n\n<p>To start, let&#8217;s just override the apply method on a new Formatter class:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom pyxll import Formatter\n\nclass FillFormatter(Formatter):\n    &quot;&quot;&quot;Our custom formatter, based on pyxll.Formatter&quot;&quot;&quot;\n\n    # Our overridden apply method. This takes several additional arguments, but\n    # we&#039;re not going to use them in this example so *args and **kwargs is used here.\n    def apply(self, cell, *args, **kwargs):\n        # &#039;cell&#039; is a pyxll.XLCell object corresponding to the range where we need to apply our style.\n        #\n        # We could use Formatter.apply_style here, but instead we&#039;ll use the Excel Object Model\n        # and convert the cell to an Excel &quot;Range&quot; COM object.\n        #\n        # PyXLL can use different packages for working with COM objects. &quot;win32com&quot; is the default,\n        # but I have specified it explicitly here.\n        xl_range = cell.to_range(com_package=&quot;win32com&quot;)\n\n        # xl_range is an Excel &quot;Range&quot; object, which is the same as a VBA Range object.\n        # We can use this to update the style of the Range.\n        # Using the VBA macro recorder is a good way to find out how to do what we need,\n        # the generated VBA code can be translated to Python quite easily!\n        #\n        # For now, we&#039;ll just set the interior cell color to yellow.\n        xl_range.Interior.Color = 0x00FFFF\n\n<\/pre><\/div>\n\n\n<p>Next we need to add our new formatter to the Excel function we wrote in the previous section:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n# Create an instance of our custom formatter\nmy_formatter = FillFormatter()\n\n# Pass it to @xl_func using the &#039;formatter&#039; option\n@xl_func(&quot;int rows, int columns: var&#x5B;]&#x5B;]&quot;, auto_resize=True, formatter=my_formatter)\ndef fill_formatter_example(rows, columns):\n    ... same as above ...\n<\/pre><\/div>\n\n\n<p>Now when we call the function in Excel our formatter applies the yellow interior color automatically! If the size of the array changes, the formatter updates the style automatically.<\/p>\n\n\n\n<p>If you don&#8217;t see the style being applied, check your PyXLL log file for errors.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1.png\"><img decoding=\"async\" width=\"1024\" height=\"558\" data-attachment-id=\"1695\" data-permalink=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/image-1-7\/\" data-orig-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1.png\" data-orig-size=\"1168,636\" data-comments-opened=\"0\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"image-1\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1-1024x558.png\" src=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1-1024x558.png\" alt=\"\" class=\"wp-image-1695\" srcset=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1-1024x558.png 1024w, https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1-300x163.png 300w, https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1-768x418.png 768w, https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/image-1.png 1168w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-main-bit-writing-our-fill-down-formatter\"><span class=\"ez-toc-section\" id=\"The_main_bit_Writing_our_%E2%80%9Cfill_down%E2%80%9D_formatter\"><\/span>The main bit: Writing our &#8220;fill down&#8221; formatter<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>Now we understand how to write a custom formatter, and that we can use the entire Excel Object Model in Python in just the same way as from VBA, we&#8217;re ready to write our fill formatter.<\/p>\n\n\n\n<p>What we want to do is to copy the style from the previous range to the new range whenever the array being formatted changes size.<\/p>\n\n\n\n<p>You might have seen that with the code above, when the array shrinks the yellow formatting is automatically cleared from where the array was. The &#8220;clear&#8221; method of the formatter is what does that. In our case, we don&#8217;t want to clear the formatting as we&#8217;re going to copy that to the new range. So, we will override the clear method and save the range that should be cleared to use in the apply method. Since the apply method is always called directly after the clear method, we can store this temporarily between these two calls.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom pyxll import Formatter\n\nclass FillFormatter(Formatter):\n    &quot;&quot;&quot;Our custom formatter, based on pyxll.Formatter&quot;&quot;&quot;\n\n    def __init__(self):\n        self.__prev_cell = None\n\n    def clear(self, cell, *args, **kwargs):\n        # Instead of clearing any formatting keep a reference to the cell that should be cleared\n        self.__prev_cell = cell\n\n    def apply(self, cell, *args, **kwargs):        \n        # Get the previous cell and clear the temporary reference to it\n        prev_cell = self.__prev_cell\n        self.__prev_cell = None\n\n        # Here we want to copy the style from &quot;prev_cell&quot; to &quot;cell&quot;\n        # TODO!\n<\/pre><\/div>\n\n\n<p>We saw in the last section that we can get an Excel &#8220;Range&#8221; object from the cell. Using that, we have access to all of the same properties and methods that are also available in VBA.<\/p>\n\n\n\n<p>Specifically, we can use <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.copy\" target=\"_blank\" rel=\"noreferrer noopener\">Range.Copy<\/a> and <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.pastespecial\" target=\"_blank\" rel=\"noreferrer noopener\">Range.PasteSpecial<\/a> to copy and paste the formatting from one range to the other.<\/p>\n\n\n\n<p>To call <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.pastespecial\" target=\"_blank\" rel=\"noreferrer noopener\">Range.PasteSpecicial<\/a> we need some Excel constants, which we get from the &#8220;win32com.client.constants&#8221; module.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom pyxll import Formatter\nfrom win32com.client import constants\n\nclass FillFormatter(Formatter):\n    &quot;&quot;&quot;Our custom formatter, based on pyxll.Formatter&quot;&quot;&quot;\n\n    def __init__(self):\n        self.__prev_cell = None\n\n    def clear(self, cell, *args, **kwargs):\n        # Instead of clearing any formatting keep a reference to the cell that should be cleared\n        self.__prev_cell = cell\n\n    def apply(self, cell, *args, **kwargs):        \n        # Get the previous cell and clear the temporary reference to it\n        prev_cell = self.__prev_cell\n        self.__prev_cell = None\n\n        # Here we copy the style from &quot;prev_cell&quot; to &quot;cell&quot;\n        prev_range = prev_cell.to_range(com_package=&quot;win32com&quot;)\n        new_range = cell.to_range(com_package=&quot;win32com&quot;)\n\n        prev_range.Copy()\n        new_range.PasteSpecial(Paste=constants.xlPasteFormats, Operation=constants.xlNone)\n\n        # End copy\/paste mode.\n        new_range.Application.CutCopyMode = False\n<\/pre><\/div>\n\n\n<p>The last line above is to end Excel&#8217;s copy\/paste mode. Without that line, a dotted border will be left around the cells being copied as there is when you manually copy a range.<\/p>\n\n\n\n<p>Using this formatter, if we apply formatting to the range and then change the size of the returned array the formatting gets copied to the entire range.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><a href=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/ltViBSpS02.gif\"><img decoding=\"async\" data-attachment-id=\"1706\" data-permalink=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/ltvibsps02\/\" data-orig-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/ltViBSpS02.gif\" data-orig-size=\"1102,689\" data-comments-opened=\"0\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ltViBSpS02\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/ltViBSpS02-1024x640.gif\" src=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/ltViBSpS02.gif\" alt=\"\" class=\"wp-image-1706\" width=\"827\" height=\"517\"\/><\/a><\/figure>\n\n\n\n<p>So close, but not quite right! Above you can see that the style from the headers gets copied to as the range expands. We need to make some changes so that the header is treated separately from the rest of the table.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Improvement_1_Getting_the_header_right\"><\/span>Improvement 1: Getting the header right<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>To prevent the header style from being copied when we copy the style from the old range to the new one, we need to get the range excluding the header.<\/p>\n\n\n\n<p>To do that we can make use of the <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.offset\" target=\"_blank\" rel=\"noreferrer noopener\">Range.Offset<\/a> and <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.resize\" target=\"_blank\" rel=\"noreferrer noopener\">Range.Resize<\/a> properties. VBA is unusual in that properties can take arguments. In Python, properties never take arguments. Instead of accessing the properties directly we use the &#8220;GetX&#8221; accessor methods &#8220;Range.GetOffset&#8221; and &#8220;Range.GetResize&#8221; so that we can pass the arguments required.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom pyxll import Formatter\nfrom win32com.client import constants\n\nclass FillFormatter(Formatter):\n    &quot;&quot;&quot;Our custom formatter, based on pyxll.Formatter&quot;&quot;&quot;\n\n    def __init__(self, num_headers=1):\n        self.__num_headers = num_headers\n        self.__prev_cell = None\n\n    def clear(self, cell, *args, **kwargs):\n        # Instead of clearing any formatting keep a reference to the cell that should be cleared\n        self.__prev_cell = cell\n\n    def apply(self, cell, *args, **kwargs):        \n        # Get the previous cell and clear the temporary reference to it\n        prev_cell = self.__prev_cell\n        self.__prev_cell = None\n\n        # Here we copy the style from &quot;prev_cell&quot; to &quot;cell&quot;\n        prev_range = prev_cell.to_range(com_package=&quot;win32com&quot;)\n        new_range = cell.to_range(com_package=&quot;win32com&quot;)\n\n        # Get just the headers as the top N rows of the ranges\n        prev_header = prev_range.GetResize(RowSize=self.__num_headers)\n        new_header = new_range.GetResize(RowSize=self.__num_headers)\n\n        # Copy the header style as there could be more columns than previously\n        prev_header.Copy()\n        new_header.PasteSpecial(Paste=constants.xlPasteFormats, Operation=constants.xlNone)\n\n        # Now get the rest of the ranges, excluding the headers\n        prev_rows = prev_rows.GetOffset(RowOffset=self.__num_headers)\n        prev_rows = prev_rows.GetResize(RowSize=prev_range.Rows.Count - self.__num_headers)\n                    \n        new_rows = new_rows.GetOffset(RowOffset=self.__num_headers)\n        new_rows = new_rows.GetResize(RowSize=new_range.Rows.Count - self.__num_headers)\n\n        # And copy the style for the table below the headers\n        prev_rows.Copy()\n        new_rows.PasteSpecial(Paste=constants.xlPasteFormats, Operation=constants.xlNone)\n\n        # End copy\/paste mode.\n        new_range.Application.CutCopyMode = False\n<\/pre><\/div>\n\n\n<p>Now we&#8217;re getting somewhere! Not only does the header style not get copied to the data below, if the table expands to get wider the header style gets copied across too.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><a href=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/wwwiG2Iec1.gif\"><img decoding=\"async\" data-attachment-id=\"1710\" data-permalink=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/wwwig2iec1\/\" data-orig-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/wwwiG2Iec1.gif\" data-orig-size=\"1307,750\" data-comments-opened=\"0\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"wwwiG2Iec1\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/wwwiG2Iec1-1024x588.gif\" src=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/06\/wwwiG2Iec1.gif\" alt=\"\" class=\"wp-image-1710\" width=\"980\" height=\"563\"\/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-improvement-2-clearing-old-formatting\"><span class=\"ez-toc-section\" id=\"Improvement_2_Clearing_old_formatting\"><\/span>Improvement 2: Clearing old formatting<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>So far our formatter copied the formatting from the previous range to the new range. This works great when the Excel range is expanding, but what about when the range shrinks?<\/p>\n\n\n\n<p>You will remember that previously we added our own override to the <a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#formatter\" target=\"_blank\" rel=\"noreferrer noopener\">Formatter.clear<\/a> method. The &#8220;clear&#8221; method is what would usually clear any formatting for the previous range, before the formatter applies the new format to the current range. Our formatter can&#8217;t clear the previous range as it needs that in order to copy the formatting to the updated range. Instead, we need to modify our formatter to clear just the areas that are no longer needed.<\/p>\n\n\n\n<p>To check if the range is expanding or contracting we can use the <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/Excel.Range.Rows\" target=\"_blank\" rel=\"noreferrer noopener\">Range.Rows.Count<\/a> and <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.columns\" target=\"_blank\" rel=\"noreferrer noopener\">Range.Columns.Count<\/a> properties. If the number of rows or columns in the new range is smaller than the previous range, it is contracting. To get just the part of the range for the formatter to clear, we can use <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.offset\" target=\"_blank\" rel=\"noreferrer noopener\">Range.Offset<\/a> and <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.resize\" target=\"_blank\" rel=\"noreferrer noopener\">Range.Resize<\/a>.<\/p>\n\n\n\n<p style=\"font-style:italic;font-weight:300\">Note: Range.Offset and Range.Resize are <em>properties.<\/em> VBA has a rather strange feature where properties can take arguments, just like methods! In Python, properties never take arguments. When calling these properties with arguments in Python we have to use their accessor methods, GetOffset and GetResize instead. In general, you can call all Excel Object Model properties with arguments from Python via an accessor method. Accessor methods have the same name as the property with a &#8220;Get&#8221; prefix.<\/p>\n\n\n\n<p>Once we have identified that the range is contracting, and we have the sub-range to clear using Range.GetOffset and Range.GetResize, we call <a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/excel.range.clearformats\" target=\"_blank\" rel=\"noreferrer noopener\">Range.ClearFormats<\/a> to clear the formatting.<\/p>\n\n\n\n<p>Our updated &#8220;apply&#8221; method now looks as follows:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n    def apply(self, cell, *args, **kwargs):\n        # Reset prev_cell ahead of any future calls.\n        prev_cell = self.__prev_cell\n        self.__prev_cell = None\n\n        # Get the previous and new Range objects from the cells as win32com objects.\n        prev_range = prev_cell.to_range(com_package=&quot;win32com&quot;)\n        new_range = cell.to_range(com_package=&quot;win32com&quot;)\n        \n        # Check if the range is shrinking and clear the formatting if it is.\n        if prev_range.Rows.Count &gt; new_range.Rows.Count:\n            num_rows = prev_range.Rows.Count - new_range.Rows.Count\n            rows = prev_range.GetOffset(RowOffset=new_range.Rows.Count)\n            rows = rows.GetResize(RowSize=num_rows)\n            rows.ClearFormats()\n        \n        if prev_range.Columns.Count &gt; new_range.Columns.Count:\n            num_cols = prev_range.Columns.Count - new_range.Columns.Count\n            cols = prev_range.GetOffset(ColumnOffset=new_range.Columns.Count)\n            cols = cols.GetResize(ColumnSize=num_cols)\n            cols.ClearFormats()\n\n        # .... as previously ....\n<\/pre><\/div>\n\n\n<p>With this change, when reducing the number of rows or columns, the formatter automatically clears the formatting from the cells no longer in use.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/07\/bLrok1sYzj.gif\"><img decoding=\"async\" width=\"813\" height=\"564\" data-attachment-id=\"1718\" data-permalink=\"https:\/\/www.pyxll.com\/blog\/custom-fill-formatter\/blrok1syzj\/\" data-orig-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/07\/bLrok1sYzj.gif\" data-orig-size=\"813,564\" data-comments-opened=\"0\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"bLrok1sYzj\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/07\/bLrok1sYzj.gif\" src=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/07\/bLrok1sYzj.gif\" alt=\"\" class=\"wp-image-1718\"\/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-final-code-our-complete-fill-down-custom-excel-formatter\"><span class=\"ez-toc-section\" id=\"The_Final_Code_Our_complete_%E2%80%98Fill_Down_custom_Excel_formatter\"><\/span>The Final Code: Our complete &#8216;Fill Down&#8217; custom Excel formatter!<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p>The complete Python code accompanying this post can be found on GitHub here <a href=\"https:\/\/github.com\/pyxll\/pyxll-examples\/tree\/master\/formatters\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/pyxll\/pyxll-examples\/tree\/master\/formatters<\/a>. <\/p>\n\n\n\n<p>The FillFormatter class available on GitHub is almost the same as the code we&#8217;ve worked through in this post, but with some small additions to handle some corner cases we&#8217;ve not considered above.<\/p>\n\n\n\n<p>With what you&#8217;ve learned in this post, now you should be able to see that any formatting you want to achieve for your user defined Excel array functions is possible!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-references\"><span class=\"ez-toc-section\" id=\"References\"><\/span>References<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.pyxll.com\/docs\/userguide\/formatting\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">Formatting for Excel User Defined Functions with PyXLL<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.pyxll.com\/docs\/api\/formatting.html#pyxll.Formatter\" target=\"_blank\" rel=\"noreferrer noopener\">API Reference for base Formatter class<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.pyxll.com\/docs\/userguide\/vba.html\" target=\"_blank\" rel=\"noreferrer noopener\">Accessing Excel&#8217;s Object Model (like VBA) from Python<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/overview\/excel\" target=\"_blank\" rel=\"noreferrer noopener\">Excel Object Model Reference Guide<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>PyXLL makes returning arrays and DataFrames of data from Python to Excel simple. Python functions can return entire tables of data directly into Excel as user defined function (or UDF). All we require to do that is PyXLL&#8217;s @xl_func decorator. Python Array Functions in Excel: https:\/\/www.pyxll.com\/docs\/userguide\/udfs\/arrayfuncs.html But what if you want to format your data<\/p>\n","protected":false},"author":1,"featured_media":1721,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[12,2,272],"tags":[6,648,8],"class_list":["post-1684","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","category-pyxll","category-visualization","tag-excel","tag-formatting","tag-pyxll"],"acf":[],"jetpack_featured_media_url":"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2023\/07\/WI71KN7Q9p.gif","jetpack_shortlink":"https:\/\/wp.me\/p7l3LP-ra","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/1684","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/comments?post=1684"}],"version-history":[{"count":1,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/1684\/revisions"}],"predecessor-version":[{"id":1727,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/1684\/revisions\/1727"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/media\/1721"}],"wp:attachment":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/media?parent=1684"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/categories?post=1684"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/tags?post=1684"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}