{"id":1355,"date":"2020-04-17T15:13:58","date_gmt":"2020-04-17T15:13:58","guid":{"rendered":"https:\/\/www.pyxll.com\/blog\/?p=1355"},"modified":"2022-02-24T19:04:52","modified_gmt":"2022-02-24T19:04:52","slug":"a-super-simple-excel-progress-bar","status":"publish","type":"post","link":"https:\/\/www.pyxll.com\/blog\/a-super-simple-excel-progress-bar\/","title":{"rendered":"A Super-Simple Excel Progress Bar"},"content":{"rendered":"\n<p>When a task takes more than a second or two it can be helpful to display something to the user to show them that progress is still being made. If you don&#8217;t it can just appear that the Excel is hung because it&#8217;s unresponsive until your task is complete.<\/p>\n\n\n\n<p>Python has a variety of UI toolkits that can be used for a progress indicator. They all work mostly in the same way. You create the dialog with the progress indicator, periodically set the progress, and then close the window when your task is complete.<\/p>\n\n\n\n<p>This blog will show you how to display a progress indicator using the pywin32 package. The pywin32 package is sometimes overlooked for building user interface components because it&#8217;s not as modern as some other toolkits. If you don&#8217;t need a fancy user interface though, it can be good choice as you probably already have it installed! The packages in pywin32 wrap the underlying WIN32 APIs, so it can seem a bit &#8216;low level&#8217;, but <span style=\"text-decoration: underline;\">it includes a lot of demos to help get you started<\/span>. Take a look in <code>site-packages\/pythonwin\/pywin\/Demos.<\/code><\/p>\n\n\n\n<p>First off, install the pywin32 package if you don&#8217;t have it already.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\npip install pywin32\n<\/pre><\/div>\n\n\n<p>This actually installs a number of packages: pythonwin, pywin, win32ui, win32api, win32com and others.<\/p>\n\n\n\n<p>In the demos folder mentioned earlier (<code>site-packages\/pythonwin\/pywin\/Demos<\/code>) you&#8217;ll find a file called <code>progressbar.py<\/code>. Exactly what we need! With very little modification we can get this working in Excel and give our users that progress feedback during our long running task.<\/p>\n\n\n\n<p>Here&#8217;s the code from the pythonwin example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom pywin.mfc import dialog\nimport win32ui\nimport win32con\n\ndef MakeDlgTemplate():\n    style = (win32con.DS_MODALFRAME |\n        win32con.WS_POPUP |\n        win32con.WS_VISIBLE |\n        win32con.WS_CAPTION |\n        win32con.WS_SYSMENU |\n        win32con.DS_SETFONT)\n    cs = (win32con.WS_CHILD |\n        win32con.WS_VISIBLE)\n\n    w = 215\n    h = 36\n\n    dlg = &#x5B;&#x5B;&quot;Progress bar control example&quot;,\n        (0, 0, w, h),\n        style,\n        None,\n        (8, &quot;MS Sans Serif&quot;)],\n        ]\n\n    s = win32con.WS_TABSTOP | cs    \n\n    dlg.append(&#x5B;128,\n        &quot;Tick&quot;,\n        win32con.IDOK,\n        (10, h - 18, 50, 14), s | win32con.BS_DEFPUSHBUTTON])\n\n    dlg.append(&#x5B;128,\n        &quot;Cancel&quot;,\n        win32con.IDCANCEL,\n        (w - 60, h - 18, 50, 14), s | win32con.BS_PUSHBUTTON])\n\n    return dlg\n\nclass TestDialog(dialog.Dialog):\n    def OnInitDialog(self):\n        rc = dialog.Dialog.OnInitDialog(self)\n        self.pbar = win32ui.CreateProgressCtrl()\n        self.pbar.CreateWindow (win32con.WS_CHILD |\n                        win32con.WS_VISIBLE,\n                        (10, 10, 310, 24),\n                        self, 1001)\n        # self.pbar.SetStep (5)\n        self.progress = 0\n        self.pincr = 5\n        return rc\n\n    def OnOK(self):\n        # NB: StepIt wraps at the end if you increment past the upper limit!\n        # self.pbar.StepIt()\n        self.progress = self.progress + self.pincr\n        if self.progress &gt; 100:\n                self.progress = 100\n        if self.progress &lt;= 100:\n            self.pbar.SetPos(self.progress)\n\ndef demo(modal = 0):\n    d = TestDialog (MakeDlgTemplate())\n    if modal:\n        d.DoModal()\n    else:\n        d.CreateWindow ()\n\nif __name__==&#039;__main__&#039;:\n    demo(1)\n\n<\/pre><\/div>\n\n\n<p>This is pretty much what we need. It creates a dialog box containing a progress bar with two buttons, one to increment the progress and another to cancel the dialog box.<\/p>\n\n\n\n<p>We need to make a slight change so that we can increment the progress bar ourselves without the button, and close it after we&#8217;re done with it. We&#8217;ll remove the OnOK method and add a &#8220;progress&#8221; property to set the current progress, and add a &#8220;close&#8221; method to close the dialog. Those changes look like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass TestDialog(dialog.Dialog):\n    def OnInitDialog(self):\n        rc = dialog.Dialog.OnInitDialog(self)\n        self.pbar = win32ui.CreateProgressCtrl()\n        self.pbar.CreateWindow (win32con.WS_CHILD |\n                        win32con.WS_VISIBLE,\n                        (10, 10, 310, 24),\n                        self, 1001)\n        return rc\n\n    def set_progress(self, progress):\n        self.pbar.SetPos(progress)\n\n    def close(self):\n        self.PostMessage(win32con.WM_CLOSE, 0, 0)\n<\/pre><\/div>\n\n\n<p>Now all we need is a way to create this dialog and update it during a long running task.<\/p>\n\n\n\n<p>For that we&#8217;ll use a <a rel=\"noreferrer noopener\" href=\"https:\/\/www.pyxll.com\/docs\/userguide\/menus.html\" target=\"_blank\">PyXLL menu item<\/a> to run a task in a background thread. As the task runs, we&#8217;ll set &#8220;progress&#8221; on the dialog so the user can see something&#8217;s going on. When it&#8217;s finished we&#8217;ll close the dialog with the new &#8220;close&#8221; method we added above.<\/p>\n\n\n\n<p>Our long running task looks like this, although in a real case the time.sleep calls would be replaced with actually doing something!<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\ndef long_running_task(progress_dialog):\n    try:\n        for i in range(100):\n            time.sleep(0.05)\n            progress_dialog.set_progress(i)\n    finally:\n        progress_dialog.close()\n<\/pre><\/div>\n\n\n<p>To call our &#8220;long_running_task&#8221; from Excel as a menu function we use the @xl_menu decorator. This doesn&#8217;t have to be a menu function, it could be a macro or ribbon action and work in exactly the same way.<\/p>\n\n\n\n<p>Our menu function will create the progress dialog, start a background thread to run &#8220;long_running_task&#8221; and then wait for it to complete.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n@xl_menu(&quot;Progress Bar Demo&quot;)\ndef progress_bar_demo():\n    d = TestDialog (MakeDlgTemplate())\n    \n    # start a background thread to do some work\n    thread = threading.Thread(target=long_running_task, args=(d,))\n    thread.start()\n    \n    # show the dialog\n    d.DoModal()\n    \n    # wait for the thread to finish\n    thread.join()\n<\/pre><\/div>\n\n\n<p>And there we have it! A super-simple progress indicator in just a few lines of code.  To make it look as you want (you will probably want to remove the buttons since they no longer do anything) you can update the <code>MakeDlgTemplate<\/code> function taken from the pythonwin example code.<\/p>\n\n\n\n<p>Here&#8217;s the full code, but be sure to checkout the original pythonwin example too.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom pyxll import xl_menu\nfrom pywin.mfc import dialog\nimport time\nimport win32ui\nimport win32con\nimport threading\n\ndef MakeDlgTemplate():\n    style = (win32con.DS_MODALFRAME |\n        win32con.WS_POPUP |\n        win32con.WS_VISIBLE |\n        win32con.WS_CAPTION |\n        win32con.WS_SYSMENU |\n        win32con.DS_SETFONT)\n    cs = (win32con.WS_CHILD |\n        win32con.WS_VISIBLE)\n\n    w = 215\n    h = 36\n\n    dlg = &#x5B;&#x5B;&quot;Progress bar control example&quot;,\n        (0, 0, w, h),\n        style,\n        None,\n        (8, &quot;MS Sans Serif&quot;)],\n        ]\n\n    s = win32con.WS_TABSTOP | cs\n\n    return dlg\n\nclass TestDialog(dialog.Dialog):\n    def OnInitDialog(self):\n        rc = dialog.Dialog.OnInitDialog(self)\n        self.pbar = win32ui.CreateProgressCtrl()\n        self.pbar.CreateWindow (win32con.WS_CHILD |\n                                win32con.WS_VISIBLE,\n                                (10, 10, 310, 24),\n                                self, 1001)\n        self.pbar.SetRange(1, 100)\n        return rc\n\n    def set_progress(self, progress):\n        self.pbar.SetPos(progress)\n\n    def close(self):\n        self.PostMessage(win32con.WM_CLOSE, 0, 0)\n\ndef long_running_task(progress_dialog):\n    try:\n        for i in range(100):\n            time.sleep(0.05)\n            progress_dialog.set_progress(i)\n    finally:\n        progress_dialog.close()\n\n@xl_menu(&quot;Progress Bar Demo&quot;)\ndef progress_bar_demo():\n    d = TestDialog (MakeDlgTemplate())\n    \n    # start a background thread to do some work\n    thread = threading.Thread(target=long_running_task, args=(d,))\n    thread.start()\n    \n    # show the dialog\n    d.DoModal()\n    \n    # wait for the thread to finish\n    thread.join()\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>Learn how to display a simple progress bar for tasks that take a little bit of time.<\/p>\n","protected":false},"author":1,"featured_media":1356,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"jetpack_post_was_ever_published":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":""},"categories":[12,2,504],"tags":[596],"class_list":["post-1355","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","category-pyxll","category-user-interface","tag-ui"],"acf":[],"jetpack_featured_media_url":"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2020\/04\/progress-bar.jpg","jetpack_shortlink":"https:\/\/wp.me\/p7l3LP-lR","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/1355","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=1355"}],"version-history":[{"count":1,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/1355\/revisions"}],"predecessor-version":[{"id":1605,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/1355\/revisions\/1605"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/media\/1356"}],"wp:attachment":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/media?parent=1355"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/categories?post=1355"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/tags?post=1355"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}