Skip to main content

πŸ’‘ X++ Tip: Keep the Same Record Selected After Refresh in a Form or Data Source

 When you refresh a form data source in Dynamics AX or Dynamics 365 for Finance and Operations, the current selection is often lost — the cursor jumps back to the first record. This can be frustrating, especially if you want the user to remain on the same record after an update or refresh. Here’s a simple and reliable way to restore the cursor to the same record after the data source is refreshed. 🧩 Scenario Suppose we have a form displaying records from MyTable . When we refresh the data source — for example, after updating a field or calling executeQuery() — we want the form to return to the same record the user was viewing before the refresh. ✅ Solution: Use ds.cursor() We can achieve this using a record buffer and the cursor() method of the data source. public void refreshDataSource() { MyTable myTableRec; // buffer to store current record // Store the current record myTableRec = MyTable_ds.cursor(); // Refresh the data source MyTable_ds.r...

How to Open a Form with Filtered Data Using a Button in X++ – A Step-by-Step Guide

 In Dynamics 365 for Finance and Operations (D365FO), a common requirement is to open a form dynamically from another form and pass filtered data based on a specific condition. This functionality can enhance user experience by allowing them to interact with multiple forms seamlessly, while keeping the data relevant and focused.

In this blog, we’ll explore how to implement such a solution using X++, where a user clicks a button on Form 1 (such as a list of sales orders), and based on a selected record, Form 2 (such as invoice details) opens with only the relevant filtered data.

Scenario Overview

Let’s assume the following scenario:

  • Form 1: Displays a list of sales orders, and each order has an OrderID, CustomerID, and OrderAmount.
  • Form 2: Displays details of invoices (from the InvoiceDetails table) that are linked to the selected OrderID from Form 1.

The goal is to click a button on Form 1, pass the OrderID to Form 2, and display only the relevant invoice records related to that OrderID.

Step 1: Set Up the Button Logic on Form 1

The first part of this solution involves creating a button on Form 1 (the sales order form). When the button is clicked, it will pass the selected OrderID to Form 2, filtering the data based on that ID.

Code for Button Logic on Form 1:


class InvoiceDetailsButton
{
    public void clicked()
    {
        super();
        SalesOrder salesOrder;
        MenuFunction menuFunction;
        InvoiceDetails invoiceDetails;
        Args args = new Args();
        FormRun formRun = this.formRun();
        int64 orderID;
        List filteredInvoices = new List(Types::Record); // List to store filtered invoice records

        // Validate that the form has a valid data source
        if (formRun && formRun.dataSource(formDataSourceStr(SalesOrderForm, SalesOrder)))
        {
            salesOrder = formRun.dataSource(formDataSourceStr(SalesOrderForm, SalesOrder)).cursor();

            if (salesOrder)
            {
                orderID = salesOrder.OrderID; // Retrieve the selected OrderID
            }
            else
            {
                warning("No order selected in SalesOrder.");
                return;
            }
        }
        else
        {
            warning("SalesOrder data source not found on the form.");
            return;
        }

        // Query to filter data from InvoiceDetails
        Query query = new Query();
        QueryBuildDataSource qbds;
        QueryBuildRange qbr;

        qbds = query.addDataSource(tableNum(InvoiceDetails));
        qbr = qbds.addRange(fieldNum(InvoiceDetails, OrderID)); // Add a range filter for OrderID
        qbr.value(queryValue(orderID)); // Use the OrderID as the filter value

        QueryRun queryRun = new QueryRun(query);
        while (queryRun.next())
        {
            invoiceDetails = queryRun.get(tableNum(InvoiceDetails));
            filteredInvoices.addEnd(invoiceDetails); // Add record to the list
        }

        if (filteredInvoices.elements() == 0)
        {
            warning("No matching invoice details found for the selected order.");
        }
        else
        {
            // Pass all filtered data to the target form
            args.parmObject(filteredInvoices); // Pass filtered invoices to Form 2
            args.caller(formRun); // Pass the original form context

            menuFunction = new MenuFunction(menuItemDisplayStr(InvoiceDetailsForm), MenuItemType::Display);
            menuFunction.run(args); // Launch Form 2 with filtered invoices
        }
    }
}



Explanation of the Code for Form 1:

  1. SalesOrder salesOrder: Represents the selected sales order from the SalesOrder data source.
  2. orderID: The OrderID of the selected sales order is extracted, which will be used to filter the invoice records.
  3. Query: We create a query to filter data from the InvoiceDetails table based on the OrderID.
  4. filteredInvoices: The filtered invoice records are stored in a list and passed to Form 2.
  5. args.parmObject(filteredInvoices): This method passes the filtered invoices to Form 2.
  6. menuFunction.run(args): This function launches Form 2, passing the filtered data along with it.

Step 2: Handle the Data in Form 2

Now, let's set up Form 2 (InvoiceDetailsForm) to receive the filtered data and display it accordingly.

Objective: When Form 2 is opened, it should receive the filtered list of invoice details (based on OrderID), and apply the necessary filter to the data source.

Code for Form 2 (InvoiceDetailsForm):


public class InvoiceDetailsFormRun extends FormRun
{
    public void init()
    {
        super();

        List filteredInvoices;
        int64 orderID = 0;

        // Check if the calling form has passed filtered data (InvoiceDetails)
        if (this.args().parmObject())
        {
            filteredInvoices = this.args().parmObject() as List;

            if (filteredInvoices)
            {
                ListEnumerator enumerator = filteredInvoices.getEnumerator();

                // Iterate through the filtered list and extract the OrderID
                while (enumerator.moveNext())
                {
                    InvoiceDetails invoiceDetails = enumerator.current() as InvoiceDetails;
                    if (invoiceDetails)
                    {
                        orderID = invoiceDetails.OrderID; // Get the OrderID from each record
                    }
                }
            }
            else
            {
                warning("No records found in the filtered list.");
            }
        }
        else
        {
            warning("No data received from the caller form.");
        }

        // Apply filter to the form's data source (InvoiceDetails)
        if (orderID)
        {
            FormDataSource formDatasource = this.dataSource();

            if (formDatasource)
            {
                QueryBuildDataSource qds = formDatasource.queryBuildDataSource();
                qds.addRange(fieldNum(InvoiceDetails, OrderID)).value(SysQuery::value(orderID)); // Filter by OrderID
            }
            else
            {
                warning("DataSource not found.");
            }
        }
        else
        {
            warning("No filter value (OrderID) found.");
        }
    }
}


Step 3: Testing the Solution

Once the button logic is set up on Form 1 and the filtering mechanism is implemented on Form 2, you can proceed to test the solution:

  1. Form 1: Select a sales order from the list, and click the button.
  2. Form 2: After clicking the button, Form 2 should open and display only the invoice records related to the selected OrderID.

Tips for Effective Implementation:

  • Error Handling: Always ensure proper error handling is in place to handle scenarios where data is not passed correctly or records are not found.
  • Optimizing Queries: If the dataset is large, consider optimizing your queries to improve performance, using indices and limits where applicable.
  • User Feedback: Use informative messages (e.g., warning()) to notify users if no records are found, or if there are issues with the data passed.

Conclusion

In this blog, we’ve demonstrated how to dynamically open and filter forms in Dynamics 365 FO using X++. We used a practical example where a user selects a sales order on Form 1, and upon clicking a button, Form 2 opens with filtered invoice details related to the selected OrderID.

By following this pattern, you can pass filtered data between forms seamlessly, improving the user experience and making the data more relevant and accessible.

Comments

Popular posts from this blog

How to Refresh a Form or Data Source in D365FO Using X++

  Introduction In Microsoft Dynamics 365 Finance & Operations (D365FO), refreshing the form after an action (like inserting, updating, or deleting a record) is essential for keeping the UI updated with the latest data. In this blog, we’ll explore two ways to refresh the form in X++: ✅ Refreshing the entire form using taskRefresh ✅ Refreshing a specific data source using research Let's dive into the best practices for implementing these refresh methods! πŸ”„ Refreshing the Entire Form If you need to refresh the whole form , use the taskRefresh method. This method is useful when multiple data sources are involved, and you want to reload everything. πŸ“Œ X++ Code for Full Form Refresh public void refreshForm() {     // Get the current form instance     FormRun formRun = this.formRun();     // Check if formRun is valid before refreshing     if (formRun)     {         info("Refreshing the form...");     ...

Sorting Data in X++ (D365FO) Grids Using Form Data Source Events

  Introduction : In Dynamics 365 Finance and Operations, form grids often display data retrieved from a table or query. However, the default sorting applied may not always align with business requirements. Customizing sorting behavior at runtime ensures that the grid data is presented in a meaningful order for users. This blog post demonstrates how to use the Initialized event of a form data source to apply custom sorting to a grid. We will sort the grid rows based on a specific field ( DisplayOrder ) in ascending order. Understanding the Code : Here’s the complete code snippet: --------------------------------------------------------------------------------------------------------------- [FormDataSourceEventHandler(formDataSourceStr(MyFormDataSource,  MyTable), FormDataSourceEventType::Initialized)] public static void MyFormDataSource_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e) {     // It will clear if any other sorting is applied on the grid ...