{"id":943,"date":"2019-12-06T12:21:47","date_gmt":"2019-12-06T12:21:47","guid":{"rendered":"https:\/\/www.pyxll.com\/blog\/?p=943"},"modified":"2023-01-05T10:04:52","modified_gmt":"2023-01-05T10:04:52","slug":"extending-the-excel-user-interface","status":"publish","type":"post","link":"https:\/\/www.pyxll.com\/blog\/extending-the-excel-user-interface\/","title":{"rendered":"Extending the Excel User Interface"},"content":{"rendered":"\n<p class=\"has-very-light-gray-background-color has-background\">You can find the code for the examples in the&nbsp;<em>qtgraphics<\/em>&nbsp;folder of our&nbsp;<a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/pyxll\/pyxll-examples\" target=\"_blank\">examples repository.<\/a><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Interacting with your users<\/h3>\n\n\n\n<p>Python has great support for building user interface, or GUI, tools. With PyXLL, these can be used from within Excel. In this and the next few posts we&#8217;ll be building Excel user interfaces using the popular <a href=\"https:\/\/www.riverbankcomputing.com\/static\/Docs\/PyQt5\/\">PyQt5 package<\/a> (based on the cross-platform Qt5 library). There are <a href=\"https:\/\/build-system.fman.io\/pyqt5-tutorial\">fast introductions<\/a> to PyQt5 and <a href=\"https:\/\/wiki.python.org\/moin\/PyQt\">quite a lot of other literature<\/a> on the Internet, continuously being updated. Because of that, these posts look mostly at the PyQt5 features that you need to solve specific problems.<\/p>\n\n\n\n<p>The simplest interactions are by means of <em>modal dialogues<\/em>, which suspend all other operations in the program until they are dismissed. You can do quite a lot just with such dialogues. To do anything at all in Qt you must first create a QApplication object. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Accessing the QApplication <\/h3>\n\n\n\n<p>You want to be sure that you don&#8217;t create more than one QApplication, so first try and access the current instance. If none exists, then create one. This makes a relatively simple function. (<em>In this and other snippets we omit the necessary imports, which are included in the complete programs in the examples repository).<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\ndef get_qt_app():\n    &quot;&quot;&quot;Returns a QApplication instance.\n    Must be called before showing any dialogs.\n    &quot;&quot;&quot;\n    app = QApplication.instance()\n    if app is None:\n        app = QApplication(&#x5B;sys.executable])\n    return app\n<\/pre><\/div>\n\n\n<div style=\"height:30px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p class=\"has-very-light-gray-background-color has-background has-normal-font-size\"><strong>Note:&nbsp;Anaconda Installations of PyQt5<\/strong><br><br>If you are using Anaconda and have installed PyQt5 using <em>conda<\/em> you may come across this error:<br><em><strong>PyQt: Could not find or load the Qt platform plugin &#8220;windows&#8221;<\/strong><\/em><br><br>To fix this you need to tell it where to look by setting the QT_PLUGIN_PATH environment variable. This can be done in your pyxll.cfg file, for example:<br><br>[ENVIRONMENT]<br>QT_PLUGIN_PATH = C:\\Users\\xxx\\Anaconda\\envs\\pyxll\\Library\\plugins<br><br>For more information please <a rel=\"noreferrer noopener\" label=\"see this FAQ article (opens in a new tab)\" href=\"https:\/\/support.pyxll.com\/hc\/en-gb\/articles\/360038047773-Problem-using-PyQt-Could-not-find-or-load-the-Qt-platform-plugin-windows-\" target=\"_blank\">see this FAQ article<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Meet QMessageBox<\/h3>\n\n\n\n<p>Qt5 has a family of built-in dialogues for various purposes. The simplest, the QMessageBox, displays a (possibly multi-line) message to the user and requires acknowledgement. Here&#8217;s a program that shows a simple use of QMessageBox. The complete code is file <em>qt_ex1.py<\/em> in the repository.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nif __name__ == &#039;__main__&#039;:\n    app = get_qt_app()\n    msgBox = QMessageBox()\n    msgBox.setText(&#039;This is a multi-line\\nmessage for display\\nto the user.&#039;)\n    msgBox.setInformativeText(&quot;And here is a detailed explanation,&quot;\n                              &quot; or at least the closest you will get.&quot;)\n    msgBox.setWindowTitle(&#039;TITLE&#039;)\n    msgBox.exec_()\n<\/pre><\/div>\n\n\n<div style=\"height:25px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<div class=\"wp-block-media-text alignwide has-media-on-the-right is-stacked-on-mobile img-border\" style=\"grid-template-columns:auto 39%\"><figure class=\"wp-block-media-text__media\"><img decoding=\"async\" width=\"275\" height=\"177\" data-attachment-id=\"1010\" data-permalink=\"https:\/\/www.pyxll.com\/blog\/extending-the-excel-user-interface\/messagebox-2\/\" data-orig-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/12\/MessageBox-1.png\" data-orig-size=\"275,177\" 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=\"MessageBox\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/12\/MessageBox-1.png\" src=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/12\/MessageBox-1.png\" alt=\"A PyQt5 modal dialog\" class=\"wp-image-1010\"\/><\/figure><div class=\"wp-block-media-text__content\">\n<p>Here&#8217;s what the program<br>will display. Notice that the informative text is automatically wrapped to length<\/p>\n<\/div><\/div>\n\n\n\n<div style=\"height:25px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Making the optimizer more usable<\/h3>\n\n\n\n<p>In <a href=\"https:\/\/www.pyxll.com\/blog\/a-better-goal-seek\/\">a previous post<\/a> I explained how to use PyXLL to tackle a broad range of optimization problems with Excel. The short animation showed a PyXLL-driven goal seek. Excel&#8217;s &#8220;undo&#8221; feature couldn&#8217;t reverse the changes to the input values after an optimization, though. You need that ability to be able to ask &#8220;what if&#8221; questions of the data.<\/p>\n\n\n\n<div class=\"wp-block-image is-style-default no-shadow center\"><figure class=\"aligncenter size-large is-resized\"><img decoding=\"async\" data-attachment-id=\"985\" data-permalink=\"https:\/\/www.pyxll.com\/blog\/extending-the-excel-user-interface\/optimizationsmall\/\" data-orig-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/11\/OptimizationSmall.png\" data-orig-size=\"702,438\" 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=\"OptimizationSmall\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/11\/OptimizationSmall.png\" src=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/11\/OptimizationSmall.png\" alt=\"\" class=\"wp-image-985\" width=\"397\" height=\"247\" srcset=\"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/11\/OptimizationSmall.png 702w, https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/11\/OptimizationSmall-300x187.png 300w\" sizes=\"(max-width: 397px) 100vw, 397px\" \/><\/figure><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Giving the user a choice<\/h3>\n\n\n\n<p>Here&#8217;s a modified version of the final optimization function from the last blog post. It displays the results of the optimization and asks the user whether to keep the optimization result or to revert the inputs to their original state?<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: true; title: ; notranslate\" title=\"\">\n@xl_menu(&quot;sicpy.optimize&quot;)\ndef optimize4():\n    xl = xl_app()\n    # Get the initial values of the input cells\n    in_values = list(xl.Range(&#039;Inputs&#039;).Value)\n    X = np.array(&#x5B;x&#x5B;0] for x in in_values])\n\n    orig_calc_mode = xl.Calculation\n    try:\n        # switch Excel to manual calculation\n        # and disable screen updating\n        xl.Calculation = constants.xlManual\n        xl.ScreenUpdating = False\n\n        # run the minimization routine\n        xl_obj_func = partial(obj_func, xl)\n        result = minimize(xl_obj_func, X, method=&#039;nelder-mead&#039;)\n\n        mbox = QMessageBox()\n        mbox.setIcon(QMessageBox.Information)\n        mbox.setText(&quot;Optimization results shown below.&quot;\n                     &quot;\\nMake changes permanent?&quot;)\n        mbox.setWindowTitle(&quot;Optimization Complete&quot;)\n        mbox.setInformativeText(&quot;\\n&quot;.join(&#x5B;\n            &quot;Successful:       %s&quot; % result.success,\n            result.message,\n            &quot;After %d iterations&quot; % result.nit\n            ]))\n        mbox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)\n        yes_no = mbox.exec_()\n\n        if yes_no != QMessageBox.Ok:\n            xl.Range(&#039;Inputs&#039;).Value = in_values\n        else:\n            xl.Range(&#039;Inputs&#039;).Value = &#x5B;(float(x), ) for x in result.x]\n\n    finally:\n        # restore the original calculation\n        # and screen updating mode\n        xl.ScreenUpdating = True\n        xl.Calculation = orig_calc_mode\n<\/pre><\/div>\n\n\n<p>Notice that line 5 saves the values of the input cells. When optimization is complete, starting at line 19 the function summarises the <a rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\" href=\"https:\/\/docs.scipy.org\/doc\/scipy\/reference\/generated\/scipy.optimize.OptimizeResult.html\" target=\"_blank\">results<\/a> in a QMessageBox and asks the user whether or not to make the changes permanent. Note how we configured QMessageBox with two buttons. Its <em>_exec<\/em> method returns a value indicating which button was pressed.<\/p>\n\n\n\n<p>Rather than rely on keyboard shortcuts I added this optimizer as a menu command. I also fixed a small bug in the original. This introduced slight errors by leaving the objective function&#8217;s input set to the last evaluation&#8217;s.<\/p>\n\n\n\n<p>The complete code can be found in the <em>optimizer4.py<\/em> file in the examples repository.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Next Steps<\/h2>\n\n\n\n<p>Continue with our next PyQt5 article,&nbsp;<a href=\"https:\/\/www.pyxll.com\/blog\/asking-the-user-for-input\/\">Asking the User for Input with PyQt5<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Part 1 of our guide to creating user interfaces in Excel with PyQt5.<\/p>\n","protected":false},"author":803,"featured_media":1028,"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":[],"class_list":["post-943","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","category-pyxll","category-user-interface"],"acf":[],"jetpack_featured_media_url":"https:\/\/www.pyxll.com\/blog\/wp-content\/uploads\/2019\/12\/excel-qt.png","jetpack_shortlink":"https:\/\/wp.me\/p7l3LP-fd","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/943","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\/803"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/comments?post=943"}],"version-history":[{"count":1,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/943\/revisions"}],"predecessor-version":[{"id":1672,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/posts\/943\/revisions\/1672"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/media\/1028"}],"wp:attachment":[{"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/media?parent=943"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/categories?post=943"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pyxll.com\/blog\/wp-json\/wp\/v2\/tags?post=943"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}