{"id":23,"date":"2009-08-21T08:05:18","date_gmt":"2009-08-21T13:05:18","guid":{"rendered":"http:\/\/anvil-of-time.com\/2009\/08\/21\/menu-service-for-powerbuilder-applications\/"},"modified":"2021-03-06T19:51:46","modified_gmt":"2021-03-07T00:51:46","slug":"menu-service-for-powerbuilder-applications","status":"publish","type":"post","link":"https:\/\/anvil-of-time.com\/powerbuilder\/menu-service-for-powerbuilder-applications\/","title":{"rendered":"Dynamic Menu Service for PFC PowerBuilder Applications"},"content":{"rendered":"\n<p class=\"m-size-10 size-14\"><span class=\"m-font-size-10 font-size-14\">Back in my PB 7 days I was tasked with creating a Requisition System for the Purchasing department to replace an extremely cumbersome manual process.&nbsp; Essentially it was taking the data from a multi-thousand page report (which was produced daily) into a client-server application.&nbsp; It was a task most business developers should relish since, if done properly, it will produce instant productivity gains for the company.&nbsp; <\/span><span class=\"m-font-size-10 font-size-14\"><br><\/span><\/p>\n\n\n\n<p class=\"m-size-10 size-14\"><span class=\"m-font-size-10 font-size-14\">I decided on an MDI model for the app but when it came to the menus for the windows, I really did not want to create a bunch of objects &#8211; even though the app itself only had about forty five windows.\u00a0 With some research in the PFC documentation on the pfc_n_cst_menu this is what I came up with.<\/span><\/p>\n\n\n\n<p class=\"m-size-10 size-14\"><span class=\"m-font-size-10 font-size-14\">My basic idea was to have a single application menu which was used on all screens rather than the standard method of using one menu for each window.\u00a0 This &#8216;master&#8217; menu would be initialized prior to the opening of a window and options\u00a0 enabled \/ disabled dynamically as the user progressed through their  workflow.\u00a0 Now PB menu handling was always known to be memory intensive but based on the hardware the end users would be on I felt the app should still perform at an acceptable level.<\/span><\/p>\n\n\n\n<p class=\"m-size-10 size-14\"><span class=\"m-font-size-10 font-size-14\">The initial database set up for this service involves entry of the rows which differ from the menu defaults (i.e., items enabled \/ disabled when window opened).<\/span> Each window has a set of entries for all the applicable menu entries.<\/p>\n\n\n\n<p><strong>Objects to get this working<\/strong><\/p>\n\n\n\n<p class=\"m-size-10 size-14\"><code><strong>Single Menu object (used by forty odd windows)<\/strong><\/code><\/p>\n\n\n\n<p class=\"m-size-10 size-14\"><span class=\"m-font-size-10 font-size-14\">m_app_frame (inherited from the PFC m_master object)<\/span><\/p>\n\n\n\n<p class=\"m-size-10 size-14\"><span class=\"m-font-size-10 font-size-14\">This object has all the various options for all windows within the application.&nbsp; If there are menu options you won&#8217;t be using in your application, make them invisible on this object.<\/span><\/p>\n\n\n\n<p><code><strong>Table definition (MS SQLServer)<\/strong><\/code><\/p>\n\n\n\n<pre class=\"wp-block-preformatted SQL\">CREATE TABLE dbo.cmnMenuOptions\n(menuOptionKey int NOT NULL\n, menuName varchar(50) NOT NULL\n, windowName varchar(50) NOT NULL\n, initialSetting char(1) NOT NULL\n, CONSTRAINT pkmenuoptionkey PRIMARY KEY CLUSTERED (menuOptionKey)) ;\n\nCREATE UNIQUE INDEX idx_cMO_1 ON dbo.cmnMenuOptions (windowName , menuName ) ;<\/pre>\n\n\n\n<p><code><strong>Datawindow object (d_menu_settings) based on cmnMenuOptions table with retrieval argument of 'as_windowname'<\/strong><\/code><\/p>\n\n\n\n<pre class=\"wp-block-preformatted VB\">table(column=(type=char(50) updatewhereclause=yes name=menuname dbname=&quot;cmnMenuOptions.menuName&quot; )\ncolumn=(type=char(1) updatewhereclause=yes name=initialsetting dbname=&quot;cmnMenuOptions.initialSetting&quot; )\nretrieve=&quot;\u00a0 SELECT cmnMenuOptions.menuName,\ncmnMenuOptions.initialSetting\nFROM cmnMenuOptions \nWHERE cmnMenuOptions.windowName = :as_windowname\n&quot; arguments=((&quot;as_windowname&quot;, string)) )<\/pre>\n\n\n\n<p><strong>Menu service object code<\/strong> (from the export)<\/p>\n\n\n\n<pre class=\"wp-block-preformatted VB\">forward\nglobal type n_cst_menuservice from n_base\nend type\nend forward\n\nglobal type n_cst_menuservice from n_base autoinstantiate\nend type\n\ntype variables\n\ndatastore\u00a0\u00a0\u00a0 ids_menuoptions\nmenu\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 im_menu, im_item\nstring\u00a0\u00a0\u00a0 is_menuoptions[]\nwindow\u00a0\u00a0\u00a0 iw_parent\nn_cst_menu\u00a0\u00a0\u00a0 inv_menu \/\/ PFC menu service\nn_cst_string\u00a0\u00a0\u00a0 inv_string \/\/ PFC string service\nend variables\n\nforward prototypes\npublic function integer of_setmenuoptions ()\npublic subroutine of_setmenu (boolean ab_enable, string as_menuitem)\npublic function integer of_changemenuoption (string as_option, string as_enabled)\npublic function integer of_findmenuoption (string as_option)\npublic subroutine of_menuinit (string as_window)\nend prototypes\n\npublic function integer of_setmenuoptions ();\n\/\/ sets the is_menuoptions array for the\n\/\/ menu assigned to this window.\n\/\/ only visible options are included.\ninteger\u00a0\u00a0\u00a0 li_limit, li_cnt, li_submenulimit, li_smcnt\ninteger\u00a0\u00a0\u00a0 li_count\nstring ls_option\nli_count = 1\nli_limit = UpperBound (im_menu.item)\n\/\/ loop through every menu item on the menu\nFOR li_cnt = 1 TO li_limit\n   \/\/ skip any invisible menu items\n   IF (im_menu.item[li_cnt].visible) THEN\n     \/\/ check for sub menus\n     li_submenulimit = Upperbound(im_menu.item[li_cnt].item)\n      CHOOSE CASE lower(im_menu.item[li_cnt].classname())\n       CASE &#039;m_window&#039;,&#039;m_help&#039;\n        \/\/ skip these\n        CASE ELSE\n         FOR li_smcnt = 1 to li_submenulimit\n          IF (im_menu.item[li_cnt].item[li_smcnt].Visible) THEN\n            ls_option =lower(im_menu.item[li_cnt].item[li_smcnt].classname())\n            IF (Pos(ls_option,&#039;dash&#039;) = 0) THEN \/\/ skip any dash entries\n              ls_option +=&#039;=&#039;+string(im_menu.item[li_cnt].item[li_smcnt].enabled)\n              is_menuoptions[li_count]=ls_option\n              li_count++\n            END IF\n          END IF\n        NEXT\n      END CHOOSE\n   END IF\nNEXT\nRETURN li_limit\nend function\n\npublic subroutine of_setmenu (boolean ab_enable, string as_menuitem);\n\/\/ enables or disables a menuitem\nIF IsNull(as_menuitem) or as_menuitem = &#039;&#039; THEN RETURN\n\nim_menu = iw_parent.MenuID\n\/\/ get reference to menu item so it&#039;s properties can be changed\ninv_menu.of_GetMenuReference (im_menu, as_menuitem, im_item)\nim_item.Enabled = ab_enable\nend subroutine\n\npublic function integer of_changemenuoption (string as_option, string as_enabled);\n\/\/ set the menuoptions array to the proper format (ie, &quot;m_save=true&quot;)\ninteger li_rc\ninteger\u00a0\u00a0\u00a0 li_ac\nli_rc = 0\n\nli_ac = this.of_findmenuoption(as_option)\n\nIF (li_ac &gt; 0) THEN\n   is_menuoptions[li_ac] = lower(as_option + &#039;=&#039; + as_enabled)\n   li_rc = li_ac\nEND IF\nRETURN li_rc\nend function\n\npublic function integer of_findmenuoption (string as_option);\n\/\/ find the specified menu item in the array of menu options\ninteger\u00a0\u00a0\u00a0 li_ac\ninteger\u00a0\u00a0\u00a0 li_i\ninteger\u00a0\u00a0\u00a0 li_rc\n\nli_rc = 0\nli_ac = upperbound(is_menuoptions)\n\nFOR li_i = 1 to li_ac\n   IF\u00a0(Pos(is_menuoptions[li_i],as_option) &gt; 0) THEN\n      li_rc = li_i\n      EXIT\n   END IF\nNEXT\n\nRETURN li_rc\nend function\n\npublic subroutine of_menuinit (string as_window);\n\/\/ initialize menu for specified window from the database table\n\nlong\u00a0\u00a0\u00a0 ll_rc\nlong\u00a0\u00a0\u00a0 ll_i\nstring\u00a0\u00a0\u00a0 ls_option\nstring\u00a0\u00a0\u00a0 ls_enabled\n\/\/ get the menu options for the specified window\nll_rc = ids_menuoptions.retrieve(as_window)\n\nFOR ll_i = 1 to ll_rc\n   ls_option = ids_menuoptions.getitemstring(ll_i,&#039;menuname&#039;)\n   ls_enabled = ids_menuoptions.getitemstring(ll_i,&#039;initialsetting&#039;)\n   IF (Trim(ls_enabled) = &#039;T&#039;) THEN\n      ls_enabled = &#039;true&#039;\n   ELSE\n      ls_enabled = &#039;false&#039;\n   END IF\n   of_changemenuoption(ls_option,ls_enabled)\nNEXT\n\nend subroutine\n\non n_cst_menuservice.create\ncall super::create\nend on\n\non n_cst_menuservice.destroy\ncall super::destroy\nend on\n\nevent constructor;call super::constructor;\n\/\/ set up menu service datastore\nids_menuoptions = CREATE datastore\nids_menuoptions.dataobject = &#039;d_menu_settings&#039;\nids_menuoptions.settransobject(SQLCA)\n\nend event\n\nevent destructor;call super::destructor;\n\/\/ destroy datastore\nIF IsValid(ids_menuoptions) THEN DESTROY ids_menuoptions\nend event<\/pre>\n\n\n\n<p><strong>Code from window(sheet) ancestor<\/strong><\/p>\n\n\n\n<p><em><strong>Instance Variable<\/strong><\/em><br>n_cst_menuservice inv_ms<\/p>\n\n\n\n<p><em><strong>pfc_postopen event code<\/strong><\/em><\/p>\n\n\n\n<pre class=\"wp-block-preformatted VB\">inv_ms.iw_parent = THIS \/\/ sets parent on menu service\ninv_ms.im_menu = this.MenuID\ninv_ms.of_setmenuoptions()\nevent trigger ue_menuinit()\nwf_menu(inv_ms.is_menuoptions)<\/pre>\n\n\n\n<p><em><strong>ue_menuinit event code<\/strong><\/em><\/p>\n\n\n\n<pre class=\"wp-block-preformatted VB\">\/\/Initial menu for this window\nstring&amp;nbsp;&amp;nbsp;&amp;nbsp; ls_classname\nls_classname = this.classname()\ninv_ms.of_menuinit(ls_classname)<\/pre>\n\n\n\n<p><em><strong>wf_menu function code<\/strong><\/em><\/p>\n\n\n\n<pre class=\"wp-block-preformatted VB\">\/\/ call the menu processing service for each item\n\/\/ in the as_items array.\n\/\/ format of as_items is: &quot;m_new=true&quot;\n\/\/ indicating this item should be enabled\n\ninteger\u00a0\u00a0\u00a0 li_return\ninteger\u00a0\u00a0\u00a0 li_rc\ninteger\u00a0\u00a0\u00a0 li_i\nstring\u00a0\u00a0\u00a0 ls_option\nboolean\u00a0\u00a0\u00a0 lb_enabled\n\nli_return = 1\nli_rc = upperbound(as_items)\nFOR li_i = 1 to li_rc\n   ls_option = inv_ms.inv_string.of_gettoken(as_items[li_i],&#039;=&#039;)\n   lb_enabled = (Pos(as_items[li_i],&#039;true&#039;) &gt; 0)\n   inv_ms.of_setmenu(lb_enabled,ls_option)\nNEXT\nRETURN li_return<\/pre>\n\n\n\n<p><strong>Example descendant window code used to enable \/ disable menu items based on user input, processing, work flow changes, etc.<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted VB\">string ls_menu[] \/\/ this could be made an instance variable too\nls_menu[1] = &#039;m_save=true&#039; \/\/enable the save option on the window\n\/\/ add additional array elements as needed\nwf_menu(ls_menu)  \/\/ now the menu option is changed<\/pre>\n\n\n\n<p><strong>Example cmnMenuOptions rows for window &#8216;w_inv_action_list&#8217;<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Key   menuName        windowName         initialSetting\n 1    m_save          w_inv_action_list  F\n 2    m_saveas        w_inv_action_list  F\n 3    m_printpreview  w_inv_action_list  F\n 4    m_pagesetup     w_inv_action_list  F\n 5    m_first         w_inv_action_list  F\n 6    m_priorpage     w_inv_action_list  F\n 7    m_nextpage      w_inv_action_list  F\n 8    m_lastpage      w_inv_action_list  F\n 9    m_zoom          w_inv_action_list  F\n 10   m_processdetail w_inv_action_list  F\n 11   m_sort          w_inv_action_list  F\n 12   m_uploadreq     w_inv_action_list  F\n 13   m_selectall     w_inv_action_list  F\n 14   m_clear         w_inv_action_list  T\n 15   m_paste         w_inv_action_list  F\n 16   m_copy          w_inv_action_list  F\n 17   m_cut           w_inv_action_list  F\n 18   m_undo          w_inv_action_list  F\n 19   m_insertrow     w_inv_action_list  F\n 20   m_addrow        w_inv_action_list  F\n 21   m_deleterow     w_inv_action_list  F\n 22   m_nextitem      w_inv_action_list  F\n 252  m_reset         w_inv_action_list  F<\/pre>\n\n\n\n<p>Now if a new menu option is added to the application you add rows to the cmnMenuOptions for each window.  Similar to this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT cmnMenuOptions (menuName, windowName, initialSetting) SELECT DISTINCT &#039;m_newmenuname&#039;, windowName, &#039;F&#039; from cmnMenuOptions<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Back in my PB 7 days I was tasked with creating a Requisition System for the Purchasing department to replace an extremely cumbersome manual process.&nbsp; Essentially it was taking the data from a multi-thousand page report (which was produced daily) into a client-server application.&nbsp; It was a task most business developers should relish since, if&hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[3],"tags":[33,12,16],"_links":{"self":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts\/23"}],"collection":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/comments?post=23"}],"version-history":[{"count":11,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts\/23\/revisions"}],"predecessor-version":[{"id":1701,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/posts\/23\/revisions\/1701"}],"wp:attachment":[{"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/media?parent=23"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/categories?post=23"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/anvil-of-time.com\/wp-json\/wp\/v2\/tags?post=23"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}