Tag: NinjaDevTools

  • D365/X++ NinjaDevTools to Development Add-In

    Background

    Back in December 2017, Hichem Chekebkeb released NinjaDevTools on GitHub as a way to simplify D365 X++ development tasks. NinjaDevTools is a set of utilities that helps technical resources perform tasks that usually take multiple clicks on a single click. Out of the various utilities within NInjaDevTools, the ones I like the most (up to this day) are the Form Scaffolding and Menu Item Creation tools.

    I like the project so much that I have upgraded it throughout the years on my computer, even though I have zero knowledge of C# 😊 upgrading from Visual Studio 2017 to 2019 was a bit of an endeavour for me, but, I managed to complete it and got the project to work.

    Nowadays, we use Visual Studio 2022 for D365 X++ development, and from an architectural perspective, it is a different beast. Some of the .net libraries were changed or deprecated and for this reason, the update was not working, the same update path I followed between VS2017 and VS2019 got me nowhere.

    After many tries, I finally got the project to build a DLL, however, the extension never loaded when added to the D365 Add-In extensions folder and I just gave up.  


    The Breakthrough

    After a whole lot of disappointment, I researched a bit more about the subject. I discovered that in Visual Studio 2022, some framework classes used for design and context manipulation were changed or deprecated. At the same time, I found out about the development add-ins for D365. The add-in project has been there for a while, but now I found myself at a crossroads with the upgrade. Should I continue trying to upgrade it? Or, should I try something else? That’s the moment it hit me, I can use a development add-in to implement the utilities I want.

    My original idea was to update the NinjaDevTools solution to continue having an all-in-one utility tool. However, with the multiple setbacks, and the framework changes I decided to extract the tools I liked from the original solution to create a couple of D365 Development Add-ins.

    The form scaffolding utility populates the form with all the required pattern controls, or at least the vast majority. The menu item creation utility’s purpose is self-explanatory, but it creates a menu item and inserts it into the current working project. In this article, I will showcase the creation of the form scaffolding add-in.

    Please be aware that the form pattern creation is not dynamic, Hichem spent some time creating the form pattern controls through code, meaning, if you want to include more functionality to the add-in you can add the controls manually to your add-in’s code so you can use it on your development workflow later on.


    Add-In Creation

    1. Select the Developer Tools Addin project when creating a new project on Visual Studio 2022:
    1. Specify the details of your add-in project and click on Create.
    1. The solution will contain the items presented in the screenshot below.
    1. Out of the list above, the only items we should care about are:
      • DesignerContextMenuAddin.cs: work on this file if your add-in is intended to work during design time, meaning, if you have the designer open and want to execute an action during design.
      • MainMenuAddIn.cs: use this file if your add-in is located at the Main Menu > Extensions > Dynamics 365 > Addins and its objective is to perform tasks that are more of a generic nature.
      • AddinResources.resx: this file will allow us to set the labels for the designer context and the main menu buttons.
    2. In this article, we will implement the form scaffolding from NinjaDevTools while on the designer. For that reason, we will use the DesignerContextMenuAddin.cs file.
    3. This is how the file looks at the beginning. If you notice the file contains a lot of information about how to use it:
    using Microsoft.Dynamics.AX.Metadata.Core;
    using Microsoft.Dynamics.Framework.Tools.Extensibility;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Automation;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Automation.Forms;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Automation.Tables;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Core;
    using System;
    using System.ComponentModel.Composition;
    using System.Linq;
    
    namespace FormPatternAddin
    {
        /// <summary>
        /// TODO: Say a few words about what your AddIn is going to do
        /// </summary>
        [Export(typeof(IDesignerMenu))]
        // TODO: This addin will show when user right clicks on a form root node or table root node. 
        // If you need to specify any other element, change this AutomationNodeType value.
        // You can specify multiple DesignerMenuExportMetadata attributes to meet your needs
        [DesignerMenuExportMetadata(AutomationNodeType = typeof(IForm))]
        [DesignerMenuExportMetadata(AutomationNodeType = typeof(ITable))]
        public class DesignerContextMenuAddIn : DesignerMenuBase
        {
            #region Member variables
            private const string addinName = "DesignerFormPatternAddin";
            #endregion
    
            #region Properties
            /// <summary>
            /// Caption for the menu item. This is what users would see in the menu.
            /// </summary>
            public override string Caption
            {
                get
                {
                    return AddinResources.DesignerAddinCaption;
                }
            }
    
            /// <summary>
            /// Unique name of the add-in
            /// </summary>
            public override string Name
            {
                get
                {
                    return DesignerContextMenuAddIn.addinName;
                }
            }
            #endregion
    
            #region Callbacks
            /// <summary>
            /// Called when user clicks on the add-in menu
            /// </summary>
            /// <param name="e">The context of the VS tools and metadata</param>
            public override void OnClick(AddinDesignerEventArgs e)
            {
                try
                {
                    // TODO: Do your magic for your add-in
                }
                catch (Exception ex)
                {
                    CoreUtility.HandleExceptionWithErrorMessage(ex);
                }
            }
            #endregion
        }
    }
    
    Code language: C# (cs)
    1. Resolve the TODO comments and remove the ITable metadata attribute from the class as we will implement this add-in only for forms.
    2. The onClick() method is the most important method in this file. This file is where the form control population will be implemented.

      For this article, I will implement the DetailsMaster form pattern, but you can create and add the SimpleList and SimpleListDetails patterns which are available at Hichem’s NinjaDevTools project on GitHub (https://github.com/HichemDax/D365FONinjaDevTools/blob/master/D365FONinjaDevTools/ScaffoldFormPattern/ScaffoldFormPattern.cs)
    using Microsoft.Dynamics.AX.Metadata.Core;
    using Microsoft.Dynamics.Framework.Tools.Extensibility;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Automation;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Automation.Forms;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Automation.Tables;
    using Microsoft.Dynamics.Framework.Tools.MetaModel.Core;
    using System;
    using System.ComponentModel.Composition;
    using System.Linq;
    
    namespace FormPatternAddin
    {
        /// <summary>
        /// This add-in allows to implement form pattern control auto-population
        /// </summary>
        [Export(typeof(IDesignerMenu))]
        [DesignerMenuExportMetadata(AutomationNodeType = typeof(IForm))]
        public class DesignerContextMenuAddIn : DesignerMenuBase
        {
            #region Member variables
            private const string addinName = "DesignerFormPatternAddin";
            #endregion
    
            #region Properties
            /// <summary>
            /// Caption for the menu item. This is what users would see in the menu.
            /// </summary>
            public override string Caption
            {
                get
                {
                    return AddinResources.DesignerAddinCaption;
                }
            }
    
            /// <summary>
            /// Unique name of the add-in
            /// </summary>
            public override string Name
            {
                get
                {
                    return DesignerContextMenuAddIn.addinName;
                }
            }
            #endregion
    
            #region Callbacks
            /// <summary>
            /// Called when user clicks on the add-in menu
            /// </summary>
            /// <param name="e">The context of the VS tools and metadata</param>
            public override void OnClick(AddinDesignerEventArgs e)
            {
                try
                {
                    var metaModelProviders = CoreUtility.ServiceProvider.GetService(typeof(IMetaModelProviders)) as IMetaModelProviders;
                    var metaModelService = metaModelProviders.CurrentMetaModelService;
    
    
                    var form = (IForm)e.SelectedElement;
                    var axForm = (AxForm)form.GetMetadataType();
    
    
                    switch (axForm.Design.Pattern)
                    {
                        case "DetailsMaster":
                            //<Main>
                            axForm.Design.AddControl(new AxFormActionPaneControl { Name = "MainActionPane" });
                            var myNavigationListGroup = new AxFormGroupControl { Name = "NavigationListGroup" };
    
                            // <NavigationListGroup>
                            myNavigationListGroup.AddControl(new AxFormControl
                            {
                                Name = "NavListQuickFilter",
                                FormControlExtension = new AxFormControlExtension { Name = "QuickFilterControl" }
                            });
    
                            myNavigationListGroup.AddControl(new AxFormGridControl { Name = "MainGrid" });
                            // </NavigationListGroup>
    
                            axForm.Design.AddControl(myNavigationListGroup);
                            var myPanelTab = new AxFormTabControl { Name = "PanelTab" };
    
                            var gridPanel = new AxFormTabPageControl { Name = "GridPanel" };
                            gridPanel.AddControl(GetFilterGroup("DetailsFilterGroup", "DetailsQuickFilter"));
                            gridPanel.AddControl(new AxFormGridControl { Name = "DetailsGrid" });
                            gridPanel.AddControl(new AxFormCommandButtonControl { Name = "DetailsButtonCommand" });
    
                            var detailsPanel = new AxFormTabPageControl { Name = "DetailsPanel" };
    
                            var titleGroup = new AxFormGroupControl { Name = "TitleGroup" };
                            titleGroup.AddControl(new AxFormStringControl { Name = "HeaderTitle" });
    
                            detailsPanel.AddControl(titleGroup);
    
                            var detailsPanelTab = new AxFormTabControl { Name = "DetailsTab" };
                            detailsPanelTab.AddControl(new AxFormTabPageControl { Name = "DetailsTabPage" });
                            detailsPanel.AddControl(detailsPanelTab);
    
                            myPanelTab.AddControl(detailsPanel);
                            myPanelTab.AddControl(gridPanel);
                            axForm.Design.AddControl(myPanelTab);
                            //</Main>
                            break;
                    }
    
                    var model =
                        DesignMetaModelService.Instance.CurrentMetadataProvider.Forms.GetModelInfo(axForm.Name)
                            .FirstOrDefault();
    
                    metaModelService.UpdateForm(axForm, new ModelSaveInfo(model));
                }
                catch (Exception ex)
                {
                    CoreUtility.HandleExceptionWithErrorMessage(ex);
                }
            }
    
            private static AxFormGroupControl GetFilterGroup(string filterGroup = "FilterGroup",
                string quickFilter = "QuickFilter")
            {
                var filterGrp = new AxFormGroupControl
                {
                    Name = filterGroup,
                    Pattern = "CustomAndQuickFilters"
                };
                filterGrp.AddControl(new AxFormControl
                {
                    Name = quickFilter,
                    FormControlExtension = new AxFormControlExtension { Name = "QuickFilterControl" }
                });
                return filterGrp;
            }
    
            #endregion
        }
    }
    
    Code language: C# (cs)
    1. Now build the solution. Then, go to your solution folder using File Explorer look for the bin/Debug folder and copy the DLL into the addin extensions folder of the Visual Studio D365 extension.

    Tip: to know where your D365 extension is located I usually open the code snippet manager on Tools > Code snippet manager > Select X++ as the language and select the Snippets folder (not the User snippets) copy the path and then paste it on a file explorer window.

    1. Restart Visual Studio and enjoy your newly created Development Add-in 😊.
    2. Create a new form, then when the designer opens, right-click on the form name > Addins > and look for the name you gave to your form scaffolding add-in 😊
    3. Enjoy!!!

    Conclusion

    Thanks to projects like NinjaDevTools our X++ development day-to-day work gets a bit easier. Visual Studio 2022 brought some changes, and I did not want to lose the utilities I had used for almost 7 years.

    You can also use Hichem’s project to create your own development add-ins and simplify your work at the same time.


    Thanks for checking up and happy coding😊