#VSIT Custom Forms Module added to Visual Studio Integration Toolkit Build 18

David Meego - Click for blog homepageThe release of Build 18 of Visual Studio Integration Toolkit fulfills a long-term plan to add advanced modules to the product that extend its functionality beyond the original menu navigation that was offered by its predecessor, Menus for Visual Studio Tools for Microsoft Dynamics GP.

The last big jump in functionality happened with Build 15 when support for Area Pages was added to the Menus module, and over 260 Helper Functions exposing “Dexterity only” functionality to Visual Studio Tools developers was added to the Helpers module.

Build 18 adds an advanced Custom Forms module which allows Visual Studio Tools developers to create their own Dexterity custom windows. These custom forms have the advantage over WinForms as they will work on the Web Client and all other Microsoft Dynamics GP features.

The Visual Studio Integration Toolkit Custom Forms have the following advantages over Visual Studio Tools WinForms or Visual Basic for Application (VBA) UserForms. Because they are Dexterity based:

  • They will work on the Web Client.
  • The windows can be modified with Modifier.
  • The reports can be modified with Report Writer.
  • They work with standard Microsoft Dynamics GP Application Security.
  • They work with Microsoft Dynamics GP Macros.
  • They support standard features such as record notes, lookups and reports.
  • Custom Maintenance windows have lookup and inquiry windows as well as reports (with Named Printers support).
  • They will work with the following GP Power Tools features: Deny Based Security, Company Colors, Window Position Memory, Dynamic Product Selection, Dictionary Control.
  • They are integrated with GP Power Tools Build 30 (or later) Developer Tools.

The History – From Joke to Reality

The history of the Custom Forms module goes way back in time. When non Dexterity customization tools were added to Great Plains Dynamics such as VBA (Dynamics v4.0) and VSTools (Dynamics v9.0), I had joked with Bill Marshall (then President of the GP ISV company: MC2) about creating a Dexterity product which was just some blank forms and selling it under the name “Blankety Blanks”. The idea was that developers using VBA or VSTools could then use Modifier to create Dexterity based forms which would look and behave the same as every other window in the application. It was something that would take me a few minutes to create and then sell for squillions. 🙂

While the joke product was never created, the idea hung around. But, it was only when VBA was discontinued and VSTools WinForms would not work on the Web Client that the thought of creating the custom forms module became something that was really needed and not just a joke.

GP Power Tools has a Developer Tools module which can achieve almost anything with its ability to work with Dexterity sanScript, SQL Server Transact-SQL and .Net languages such as C# and VB.Net, and also work with Modified windows (something native Dexterity cannot do). However, it had always had a limitation that it could not create entirely new windows.

Creating the Custom Forms module in Visual Studio Integration Toolkit and adding support for the new Application Programming Interfaces (APIs) into GP Power Tools became a priority.

The Implementation

I had already written Menus for Visual Studio Tools which became the Visual Studio Integration Toolkit when I left Microsoft in 2014 and was able to get the rights. I knew that to really make this idea work, it would need to be much more than just some blank forms sitting in a Dexterity dictionary. The problem was how could it work when multiple Developers could use the forms at the same time. Unlike the menu navigation functionality which created and assigned menus purely in memory, the custom forms functionality would need some way to reserve a form for use by a developer, so it becomes theirs to modify and use as they see fit.

This meant a reservation table was needed so once a form was requested it would be permanently assigned to that developer until they released it. To group custom forms for each developer together a Developer table was also added.

Finally, to speed up the process of creating the windows, different types of custom forms were created with many of the base features needed already existing. There are currently 10 forms of each type available.

The available form types (with FormType numbers, constants and SubForms) are:

  • 1 = Blank Form (CF_TYPE_BLANK, 0)
  • 2 = Dialog Form  (CF_TYPE_DIALOG, 0)
  • 3 = Setup Form (linked to Setup Table) (CF_TYPE_SETUP, 0)
  • 4 = Maintenance Form (Linked to Header Table). Includes Report and Sub Forms:
    • 0 = Maintenance Form (CF_TYPE_MAINT, 0)
    • 1 = Lookup Form (CF_TYPE_MAINT, 1)
    • 2 = Inquiry Form (CF_TYPE_MAINT, 2)
  • 5 = Maintenance Form with Lines (Linked to Header and Line Tables). Includes Report and Sub Forms:
    • 0 = Maintenance Form (CF_TYPE_MAINT_LINES, 0)
    • 1 = Lookup Form (CF_TYPE_MAINT_LINES, 1)
    • 2 = Inquiry Form (CF_TYPE_MAINT_LINES, 2)

Note: Whilst there are user interfaces for the setting up and testing of custom forms, all of the functionality is available via the API calls and so can be set up automatically by the developer. See sample code below.

Custom Forms are set up by defining the Developer ID to be used for all your forms.

Then setting up the custom form with the terminology for the current systems language IDs.

You can then test the form with the Testing UI which calls the API commands with the selected settings.

Below are examples of the base (un-modified) forms for the Maintenance Form with Lines custom form type.

Maintenance Form with lines

Lookup Form

Inquiry Form with lines

There is also a report linked to the Maintenance and Inquiry forms. The forms and the report can be edited with the Modifier and Report Writer to add whatever custom fields are desired. These fields can be stored in the DUOS (Dynamic User Object Store SY90000) table or in a custom SQL table as desired.

Using the Custom Forms

The User Guide documentation (VSTMenus.pdf) contains details on how to use the custom forms and the code example in the version history (VSTMenus.txt) includes sample code for using the Custom Forms.

To use the Custom Forms module, you need to register event handlers against the EventRegisterForm and EventHandlerForm functions.

Registering Event Handlers (C#)

        // IDexterityAddIn interface
        public void Initialize()
        {
            // Register Event to Register Custom Forms
            MenusForVisualStudioTools.Functions.EventRegisterForm.InvokeAfterOriginal += new EventRegisterFormFunction.InvokeEventHandler(VSTMEventRegisterForm);
            // Register Event to handle Custom Forms Events
            MenusForVisualStudioTools.Functions.EventHandlerForm.InvokeAfterOriginal += new EventHandlerFormFunction.InvokeEventHandler(VSTMEventHandlerForm);
        }

Registering Event Handlers (VB.Net)

    ' IDexterityAddIn interface
    Sub Initialize() Implements IDexterityAddIn.Initialize
        ' Register Event to Register Custom Forms
        AddHandler MenusForVisualStudioTools.Functions.EventRegisterForm.InvokeAfterOriginal, AddressOf VSTMEventRegisterForm
        ' Register Event to handle Custom Forms Events
        AddHandler MenusForVisualStudioTools.Functions.EventHandlerForm.InvokeAfterOriginal, AddressOf VSTMEventHandlerForm
    End Sub

Once you have registered against the event functions, you can write your code to register your custom forms.

EventRegisterForm Handler (C#)

        // Script to Register Custom Forms
        void VSTMEventRegisterForm(object sender, EventRegisterFormFunction.InvokeEventArgs e)
        {
            short FormNumber = 0;
            short Err = 0;

            // Register Developer
            Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.DeveloperRegister.Invoke("WDC", "Winthrop Development Consultants");
            if (Err < 0)
            {
                MessageBox.Show("Custom Forms Register Developer, error code: " + Convert.ToString(Err), APPNAME);
            }

            // Register Forms
            Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.FormRegister.Invoke("WDC", "TEST BLANK",                // Developer ID, Form ID
                                                                                            "Test of Blank Form",             // Form Description
                                                                                            1 /* CF_TYPE_BLANK */,            // Form Type
                                                                                            "Test Blank Form",                // Form Name
                                                                                            out FormNumber);                  // Form Number Returned
            if (Err < 0)
            {
                MessageBox.Show("Custom Forms Register Blank Form, error code: " + Convert.ToString(Err), APPNAME);
            }

            Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.FormRegister.Invoke("WDC", "TEST SETUP",                  // Developer ID, Form ID
                                                                                    "Test of Setup Form",                       // Form Description
                                                                                     3 /* CF_TYPE_SETUP */,                     // Form Type
                                                                                     "Test Setup Form",                         // Form Name
                                                                                     out FormNumber);                           // Form Number Returned
            if (Err < 0)
            {
                MessageBox.Show("Custom Forms Register Setup Form, error code: " + Convert.ToString(Err), APPNAME);
            }

            Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.FormRegisterMaint.Invoke("WDC", "TEST LINES",                     // Developer ID, Form ID
                                                                                                "Test of Maint Form",                   // Form Description
                                                                                                 5 /* CF_TYPE_MAINT_LINES */,           // Form Type
                                                                                                 "Test Maint Form",                     // Maint Form Name
                                                                                                 "Test Lookup Form",                    // Lookup Form Name
                                                                                                 "Test Inquiry Form",                   // Inquiry Form Name
                                                                                                 "Test ID",                             // ID Field Name
                                                                                                 "Test Name",                           // Description Field Name
                                                                                                 "Test Report",                         // Report Name
                                                                                                 out FormNumber);                       // Form Number Returned
            if (Err < 0)
            {
                MessageBox.Show("Custom Forms Register Maint Form, error code: " + Convert.ToString(Err), APPNAME);
            }

        }

EventRegisterForm Handler (VB.Net)

    'Script to Register Custom Forms
    Private Sub VSTMEventRegisterForm(ByVal sender As Object, ByVal e As EventRegisterFormFunction.InvokeEventArgs)
        Dim FormNumber As Short = 0
        Dim Err As Short = 0

        ' Register Developer
        Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.DeveloperRegister.Invoke("WDC", "Winthrop Development Consultants")
        If Err < 0 Then
            MessageBox.Show("Custom Forms Register Developer, error code: " + Convert.ToString(Err), APPNAME)
        End If

        ' Register Forms
        Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.FormRegister.Invoke("WDC", "TEST BLANK",
                                                                                       "Test of Blank Form",
                                                                                       1,
                                                                                       "Test Blank Form",
                                                                                       FormNumber)
        If Err < 0 Then
            MessageBox.Show("Custom Forms Register Blank Form, error code: " + Convert.ToString(Err), APPNAME)
        End If

        Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.FormRegister.Invoke("WDC", "TEST SETUP",
                                                                                    "Test of Setup Form",
                                                                                     3,
                                                                                     "Test Setup Form",
                                                                                     FormNumber)
        If Err < 0 Then
            MessageBox.Show("Custom Forms Register Setup Form, error code: " + Convert.ToString(Err), APPNAME)
        End If

        Err = MenusForVisualStudioTools.Forms.VstmForms.Functions.FormRegisterMaint.Invoke("WDC", "TEST LINES",
                                                                                           "Test of Maint Form",
                                                                                           5,
                                                                                           "Test Maint Form",
                                                                                           "Test Lookup Form",
                                                                                           "Test Inquiry Form",
                                                                                           "Test ID",
                                                                                           "Test Name",
                                                                                           "Test Report",
                                                                                           FormNumber)
        If Err < 0 Then
            MessageBox.Show("Custom Forms Register Maint Form, error code: " + Convert.ToString(Err), APPNAME)
        End If

    End Sub

Once the forms have been registered, they can be modified to add any additional fields needed. There are also 10 checkbox fields already stored in the tables which can be used on the Setup and Maintenance forms.

You can then respond to any of the Events from the custom forms. Once you have confirmed that the event is for your Developer ID and which Form ID and Sub Form, you can add the appropriate actions for the Mode, Field ID and Events.

Parameters for the EventHandlerForm function are:

IN_DeveloperID                (String)                Developer ID when registered
IN_FormID                        (String)                Form ID when registered
IN_SubForm                     (Short)                 Sub Form Number (0 when no sub forms)
IN_Mode                           (Short)                 Event Mode (see table below)
IN_FieldID                         (String)                Name or Caption of field or table key value
IN_Event                           (Short)                 Event Type (see table below)

EventHandlerForm Handler (C#)

        // Script to handle Custom Form callbacks
        void VSTMEventHandlerForm(object sender, EventHandlerFormFunction.InvokeEventArgs e)
        {
                string IN_DeveloperID = e.inParam1;
                string IN_FormID = e.inParam2;
                short IN_SubForm = e.inParam3;
                short IN_Mode = e.inParam4;
                string IN_FieldID = e.inParam5;
                short IN_Event = e.inParam6;

                // See Examples (in VSTMenus.txt) for case statements handling events
        }

EventHandlerForm Handler (VB.Net)

    ' Script to handle Custom Form callbacks
    Private Sub VSTMEventHandlerForm(ByVal sender As Object, ByVal e As EventHandlerFormFunction.InvokeEventArgs)
        Dim IN_DeveloperID As String = e.inParam1
        Dim IN_FormID As String = e.inParam2
        Dim IN_SubForm As Short = e.inParam3
        Dim IN_Mode As Short = e.inParam4
        Dim IN_FieldID As String = e.inParam5
        Dim IN_Event As Short = e.inParam6

        ' See Examples (in VSTMenus.txt) for select case statements handling events
    End Sub

The events for the different modes are shown below:

  • 1 = Form
    • 0 = TRIGGER_FOCUS_PRE
    • 2 = TRIGGER_FOCUS_POST
  • 2 = Window
    • 0 = TRIGGER_FOCUS_PRE
    • 2 = TRIGGER_FOCUS_POST use with FormAbortClose(), if needed
    • 3 = TRIGGER_FOCUS_PRINT
    • 4 = TRIGGER_FOCUS_ACTIVATE
  • 3 = Scrolling Window
    • 0 = TRIGGER_FOCUS_PRE
    • 1 = TRIGGER_FOCUS_CHANGE use with FormRejectScript(), if needed
    • 2 = TRIGGER_FOCUS_POST
    • 5 = TRIGGER_FOCUS_FILL use with FormRejectRecord(), if needed
    • 6 = TRIGGER_FOCUS_INSERT
    • 7 = TRIGGER_FOCUS_DELETE
  • 4 = Field
    • 0 = TRIGGER_FOCUS_PRE
    • 1 = TRIGGER_FOCUS_CHANGE use with FormRejectScript(), if needed
    • 2 = TRIGGER_FOCUS_POST
  • 5 = Scrolling Field
    • 0 = TRIGGER_FOCUS_PRE
    • 1 = TRIGGER_FOCUS_CHANGE use with FormRejectScript(), if needed
    • 2 = TRIGGER_FOCUS_POST
  • 6 = Table (Field Parameter will contain ID value for Header Table)
    • 4 = TRIGGER_ON_DB_ADD
    • 5 = TRIGGER_ON_DB_UPDATE
    • 16 = TRIGGER_ON_DB_DELETE
  • 7 = Scroll Table (Field Parameter will contain ID & Sequence for Line Table)
    • 4 = TRIGGER_ON_DB_ADD use with KeySplitLine() to split ID & Sequence
    • 5 = TRIGGER_ON_DB_UPDATE use with KeySplitLine() to split ID & Sequence
    • 16 = TRIGGER_ON_DB_DELETE use with KeySplitLine() to split ID & Sequence
  • 8 = Report
    • 0 = rw_ReportStart() use with ReportSetString()
    • 1 = rw_ReportEnd() use with ReportSetString()
    • 2 = rw_TableHeaderString() use with ReportGetTableHeader() & ReportSetString()
    • 3 = rw_TableHeaderCurrency() use with ReportGetTableHeader() ) & ReportSetCurrency()
    • 4 = rw_TableLineString() use with ReportGetTableLine() & ReportSetString()
    • 5 = rw_TableLineCurrency() use with ReportGetTableLine() & ReportSetCurrency()

Custom Forms API Calls

The Custom Forms API supports the following calls. Details are in the documentation (VSTMenus.pdf). The sample code also shows how many of these functions should be used:

API Function Description
DeveloperRegister This function is used to register a Developer ID for use with Custom Forms.
DeveloperUnregister This function is used to remove a Developer ID.
FormRegister This function is used to register a Form ID for the desired form type.
FormRegisterMaint This function is used to register a Maintenance Form ID for the desired Maintenance Form or Maintenance Form With Lines form type.
FormUnregister This function is used to remove a Form ID.
FormClose This function is used to close a form that is currently open.
FormOpen This function is used to open a form that has already been registered. There are also specific functions for different form types which have additional functionality.
FormOpenDialog This function is used to open a Dialog type form that has already been registered.
FormOpenInquiry This function is used to open an Inquiry Sub Form of Maintenance type form that has already been registered.
FormOpenLookup This function is used to open a Lookup Sub Form of Maintenance type form that has already been registered. Dexterity or GP Power Tools only.
FormOpenLookupReturn This function is used to open a Lookup Sub Form of Maintenance type form that has already been registered.
FormOpenMaint This function is used to open a Maintenance Sub Form of Maintenance type form that has already been registered.
FormOpenReport This function is used to open a Report for a Maintenance type form that has already been registered.
FormOpenReportDestination This function is used to open a Report for a Maintenance type form that has already been registered.
FormIsOpen This function is used to check if a form is currently open.
FormGetPosition This function is used to get the Position of an open form.
FormGetSize This function is used to get the Size of an open form.
FormGetTitle This function is used to get the Title of an open form.
FormResize This function is used to resize an open form.
FormSetPosition This function is used to set the Position of an open form.
FormSetSize This function is used to set the Size of an open form.
FormSetTitle This function is used to set the Title of an open form.
KeyCombineForm This function is used to combine the Developer ID and Form ID into a single string.
KeyCombineLine This function is used to combine the key fields ID and Sequence Number into a single string.
KeySplitForm This function is used to split a combined Developer ID and Form ID back into separate values.
KeySplitLine This function is used to split a combined field ID and Sequence Numbers into separate values.
ReportGetTableHeader This function is used to get the key fields passed into the rw_TableHeaderString() and rw_TableHeaderCurrency() report writer functions.
ReportGetTableLine This function is used to get the key fields passed into the rw_TableLineString() and rw_TableLineCurrency() report writer functions.
ReportSetCurrency This function is used to set the currency value returned by the rw_TableHeaderCurrency() and rw_TableLineCurrency() report writer functions.
ReportSetString This function is used to set the string value returned by the rw_ReportStart(), rw_ReportEnd(), rw_TableHeaderString() and rw_TableLineString() report writer functions.
FormAbortClose This function is used to set the Abort Close flag from the Window Post Event Handler to stop the window closing.
FormRejectRecord This function is used to set the Reject Record flag from the Scrolling Window Fill Event Handler to prevent a record being displayed.
FormRejectScript This function is used to set the Reject Script flag from the Event Handler to stop the current script completing.

Registration

The Visual Studio Integration Toolkit – Custom Forms module is an advanced feature and requires registration for a small annual subscription. More details are on the Product page and the Portal page.

I hope you find this new module useful. Sorry it took so long to finally write it.

David

This article was originally posted on http://www.winthropdc.com/blog.

3 thoughts on “#VSIT Custom Forms Module added to Visual Studio Integration Toolkit Build 18

Please post feedback or comments

This site uses Akismet to reduce spam. Learn how your comment data is processed.