Code highlighting

Showing posts with label Form Development. Show all posts
Showing posts with label Form Development. Show all posts

Saturday, October 07, 2017

Development Tutorial: Extensibility: Adding a table display/edit method and showing it on a form in PU11

In my previous post I described the capabilities of the Dynamics 365 FOE platform update 10, when it comes to working with display/edit methods.

In Platform Update 11 a few improvements came out, which I will describe below (with an example).

Let's use the same example as in my previous post, and add a new display method showing the internal product name for a selected product - we'll compose it by appending some text to the product search name.


Step 1 - Create a table extension and add a new display method to it - New recommended approach


All the approaches described in the previous post are still applicable, but are, in my opinion, not as intuitive as the below, so I'd recommend to always use the below approach.

[ExtensionOf(tableStr(EcoResProduct))]
public final class MyPU11_EcoResProductExtensionOfTable_Extension
{
    [SysClientCacheDataMethod]
    public display Name myInternalProductName()
    {
        return "PU11: " + this.SearchName;
    }

    // This is same as in PU10
    [SysClientCacheDataMethod]
    public static display Name myInternalProductNameStatic(EcoResProduct _ecoResProduct)
    {
        return "PU11 static: " + _ecoResProduct.SearchName;
    }
}
K/code>

OK, so what do we have here?
A simple instance method, no redundant arguments, access to table fields and methods through this.
Just as with overlayering, there's really no difference.

NoteThe logic of the method is not really important - you'd have your fields used here, most probably, but for the sake of the example I just use SearchName field.

Step 2 - Add the method to a form through a table field group


So now let's see another thing, which was not possible before PU11.

Let's create an extension for the metadata changes of the EcoResProduct table, and add a new Field Group. After that we'll put our new display method in it, as shown below.

Select the new display method as the source for the new field group field.
As you can see from the image above, you can now use the drop-down, and it will show you all the display/edit methods created in Extension/Augmentation classes as well as the standard ones.
The syntax is the same as described last time:
  • :: for static methods
  • . for instance methods (new)
All that's left is to add the new field group onto a form.

Step 3 - Add the methods to a form through extension


Same as before, we create a form extension, and add the new fields to it.
With field groups it is as easy as with overlayering - you can just drag and drop the new field group from the DataSources\EcoResProduct node onto the Design of the form, and it will create the new group and sub-controls, and set the appropriate properties on them, as shown below:

Add new field group onto the form extension
As easy as that.

You can also add the fields bound to the new display methods directly, as shown before. Except now you can also use the instance display/edit methods, which wasn't available earlier.

Add an instance extension display method to a form


Limitations

  • The drop-down for display methods does not show the extension methods, as with field groups. This should be addressed in one of the upcoming updates.
  • You are still not able to declare the display method on the form itself, or on the form data source.

Conclusion

Some last words: The future is bright! :) 
The tooling is getting better with every release, and thanks to the monthly updates and binary compatibility of the releases, new innovation from Microsoft is just a month away!

Links

You can download the project from the example from my OneDrive.

To review the list of features included in Platform update 11, see the What's new or changed topic and refer to the KB article for details about the customer found bug fixes included in this update.

Thursday, September 28, 2017

Development Tutorial: Extensibility: Adding a table display/edit method and showing it on a form in PU10

One of the super common tasks for an application developer working to address customer requirements is adding display methods showing some additional customer-specific information on existing forms.

Usually you would overlayer the corresponding table and form and insert the missing method. Overlayering is not an option soon, however it is possible to do the same using only Extensions.

As an example, let us add a new display method showing the internal product name for a selected product - we'll compose it by appending some text to the product search name.

Step 1 - Create a table extension and add a new display method to it - Option 1


As you know, there are two ways to create extension classes now, so let's see both ways in action.
Here we'll look at the "old" way, where we create an actual extension class (as in .NET), so it must be static, and the display method must be static as well, and take the record as the first argument.

Here's how it looks for my example:

/// 
/// Extension class for EcoResProduct table.
/// 
public static class MyPU10_EcoResProductTable_Extension
{
    [SysClientCacheDataMethod]
    public static display Name myInternalProductName(EcoResProduct _ecoResProduct)
    {
        return 'IntName: ' + strReplace(_ecoResProduct.SearchName, ' ', '');
    }
}

As you can see, we can define the above method and it will compile even though normally declaring a static display method is not allowed by the compiler.
We can also decorate the method with attribute, like I have done here by applying the display method caching attribute.
The logic of the method is not really important - you'd have your fields used here, most probably, but for the sake of the example I just use SearchName field.

Step 2 - Create a table extension and add a new display method to it - Option 2


So, another "new" way to extend a table is through an augmentation class, using the ExtensionOf attribute. This is shown below for my example:


/// 
/// Extension class for EcoResProduct table.
/// 
[ExtensionOf(tableStr(EcoResProduct))]
final class MyPU10_EcoResProductExtensionOfTable_Extension
{
    public static display Name myInternalProductName(EcoResProduct _ecoResProduct)
    {
        return "Alt: " + _ecoResProduct.SearchName;
    }
}

As you can see, this again is a static method - declaring it as an instance method will compile and would allow you to reference the record through this, but you will not be able to use it as a display method on the form as of today.
The method also needs to take the record as the argument.
It, of course, can also be decorated with the SysClientCacheDataMethod attribute, as in the first example.

Step 3 - Add the methods to a form through extension

Note - Limitation

You cannot as of today add the newly created display methods to a field group on the table. It will compile, but the control will not show up on the form if you add the field group to it.


First off, we'll need to create an extension of the EcoResProductDetails form in the desired model.
Then we'll add a new tab page to it and place two new String controls into it.

Now, the trick is with how to specify the display method name in Properties.

See the example below:

Specify properties for form string control to bind it to a table data method
As you can see, the trick is to specify the full name, including the class name and the method name with the static method delimiter in the format:

<class name>::<static method name>

This is only supported for table methods, so you won't be able to do the same for a Form Data Source, for example.

Result

Here's how our new awesome display methods look at run-time:

Additional information shown through display methods on Product details form

Download the project

You can download it from my OneDrive here.

What's next

In an upcoming platform update we hope to provide a much more intuitive way of adding display methods, however the above approach will keep being supported.
Stay tuned for an update!

Friday, August 12, 2016

Development Tutorial: FormObservable or how to handle display methods in the new Dynamics AX

In the new Dynamics AX the approach to how display/edit methods are refreshed by the kernel has changed to much less aggressive, meaning that after migrating your application to the latest version you might find that after opening the form the data is correct, but after performing actions and editing data the display/edit methods data is not refreshed.

This blog post is going to explain, how to handle such cases now.

I want to stress the importance of reading about this, since this is completely new (among many other things) in AX '7', and without full understanding of these mechanics you will not be able to write efficient and correctly working user interface code.

To summarize, here's a quick set of rules:
  • If your display method depends on form state or some data event, you might need to use one of the following two approaches:
    • The FormObservable attribute to flag an existing form or class variable
    • A new variable of the type FormObservableLink
  • If your display method depends on another data source, you might need to use the following approach:
    • The datasource.observe() method
As you can see from the names of these types, they are relying on the Observer pattern, subscribing to notifications about data changes, so as to invoke the refresh of the display/edit method data.


When I decided to finally write this blog post, I found out there's already somebody who beat me to it, so instead of repeating the same, here's a link, where you can read in more detail about each of the 3 above options:

FormObservable in Dynamics AX7

Enjoy, and kudos to the author of that blog.

Monday, May 11, 2015

Tutorial: Caching display methods

Overview

Caching is an important part of the Microsoft Dynamics AX application. It allows us to significantly speed up certain processes by avoiding redundant round trips to the server and the database. Display/Edit methods are a good example of where caching is helpful, as it significantly increases the overall responsiveness of a form.
In AX 2012 R3 there are a number of ways you can specify that a display method needs to be cached. Below is a list, as well as a brief description and an xpo with a tutorial form to show the various options.
  • Invoke cacheAddMethod(MethodName) in the init() method of the data source
This is the only option that was available in older versions of Dynamics AX.
All cached data methods are calculated and cached in executeQuery of the data source they belong to. That is why it is important to call the above method in the init() of the data source, once the data source is initialized, but the data hasn't been retrieved yet. The optional second parameter controls whether or not the display method value should be refreshed when data is written to DB
  • Add the SysClientCacheDataMethodAttribute attribute on the display method
Now that attributes are available, caching a display method is as easy as adding an attribute on the method. It has an optional parameter same as the above method. Note, that marking a display method with this attribute will mean that it is cached on all forms the method is used on. So make sure that is what you want to happen before adding it.
  • Set the “Cache data method” property on the display method form control to Yes
This is, again, a new capability in the latest version of AX, that is pretty much identical to calling the cacheAddMethod, just much easier.
  • Set the “Cache data method” property on the display method form control to Auto, where the data source is read-only
Now, this is something that is done automatically by the kernel. If the data source which the display method belongs too is read-only, and Cache data method property on the corresponding control is set to Auto, the kernel will cache this method. This is smart, as the value can’t really change. You can still change the property to No, if there’s a reason why you want the display method not to be cached.

Additional info

On top of the above, I would also like to mention a few other things.

  • There are 2 kernel-defined methods, called titleFields and parentTitleFields, which are always being cached automatically by the kernel. The titleFields method corresponds to the 2 Title Fields that you can set on the table level. Then, if you have this table as your TitleDatasource on the form design, the 2 fields selected will be shown in the caption of the form. parentTitleFields is very similar, only it displays the title fields of the parent data source table, which is relevant in a header-lines (dynalinked datasources) kind of scenario. As a fallback, it shows the titleFields.
  • There are methods for managing the cached methods, one of them being cacheCalculateMethod, which asks the server to re-calculate the method value and update the cache for the current record. Note that it will only take effect if the method has previously been cached. In the tutorial project I am using this method in the form method runHack to refresh the value of 2 of the display methods. 

Example

You can download the xpo project with the tutorial from my OneDrive.
Upon opening the form in the project, some data will be pulled into the form. More specifically, the first 3 items/customers/vendors/sites/warehouses. For each of them a display method is present, and one of the above approaches is used to cache it.

Step 1



None of the display methods are cached. As you can see, nothing extra was done in executeQuery of the main data source, but itemName was cached for all 3 records for the read-only data source, as the control has "Cache data method" property set to Auto. This happens even though the tab page is not the active one, so it wasn't really yet necessary to calculate these values. Maybe the user won't even navigate to that tab page. You should consider this carefully when designing a form, and always aim at increasing the overall responsiveness of a form by only executing the appropriate display methods.

Step 2 and 3



inventSiteName and vendName were cached (through one of the options above). As you can see, now their values are calculated once for each of the records shown. InventLocationName and custName are still not cached.
Clicking on Refresh result button will show any extra calls to the display methods since the form started. As you’ll quickly notice, the non-cached methods are being called every time something hints at covering up the value of the control, or rather every time AX thinks it needs to recalculate the value, which is VERY OFTEN. That should make it pretty clear how useful caching is for display methods.

Step 4



Now inventLocationName was also cached, and the only method that is remaining and not cached is custName. You can see how more and more methods are executed in one batch on the server as part of the executeQuery method on the data source.

Step 5



As the last step, I have added 2 standalone controls that show the Title fields of the main data source table, and started logging the time method caption is invoked. As I have written above, titleFields and parentTitleFields are cached automatically, but you can't really see it, as it is not possible to override it. caption is the closest you can get to it, so I figured it's worth showing that. You can see the method is evaluated right after all data source queries have executed (as it relies on the currently selected record). It is evaluated first time based on the selected data source record, and then again when focus actually goes to the grid, so that the first record is explicitly selected. It will be invoked again whenever the active record changes. Note that unlike custName method, it does not get constantly refreshed.

Play around with the form and let me know if you have any questions or comments.
Thanks

Friday, January 16, 2015

Tutorial: InventDimCtrl_Frm classes and the amazing things you can do with their help

Preamble

I was making some adjustments to one of the forms in the application today. Specifically, it was the PdsCustSellableDays form, which is a setup form you can open for a selected customer through the Sell\Setup\Sellable Days action pane button. You can read more about this functionality here:

Working on these changes brought back the memory of what it is that I have always enjoyed most about developing customizations in AX: Making a lot of difference with only a few small targeted code changes.

So I was inspired to use this as an example for describing some of the capabilities of the InventDimCtrl class hierarchy. There are a lot of classes in the hierarchy, targeted to various forms in the application that display Inventory dimensions one way or the other. The main purpose of this class hierarchy is to control the display and behavior of the inventory dimensions as well as the “Dimension display” button that is normally also present on such forms.

Most of you probably know that the way AX developers early on decided to handle inventory dimensions is through a “reference” table InventDim, and a lot of generically written code that would heavily exercise reflection and traversing form controls. This seemed like a good idea at that time, I bet, but over the years, in my opinion, it got completely out of control, and now is a pretty large overhead for almost all inventory related operations in our product. But, at the same time, it provides a nice level of flexibility to how dimensions are displayed on a particular form.

That being said, let’s start from the beginning and briefly explain the existing form behavior and the things I was set to change in it.

Existing behavior and task

Overview of the existing behavior

  • Only product dimensions are displayed in the main grid
  • The relationship to the products is implemented via a “standard” pattern of TableGroupAll. So you can set up Sellable days for a specific item, an item group, or all items.
  • Viewing Inventory\Dimension display shows the standard dialog with all inventory dimensions. However, storage and tracking dimension check-boxes are disabled for editing.
  • Upon adding a grid row and selecting a released product master, all product dimension values are shown, not just those from the released product variants.
  • Only the product dimensions active for the selected product are enabled for editing. If Item code is set to Group or All, all the dimensions are disabled for editing.

Original version of the form

Task

  • Storage and tracking dimensions cannot be selected in Dimensions Display, so should not be shown at all
  • Lookup for configuration and other product dimensions should only show those relevant for the released product master.

 

Explanation

OK, so let’s take a closer look at how such types of forms are built

  • A form-level variable of type InventDimCtrl_Frm is declared. This object is used to control the behavior of the inventory dimension controls on the form.
  • A form method called inventDimSetupObject simply returns the above reference. Through this method other AX forms and classes can access the current state of the form when it comes to the dimensions
    • This includes information about which inventory dimensions are visible in the grid, which of them are enabled for editing, which of them should be marked as mandatory, which of them should be shown when the Dimension Display button is pressed, whether or not the “large” version of the dialog is to be shown (that includes On-hand information like Closed/ClosedQty), whether Item number should be shown on the Dimension Display dialog, etc.You can browse the methods on this class to get a feeling for what else it can do.
    • Since we can only select product dimensions in the form, we want spread that over to the Dimension Display dialog as well.
  • A form method called updateDesign() exists, with a certain pattern for handling various “events” that happen on the form. The main purpose here is to update the inventDimSetupObject with the latest information from the form, and through it refresh the display of the inventory dimension controls.
    • In the PdsCustSellableDays form this happens when the form is opened (Init), whenever the item code or reference is changed (FieldChange), whenever the active record is changed (Active) or whenever the parent customer record is changed in turn refreshing the Sellable Days view (LinkActive).
      • This method is then called from the appropriate event handles or the form, as you can see if you open the form code.
    • In PdsCustSellableDays the intention is to only enable the active product dimensions for the selected item, and disable all of them if we selected all items or an item group instead. But only product dimensions are to be shown ever. This is achieved by using the following methods:
      • parmDimParmEnabled(InventDimParm) controls which dimensions can be edited
      • parmDimParmVisible(InventDimParm) controls which dimensions are shown
      • parmDimParmVisibleGrid(InventDimParm) controls which dimensions are shown in the grid.
      • parmDimParmLockedRightClick(InventDimParm) controls which dimensions can be selected through the Dimension Display button
      • formSetControls(boolean) actually executes the update of the inventory dimension controls based on the above settings. The first parameter controls whether the form is locked for the duration of the update (preventing unnecessary flicker, especially in cases where controls are being made visible/invisible in the grid).
      • formActiveSetup(InventDimGroupSetup), which is not used here, but is frequent in other forms, should be called when the active record is changed, and resets the enabled fields based on the now selected item dimension group information.
      • dimFieldsActive(InventDimParm) that is also used in the form is actually an inquiry-type method, and does not impact the state of the object in any way. Its purpose is to return a container of active dimension field IDs based on an InventDimParm buffer.
  • Lookups for all the product dimensions are based on the form called InventProductDimensionLookup, which is another example of generic handling of inventory dimensions, and has all the above properties as well.
    • It can show up to 3 tab pages, including the selected product dimension values, all relevant product variants and the on-hand information
    • In the current version of the form, all existing product dimension values are shown, regardless of the product. This is one of the things we need to fix.

 

Changes made and result

OK, so in order to accomplish the 2 tasks we set for ourselves above, we only need to change code in 3 places on the form. Let’s walk through each one in turn.

  • Modify the classDeclaration, and replace the type InventDimCtrl_Frm with InventDimCtrl_Frm_ProductDim. If we take a look at this class (it is only available starting from AX 2012 R3 CU8, so I am also including the code for this class below, for reference), we can see a few methods were overridden:
    • mustEnableField() makes sure that only the active product dimensions based on dimension group setup are enabled. This is used by the formActiveSetup method, and is not as interesting for our example.
    • mustShowGridField() makes sure that all product dimensions are shown in the grid, and only product dimensions. This was controlled by explicitly calling parmDimParmVisibleGrid() before. We’ll remove that code, as you’ll see later.
    • setupShowAllProductDimensions() – this method is important for our example. This method, if it returns true, will ensure only product dimensions are shown on the Dimension Display dialog.
  • Modify the updateDesign method as below
    • Remove the lock/unlock method calls, since this is already going to happen within formSetControls().
    • Initialize the inventDimSetupObject using the right class InventDimCtrl_Frm_ProductDim
    • Remove the variables inventDimParmDefault and inventDimParmDisenabled, and all code related to them. This code is now redundant.
    • Remove the call to dimFieldsActive. As I mentioned above, it does not do anything useful for us.
    • We can also add 1 extra line clearing the inventDim values that do not apply – this is important for when changing the item number where we already have product dimensions selected. Just a bug fix, not really related to this tutorial.
  • Add a new method itemId(), which returns the currently selected item number
    • This is necessary for the product dimension lookups to function properly. The lookup tries to find the item information from the calling form through the data source joined to InventDim as a parent, assuming it has an ItemId field. While that is true in most cases, our form uses a TableGroupAll pattern, so the field is a bit different. By creating an itemId() method on the form, we ensure that the lookup can find the right item number to reference when retrieving product dimension group information.
    • In updateDesign, after initializing the object, invoke parmSkipOnHandLookUp(true), which will ensure that the on-hand tab page is not shown on the lookups. Since this is not necessary for this type of setup form, it’s a good idea, since it will improve the lookup loading time. This is also demonstrating how the lookup form uses the inventDimSetupObject of the calling form to get the expected behavior settings.
    • The lookup will now also show the Combinations tab page, if the selected product has more than 1 product dimension active.

That’s it. With very few changes, we were able to drastically change the look and feel of this form. Here’s how it looks now:

Updated form behavior

 

Download the project

You can download the project with the changes I have made from my OneDrive.

Friday, December 19, 2014

Tutorial: Auto-Complete functionality on form controls


I guess all of you are familiar with the Auto-Complete feature present in Dynamics AX. Whenever you type in a repeated value in a control on AX forms, an automatic suggestion pops up next to the entered characters and the user can choose to accept the suggestion (for example, by tabbing out of the control).
There are methods on the form (specifically, the FormRun class) that provide you with a more tight control over the auto-completion list available to the user, and this blog post will describe the behavior of these methods, as there are a few not obvious things there.

Method signature Method behavior description
getAutoCompleteString(int _elementIndex) Retrieve the auto-complete string saved under the specified index in the auto-completion list of the form.
The indexing is not control-specific, and is not very efficient at keeping the number of values in the list low, so you will see values from multiple controls here.

The method returns a container containing 2 values:
- the auto-completion string value
- the index of this string in the auto-completion list
getAutoCompleteString(int _startIndex, FormControl _control, str _searchStr) Retrieve the auto-complete string for the specified control, finding the first one in the list starting from the specified startIndex + 1.

If _searchStr is also specified, it will find the first matching entry in the list with index > startIndex. This is the closest behavior to what happens when a user types in a certain character in the entry control on the form and the kernel needs to search and populate the auto-complete value for it.

Note: There’s a small bug in this function, where it does not return the full string value for longer texts. A workaround is to use the above “overload” and retrieve the full value by index.
setAutoCompleteString(string _string, anytype _control) The first argument is clear, and that is the auto-completion string to add to the list of suggestions.

The second argument is presented as _control, but can accept either an instance of a FormControl, or an integer value of the list item index to set.

If the _string already exists in the auto-completion list for this control, nothing will happen. If it does not exist, a new item will be added to the list (number of items cannot exceed 300) with the next available index.
setAutoCompleteString(string _string, anytype _index) The auto-completion list item at index _index will be updated in the list with the specified _string value.

This method “overload” basically allows you to override a particular value in the list with a new value.
delAutoCompleteString() Deletes all auto-completion items from the list, regardless of which control they are bound to, if any.
delAutoCompleteString(anytype _index) Deletes the item with the specified index from the auto-completion list.
delAutoCompleteString(anytype _control) Deletes all auto-completion items for the specified control from the list.

When a value in one of the controls on the form is modified, this value is also automatically added to the auto-completion list. With the above methods, however, you can control, if this value should actually stay there, or be immediately removed.
This way you can, for example, restrict the user to only be provided with auto-completion hints from a certain list (similar to IntelliSense).

I created a simple form to demonstrate all the above methods in action.

Tutorial auto-complete capabilities form
Tutorial form to demo auto-completion capabilities

You can download the form from my OneDrive.


Apart from the above methods, there is also a global setting which allows you to control auto-completion list availability for a particular user. This is control on the User options screen, as highlighted in the below image:

Autocomplete in User options
User options form


If this check box is not set, auto-completion suggestions will not be shown to the user when entering data on forms, even though the auto-completion list is still maintained behind the scenes.

Friday, October 28, 2011

Tutorial: AX 2012 - Invalid field access or Accessing unretrieved fields

Read the post all the way until the end - there's an ask for you guys there!

As many of you have already seen in the new AX 2012 release, there are a lot more tables present in the application, because we went through an exercise of normalizing the data model.
Another feature that was added at the same time was the Table Inheritance, allowing to sub-class tables and add additional fields reusing some of the behaviors of the base table.

All of it is great! The only problem with it is that it comes at a price - we now need to retrieve a lot more data and do a lot more joins between tables. So we tried to mitigate that by using field lists where possible and adding AdHoc query support, which basically eliminates unneccessary joins between tables from the hierarchy, if fields from these tables are not selected.

We have also implemented a number of things that allow us to clearly see if the field was selected in the user interface, the APIs needed for doing the same, as well as special handling for invalid field access.

This tutorial contains a form, which showcases the different aspects of data access in cases described above.
You can download the project with the tutorial from my SkyDrive.

On the first tab, we have the standard "On hand" view, which in the data model is a join between InventSum and InventDim, with group by clauses on selected fields, in this case, ItemId from InventSum and InventLocationId from InventDim, and aggregation on the AvailPhysical column.
As you can see from the screenshot below, the rest of the InventDim fields are shown as Unretrieved. So, naturally, accessing one of them from code, for example, should not be a legal operation.
To verify that statement, there are 2 buttons on the form, the Incorrect and the Correct was of accessing the field. Clicking on the first one simply tries to read the value out of the InventSiteId field, while the second one uses the API method TableBuffer.isFieldDataRetrieved() to first verify if the field can actually be accessed.

Note, that in the below example, both buttons will work, as in, there won't be any errors shown to the user. When accessing the field, even though it is not retrieved to the client, the value will be treated as the default value for that type, that is, an empty string.

In order to verify that we do not access fields in an invalid way like above, we have introduced a parameter, that will throw an exception if a field that was not retrieved is being accessed.

In order to enable this validation, you need to update the below shown parameter in the Server configuration form (Located under System administration \ Setup \ System). After changing the value (note, that it is per AOS) you need to restart the AOS for the changes to take effect.


Now, if you try to use the first button (under Incorrect) from above, you will get a stack trace, notifying you that the specified field was not retrieved.



We suggest that when testing your modifications in the application, you always have that flag enabled, so as to avoid unpleasant and hard-to-find bugs later on in the production environment.


On the second tab of the Tutorial form, the same type of information is presented, only this time the output is actually based on a new table inheritance structure I created.


The base table, GenericBall, contains 2 characteristics of any ball. SoccerBall is extending it and adding an additional characteristic (for the sake of the example, let's assume Brand is only relevant to soccer balls). This is basically a very simple table inheritance structure.

In the form, however, I am only selecting to view the Brand field from SoccerBall, not selecting the other 2 fields from the base table. As expected, they show up as Unretrieved in the user interface.
However, there is one difference in how Scsc tables are handled - and that is, they will always throw an exception when you try to access one of the unretrieved fields.
The two buttons above the grid demonstrate that. Try it out, enabling/disabling the server configuration parameter shown above.


That's pretty much all there's to it. Let me know if you have any questions.

Now, I have one thing to ask you all too.
We in the AX Test team have done our best to find invalid field access problems before the release, but if you find one using the approach above, please log it either through the standard Microsoft channels (partner/ MsConnect/ etc.), or as a comment directly under this blog post.

Thank you!

Monday, October 24, 2011

Tutorial: AX2012 - A new way of accessing the QueryBuildDataSource for a particular FormDataSource

When playing with some X++ code, I found an interesting addition that happened in AX 2012 related to form development.

There are 2 new methods that were added to the FormDataSource class, which allow you to very easily and error-free access the underlying QueryBuildDataSource, whether that is for the initial form query or for the queryRun that also contains user filters and sorting.

So, for example, instead of writing:

this.query().dataSourceTable(tableNum(InventTableModule), 2);
this.queryRun().query().dataSourceName(identifierStr(Purch));
....

you can use the 2 new methods:

this.queryBuildDataSource();
this.queryRunQueryBuildDataSource();


It might seem as a very minor improvement, but in reality it greatly simplifies maintenance of code on forms, when new datasources are added or existing ones removed/renamed.

That's all for today.

Sunday, October 09, 2011

Tool: Updating a field value for all selected records on a form

Update:
It looks like I was re-inventing the wheel here.
Turns out that a tool doing exactly the same (and even invoked from the same place on Record info form) already exists and is called "Fill Utility". The problem is that it's disabled by default in the License configuration, so it not available.
More information about how to get it and what it does can be found in this MSDN article


Problem statement:
During a recent customer visit we received a suggestion from the developers working on customizing the customer application to their needs. The suggestion was about being able to modify the value of a certain field on a form for multiple records at once.
As you know, in AX 2012, if we wanted to change a certain property for a number of entities (for example, update the default transfer order overdelivery allowence on a number of items at once), we would have to go through them one by one, which is time consuming and definitely not fun.

Solution:
Using the small example I created we can in a simple and intuitive manner update the selected field value for all the marked records.

It looks something like this:

1.    First, you multi-select the records where a certain field needs to be updated, and open the Record information for them


2.    Then, you click Update, select the field that needs to be updated and put in the new value


3.    After you confirm the changes, all the selected records will be updated with the new value.

Download:
Download the project from my SkyDrive


Issues:
These aren't really issues, just things I did not spend time on.
  1. I did not develop the idea of selecting multiple fields to update at once. Might be a good idea. I just did not want to add more code into this little project. There's however a nice UI for selecting 1 or more fields in the DEV_SysTableBrowser project (link)
  2. I did not spend enough time trying to figure out the validation that should be in place for this. I figured, if people are gonna use it, they know what they are doing.
  3. The project is based on AX 2012. There's only 1 method with code, the rest is form controls. I figured it should be pretty easy to port this to other versions if needed.
If people find this useful, I encourage them to add whatever else modifications. I can also re-post them here on request.


Thanks

Thursday, May 05, 2011

Tutorial: lockWindowUpdate() vs. lock()/unlock()

There are two method pairs in X++, that are used throughout the application by everyone writing some processing on application forms. These are:

element.lock();
element.unLock();


and

element.lockWindowUpdate(true);
element.lockWindowUpdate(false);


Now, not that many people know the difference between the two methods, and only very few think about why and when should each of them be used.
I will try to describe the behavior of these methods and at the end give some recommendations on how to use them. I have done some kernel code reading (with help of kernel dev. Andy Stach, who I would like to mention here), so what I write below is more or less backed up by code.
If you disagree with some of the recommendations though, please share your experience in using these methods through comments for this post.

FormRun.lockWindowUpdate()

is basically a wrapper around the LockWindowUpdate Win32 function. What it does is pretty simple:
When a window is locked, all attempt to draw into it or its children fail. Instead of drawing, the window manager remembers which parts of the window the application tried to draw into, and when the window is unlocked, those areas are invalidated so that the application gets another WM_PAINT message, thereby bringing the screen contents back in sync with what the application believed to be on the screen.
See the link on MSDN for a detailed description.
Note, that according to MSDN, it should not be used for general purpose suppression of redraw operations, but only when dealing with drag&drop operations. This does not hold true for AX, where this method is used all over the place to prevent redraw of controls on the form.
Another interesting point is that only one window can be locked at the same time. So, any nested calls to lockWindowUpdate will be ignored, but when unlocking, only the outer-most unlock will actually invoke the Win32 counterpart. Now, I have not seen this used in X++, which is for the better.

FormRun.lock()

is internally invoking lockWindowUpdate to prevent the redraw of the window, and then also prevents the IntelliMorph control layout engine from running. This is commonly used in scenarios where control properties affecting control arrangement are being set in a loop, which provides a performance optimization as it avoids redundant arrange calls being processed. On the other hand, when calling FormRun.unlock, more work will need to be done, compared to using lockWindowUpdate(false), where the control layout changes were actually processed by the layout engine, but simply not displayed.

So, based on my investigation, I would suggest to use the following recommendations when doing form development:

  • When formRun.resetSize() is used, specifically, when some controls become visible, increasing the form size, always use formRun.lock()/unlock(), otherwise the change in the size of the form might not get reflected on the screen correctly.
  • When changing multiple layout properties (Left, Width, etc.) on one or more controls, use lock/unlock
  • When you modify the properties that do not impact the layout of controls on the form, use formRun.lockWindowUpdate(), or, if there are only very few control properties being modified, do not lock the form window at all.

Thursday, July 01, 2010

Tutorial: Brief description of ways to close a form in AX

We had this question asked on one of the internal AX forums, and Michael Fruergaard wrote a short description of each method you can use.

Re-posting it here with some extra comments, so that new developers can read and understand, when to use what method.

There are “only” 5 ways to close a form:
  • Close - close the form. Similar to the 'X' button.
  • CloseOK – close the form, and set the OK flag – called by the Commandbutton::Ok
  • CloseCancel – close the form, and set the Cancel flag – called by the Commandbutton::Cancel
  • CloseSelectRecord – close the lookup form, and set return record
  • CloseSelect – close the lookup form, and set return value

The below methods (note their names are in past-tense) are used to determine if or how a form was closed:
  • Closed – Returns true, if the form is no longer open
  • ClosedOK – Return true, if the form was closed by the user clicking ‘OK’
  • ClosedCancel – Returns true, if the form was closed by the user clicking ‘Cancel’

Finally, CanClose() is called inside super() of any of the close methods. If CanClose() returns false, the form is not allowed to close.

Thursday, April 22, 2010

Tool: User preferred startup menu for Dynamics AX 2009

In AX 2009, the user has no control over which menu is opened in the navigation pane and address bar when AX client is started. Most of the time you simply get the Home page, which is frustrating for a number of users. Another thing that is frustrating for them is the company account the application opens with. The latter can actually be setup using standard application functionality in the User options form. Just set the 'Start company accounts' to the account you want for the user by default. I wrote a small tool, that allows the user to select a preferred startup menu, ensuring that this menu is the one open every time AX client is started. Something similar existed in Axapta 3.0 application. You can download the xpo for this tool from my SkyDrive. Note, that it contains minor changes to a number of existing application objects. I suggest that you compare the xpo from the import dialog and ensure that you don't override any of your changes during import - the best thing would be to re-implement these minor changes manually. Below is a list of changes that I am referring to above:
  • SysUserInfo table - 1 new field was added
  • SysUserSetup form - 1 new control was added. Lookup method on datasource field overridden
  • Info class - startupPost method changed
In order to enable the functionality, simply select one of the menus in the User options, as shown in the below screenshot. Next time you start AX, the selected menu will be open by default. Start Menu in User options, Microsoft Dynamics AX 2009 Below is a short listing of "code patterns" used in the project, that can serve as examples for your future projects:
  • SysTableLookup - for displaying a lookup form with a list of menus
  • infolog.globalCache() - for storing a global reference to the navigator class
  • TreeNode iteration - for finding all menu references in MainMenu
  • QueryBuild classes - for constructing and filtering a list of menu references in MainMenu, used in the lookup form
  • infolog.addTimeOut() method - for scheduling execution of another method in a set period of time

Friday, March 26, 2010

Tutorial: refresh, reread, research, executeQuery - which one to use?

X++ developers seem to be having a lot of trouble with these 4 datasource methods, no matter how senior they are in AX.
So I decided to make a small hands-on tutorial, demonstrating the common usage scenario for each of the methods. I have ordered the methods based on the impact on the rows being displayed in the grid.
You can download the xpo with the tutorial on my SkyDrive.

1. Common mistakes

Often, developers call 2 of the mentioned methods in the following order:
formDataSource.refresh()
formDataSource.research()

or
formDataSource.reread()
formDataSource.research()

or
formDataSource.research()
formDataSource.executeQuery()

or
formDataSource.research()
formDataSource.refresh() / formDataSource.reread()

All of these are wrong, or at least partially redundant.
Hopefully, after reading the full post, there will be no questions as to why they are wrong. Leave a comment to this post if one of them is still unclear, and I will try to explain in more detail.

2. Refresh

This method basically refreshes the data displayed in the form controls with whatever is stored in the form cache for that particular datasource record. Calling refresh() method will NOT reread the record from the database. So if changes happened to the record in another process, these will not be shown after executing refresh().
refreshEx
Does a redraw of the grid rows, depending on the optional argment for specifying the number of the record to refresh (and this means the actual row number in the grid, which is less useful for AX devs). Special argument values include -1, which means that all records will be redrawn, and -2, which redraws all marked records and records with displayOptions. Default argument value is -2.
This method should be used sparingly, in cases where multiple rows from the grid are updated, resulting in changes in their displayOptions, as an example. So you should avoid using it as a replacement for refresh(), since they actually have completely different implementations in the kernel.
Also, note, that refreshEx() only redraws the grid, so the controls not in the grid might still contain outdated values. Refresh() updates everything, since this is its intention.

3. Reread

Calling reread() will query the database and re-read the current record contents into the datasource form cache. This will not display the changes on the form until a redraw of the grid contents happens (for example, when you navigate away from the row or re-open the form).
You should not use it to refresh the form data if you have through code added or removed records. For this, you would use a different method described below.
How are these 2 methods commonly used?
Usually, when you change some values in the current record through some code (for example, when the user clicks on a button), and update the database by calling update method on the table buffer, you would want to show the user the changes that happened.
In this case, you would call reread() method to update the datasource form cache with the values from the database (this will not update the screen), and then call refresh() to actually redraw the grid and show the changes to the user.
Clicking buttons with SaveRecord == Yes
Each button has a property SaveRecord, which is by default set to Yes. Whenever you click a button, the changes you have done in the current record are saved to the database. So calling reread will not restore the original record values, as some expect. If that is the user expectation, you as a developer should set the property to No.

4. Research

Calling research() will rerun the existing form query against the database, therefore updating the list with new/removed records as well as updating all existing rows. This will honor any existing filters and sorting on the form, that were set by the user.
Research(true)
The research method starting with AX 2009 accepts an optional boolean argument _retainPosition. If you call research(true), the cursor position in the grid will be preserved after the data has been refreshed. This is an extremely useful addition, which solves most of the problems with cursor positioning (findRecord method is the alternative, but this method is very slow).

5. ExecuteQuery

Calling executeQuery() will also rerun the query and update/add/delete the rows in the grid. The difference in behavior from research is described below.
ExecuteQuery should be used if you have modified the query in your code and need to refresh the form to display the data based on the updated query.
formDataSource.queryRun().query() vs formDataSource.query()
An important thing to mention here is that the form has 2 instances of the query object - one is the original datasource query (stored in formDataSource.query()), and the other is the currently used query with any user filters applied (stored in formDataSource.queryRun().query()).
When the research method is called, a new instance of the queryRun is created, using the formDataSource.queryRun().query() as the basis. Therefore, if the user has set up some filters on the displayed data, those will be preserved.
This is useful, for example, when multiple users work with a certain form, each user has his own filters set up for displaying only relevant data, and rows get inserted into the underlying table externally (for example, through AIF).
Calling executeQuery, on the other hand, will use the original query as the basis, therefore removing any user filters.
This is a distinction that everyone should understand when using research/executeQuery methods in order to prevent possible collisions with the user filters when updating the query.

Tuesday, February 16, 2010

UtcDateTime in Dynamics AX 2009

In Dynamics AX 2009, Microsoft introduced a new data type, UtcDateTime, that is going to eventually replace the 2 existing types, Date and Time, which are still present in the application right now.
Obviously, the introduction of this new type requires a tutorial on how it can be used on forms, how you can filter on fields of this type, as well as what functions are available out of the box for it.
So I have made such a tutorial, and I hope it will be useful for developers upgrading to AX 2009.

Download the xpo for the tutorial from my SkyDrive

The tutorial consists of a single form, containing the following elements:
  • a grid, displaying data from CustTable
  • 4 buttons for various filtering actions
  • 3 controls that allow specifying the filtering conditions for the data
. In the form, you can see how UtcDateTime based controls are displayed both in a regular group and in a grid.

Dynamics AX UtcDateTime tutorial form

Below is an explanation of the implemented functionality, in form of a Question/Answer section:
  1. Q: Can I filter on the new UtcDateTime type, specifying the Date part only?
    A: Yes. You simply have to specify only the date part when applying the filter, like below. Note, that this also works fine when filtering directly from the UI (Ctrl+F).
    qbdsCustTable.addRange(fieldNum(CustTable, CreatedDateTime)).value(queryValue(DateFilter.dateValue()));
    What is interesting is how the kernel processes this range. In the below infolog, you can see that when viewing the query, it displays a "==" condition on a specific dateTime value.
    But in reality, as you can see from the SQL trace, a range ">= && <=" condition is applied to span exactly one day.
    Also note, that the values in the trace are displayed accounting for the TimeZone I am in, as well as for Daylight Saving Time

    SQL trace for Date filter on UtcDateTime field
  2. Q: Can I filter on the new UtcDateTime type, specifying the Time part only?
    A: No, this is not possible with a UtcDateTime type. The range applied when specifying a Time value is the minimum DateTime value, as seen below. Note, that in the SQL trace it is converted to "no range".

    SQL trace for Time filter on UtcDateTime field
  3. Q: Can I use similar query functions for UtcDateTime type?
    A: Yes. All the main existing functions for working with QueryBuildRange also support UtcDateTime. For example, in the infolog below you can see how a range on 2 UtcDateTime dates is applied. Global::queryRange method was used to achieve that. Note, again, that the SQL trace offsets the DateTime by the appropriate number of hours based on my location.

    SQL Trace for UtcDateTime range
  4. Q: How is the UtcDateTime stored in the database? Is it displayed the same way on forms?
    A: The UtcDateTime fields are in the database always stored in Coordinated Universal time (UTC). Whenever displayed on forms and bound to table fields, the data is converted to the user's preferred timezone. Note, that you need to take care of the conversion yourself, if the control is not bound to a field. For an example, see the init method of the tutorial form.
  5. Q: What standard helper functions are present for working with UtcDateTime type in the application?
    A: The main entry point for working with UtcDateTime type is the DateTimeUtil class. It allows adding Days/Months/Years, as well as applying an offset, getting the user's preffered timezone, converting from/to other types, etc. An example from the form init method is posted below:
        // getSystemDateTime() returns the current DateTime set in the system, not the current machine dateTime.
        // Note that getSystemDateTime() returns a UTC date and time, not your local date time.
        // In order to receive your local DateTime value, you should use methods applyTimeZoneOffset and specify the preferred time zone.
        utcDateTimeFilter.dateTimeValue(
            DateTimeUtil::applyTimeZoneOffset(
                DateTimeUtil::getSystemDateTime(),
                DateTimeUtil::getUserPreferredTimeZone()));
  6. Q: Does this mean that the support for Date and Time types has been removed?
    A: No, Date and Time are still supported. As you can see in the form init method, SystemDateGet(), timeNow(), today() are all still supported
  7. Q: I don't see the actual filter values in the SQL log. Instead, all I see are "?"'s. Also, how can I limit the number of data/fields selected from the database?
    A: This is just some extra stuff, not related to UtcDateTime, but still useful to know and pay attention to.
    CustTable has a very large number of fields, and I am only displaying 4 of those in the form, so it would be unwise to always query and return all of the fields. Luckily, the datasource has a property OnlyFetchActive, which controls the query behavior by only selecting the fields actually displayed on the form. Note, that you should avoid using this with editable datasources. See comments to this post for details
    As for "?"'s in the SQL trace - that is happening due to the use of placeholders. This in general optimizes the performance of the queries, by creating a query execution plan and storing it for future use. But it is possible, and is required in some specific cases, to force the use of literals (meaning the actual values of the ranges in the query). This can be done using the literals method on the query. See method init on the form for an example.

Monday, September 22, 2008

SysMultiTableLoookup - dynamic lookups based on multiple tables

Wow! MSDN for Microsoft Dynamics AX is getting better every day. This is terrific news!! I still remember the days, when all the information was extremely hard to find. It had its own charm though :)
Anyway, this topic is really not about MSDN. It is about lookup forms.

First of all, for the record: I (and the Best Practices document as well) recommend creating custom lookup forms in AOT instead of dynamically coding them in the overridden lookup methods on controls/datasource fields.
But, in reality, this is true only for lookup forms with very large complexity. I won't go into a discussion of why that is the way it is here. :)

Now, back to what I was planning to write about:
In order to build a lookup form from code, developers use the SysTableLookup class.
You can go to MSDN (mentioned above) to read a How-to article on creating a run-time lookup form, as well as take a quick look at the SysTableLookup method descriptions.

SysTableLookup class has evolved over the multiple releases, providing more and more flexibility and control to the application developers.
I would like to publish another extension to this class, SysMultiTableLookup, which I hope will prove useful to members of the AX community.

Short list of features:
- Backward compatible, should cover everything that is present in AX 2009 version of SysTableLookup class
- Allows including multiple tables into lookups with different join types
- Completely based on the Query that you build, no extra parameters (except for the control) are needed to initialize the class
- Allows adding aggregated fields to the lookup
- Displays fields based on Boolean Enum as check boxes
- Allows to specify alternative labels when adding fields to the lookup

New download link, as Axaptapedia seemed to mess up the file

You can download the project from axaptapedia.com.


It has (to some extent) been tested on Axapta 3.0 SP3, AX 4.0 and AX 2009.

Also included in the project is a tutorial form, showing 4 examples of dynamic lookups using the new class. After importing the project, make sure to try out the form, and use it for future reference for code examples and other inspiration.

Tuesday, May 13, 2008

AxPaint / AxAssist for Axapta 3.0, AX 4.0 and AX 2009

First of all, I would like to remind anyone interested of what AxPaint is. This is a small activeX component + a small xpo, that will allow you to change the background image of your application to whatever image you like.
You can find installation instructions, screenshot and a more detailed description through the link below:
http://kashperuk.blogspot.com/2007/06/axpaint-make-your-dax-look-cool.html

Well, the tool has recently been updated with support for all versions of AX starting with version 3.0.
The download link for the new (and, most probably, final) version of this tool is AxPaint.
Enjoy! :)

Also, it is hard not to mention another development tool from the same author, AxAssist. Sadly, it is not free, but it is still worthwhile to check it out - maybe that's something you company would be interested in buying. There is a Trial version you will be able to play around with for 30 days. The homepage for the tool is - www.axassist.com

Monday, April 28, 2008

SysFormEnumComboBox - class allowing to show only some of the values for a comboBox

In Microsoft Dynamics AX 2009, a new class has been created, that got my attention recently after yet another question about this sort of thing was asked by a fellow Dynamics AX developer.

Often enough, we need to restrict the user selection from a particular ComboBox. Creating a new BaseEnum specifically for this purpose is a really cumbersome solution, which later might lead to problems of maintenance.

Originally, the SysFormEnumComboBox was designed to be used on forms to provide for this behavior. I modified the class slightly to allow for usage in RunBase Framework classes.
To demonstrate, how this class can be used, I modified the tutorial_RunBaseForm class and form, showing both scenarios: adding the control from the dialog method of the class, as well as adding it manually in the form design and methods.

To use this class, you need to know only one static method:
public static SysFormEnumComboBox newParameters(
FormRun _formRun,
int _comboBoxControlId,
enumId _enumId,
Set _allowedEnumValuesSet,
Form _form = null)

And here is an example of how you would use it:
sysFormEnumComboBox = SysFormEnumComboBox::newParameters(element, 
control::ComboBoxOnForm,
enumnum(InventTransType),
enumSet);

which means that the control ComboBoxOnForm will be bound to BaseEnum InventTransType, containing only values, found in the Set enumSet.

You can download the project for versions 4.0 and 2009 of Microsoft Dynamics AX through the following link:
download

P.S. It is also worth mentioning, that this class does not provide support for grids. It requires a stand-alone control, not bound to a database table field.

Saturday, October 20, 2007

List panels in Dynaics AX - a short description of SysListPanel class

SysListPanel class – what is it?

Whenever a user needs to select a number of values from a certain list, so that the selected list gets processed and saved, a list panel can be used. Another use of this type of control is when you are assigning certain properties/options to a specific object.

A good example is the “Administration\Users” form, where each user is assigned to one or more user groups, giving him certain access to DAX functionality.

Here is how it looks:


SysListPanel is the basic class for a large hierarchy of classes showing different list panels throughout the application.

The hierarchy tree for this class is shown below (DAX 4.0 SP2):

One important thing you need to understand when working with list panels – the class is not enough. :) When there is a class, there is a form, containing the list panel and using this class. But, of course, the class is responsible for most of the things happening when a user works with list panels.

SysListPanel or SysListPanelSet – which class to use?

When I consider, what class to extend when creating a list panel on a form, I start off by figuring out how the selected data is going to be stored. If the data selection is supposed to be reflected in the database (for example, when we add the user to a user group, a record is created in UserGroupList table), then extending from SysListPanel should be your choice in most cases.

SysListPanelSet is already an extension of the SysListPanel class, and has one extra variable declaration – inSet variable of type SET. This is the entity that is going to store the values a user selects in the list panel. They can, when selected, be used for further processing.

The SysListPanelSet_TableBrowser class, used in the DEV_SysTableBrowser project (http://www.axaptapedia.com/DEV_SysTableBrowser) is an example, using this set to create the selected controls in the browser.

You should also notice from the SysListPanel hierarchy tree, that there are a lot of classes already available in the application, so it is wise to re-use one of the existing classes, if it matches your requirements or extend from one of them and not from the base class.

Again, the SysListPanelSet_TableBrowser class is an example, as it is extending from the SysListPanelSet_Fields class. It uses the functionality of this class to get the data about table fields, extending them with information about display methods and interaction with the browser form.

Creating a list panel – what methods need to be implemented?

Let’s say the SysListPanelSet_TableBrowser class does not exist yet, and we are just planning to create it, along with the form.

Creating the form is the easy part. There are not many requirements that have to be fulfilled here.

First of all, of course, you need to have a variable to reference your listPanel class. Put it in the classDeclaration of the form, so that it is visible in all form methods. And after that all that is left to do is to create an object of your listPanel class, and the class will do the rest of the work.

Here is what needs to be done:
  1. In the init() method of the form, you need to initialize the listPanel class, specifying, that it is this form you want the list panel to be on, the group that the list panel is going to be in on the form, and any other relevant parameters (for example, when using SysListPanelSet, you usually pass the current state of the values (inSet) as well.
  2. Most convenient way to accomplish this is by creating a static method with the needed list of parameters, and call this method from the init() method of the form. Usually, this method is named newForm.
  3. The SysListPanel class contains a lot of parm* methods, that you can use to modify the behavior of the list panel. The SysListPanelSet adds another method to that list, allowing to set the current set of selected values (parmInSet). You can see how these methods are used in Classes\SysListPanelSet_Fields_TableBrowser\newForm() method.
  4. After basic initialization you have to call the build method. The build method of SysListPanel class is responsible for actually adding all the needed controls to the specified form. Depending on the parameters that you set before calling this method, the list panel will/won’t add the “Add All”/”Remove All” buttons, the “Up” and “Down” buttons, will set the needed number of columns shown in the panels, etc. You can override any of the methods, adding additional functionality (see \Classes\SysListPanelSet_TableBrowser \build() method) or even completely rewrite the code of the basic method (this is not recommended, of course). Notice, that the build method should be called BEFORE super() in the form’s init() method, because FormBuild*Control classes are used to add controls to the form.
  5. After build method is executed, all FormBuild*Controls are initialized. You need to call the init method then, so that all Form*Control variables are initialized. This method should be called AFTER the super() call in the form init method, as Form*Controls don’t exist until super() is called.
  6. The last thing you have to do is call the fill method of your new class, so that the list panel is filled with needed data. Let’s talk about this method in a little more detail below.

The Classes\SysListPanel\fill() method, basically, consists of 4 parts:

  1. getData method is called. This is the method that gathers the data for the left and right part of the list panel. This is an abstract method, and it has to be implemented in the extending class. It returns a container with all the information for the list panel. It is different from class to class and depends on the data being shown in the list panel.
  2. Both parts of the list panel are cleared and list panel columns are created (the first time).
  3. fillView method is called for both parts of the list panel. These methods use the data generated in step 1.
  4. Buttons are enabled depending on the data in both list panel parts. For example, if there are no items selected (left part of the list panel), than the "Remove" and "RemoveAll" buttons are disabled.

The rest of the methods on SysListPanel class are for handling events that occur from user interaction.

The class is the handler of these events in most cases, controlMethodOverloadObject method is used to specify that when the list panel is built.

I guess, the only two methods left that are worth mentioning are the abstract methods addData and removeData. Both of these have to be implemented in the child class, providing the logic for adding and removing items from the left part of the list panel. For example, for SysListPanelSet class the implementation is very simple, just adding or removing the item from the inSet object. For classes extending directly from SysListPanel these methods are where the main logic is located, adding or removing records from the database. In most cases, these methods simply call a server method that does all the needed actions (Remember the rule of thumb – place database logic as close to the database layer as possible).

That is pretty much all you need to know to use and create list panels in Dynamics AX.

Conclusion

Of course, this is not a full description, and much can be added to the information posted here.

If you have any additions, corrections or questions, feel free to post them as comments.

See also

\Forms\Tutorial_SysListPanel

Friday, September 14, 2007

DEV_SysTableBrowser version 2.0 is out!

I have been asked by a number of my blog readers to move this tool to Dynamics AX 3.0, so here is the updated version of this tool. Now it works on both DAX 3.0 and 4.0 (tested on 3.0 SP5 KR2 and 4.0 SP1 and SP2).

Also, I added a couple of things I considered useful to this new release:
- The user field select dialog option has now, by default, all the non-system fields selected, so that users don’t get surprised when no fields are selected. I agree – it does look strange :)
- Now it’s possible to select display methods along with table fields in the user field select dialog. This feature was already working with FieldGroups, so I decided it could be a nice add-on to the User Field List option
- I also fixed a couple of old (and new, brought to us by 4.0 SP2) bugs that existed in the tool
- After reading some of the last posts on AxForum, I decided to add the ability of browsing temporary tables into my project as well, so that others may use them if they want :) (this will also include browsing temp tables data shown in forms from Tabax when the next version comes out)
- Now you can also use the browser for debugging purposes, launching it from code (this works for temp tables as well). Just write:

SysTableBrowser::browseTable(table.TableId, table);

where table is a cursor (second parameter can be omitted for non-temp tables). In this case the browser will open and code execution will stop until you close the browser (if not in a transaction). Third parameter controls if code execution should be stopped.

- There are two variables in method new of class SysTableBrowser
saveQueryRun = true; // Enables/Disables queryRun saving when new options are specified
savePosition = true; // Enables/Disables cursor position saving when new options are specified

The options allow to save the user filters and cursor position when changing setup options.
Both operations could result in performance problems on large tables.
If that happens (or you just don't need them), just turn them to false. :)

The download link is: DOWNLOAD

See extended installation instructions and tool description on: HOMEPAGE

What was a little disappointing is that a number of bugs I wrote about earlier were not fixed since SP1. Here are the 2 I mentioned before, again:
- UPDATE_RECORDSET command, when used in the table browser, crashes DAX.
- The left top edge position (the coordinates) of the table browser gets reset when any of the options are changed. I fixed this by specifying 0 instead of -1 for the leftMode and topMode properties of the design. So when you download this tool, the browser will stay in one place, which is nice ;)

Also, 1 new bug I found is for the Russian localization team.
- Changing the view (in the Unmodified version of the browser was causing the full table list to open, prompting the user for a selection of the table). This is caused by a small validation in the SysTableBrowser::main() method. SysSetupFormRun is supposed to be the classId of the calling object. And it is not, because some system wide changes were made to the SysSetupFormRun::construct() method. Anyway, I fixed this small bug as well.

OK. That’s all about the tool and the bugs. I have also been asked by a couple of my readers to write a tutorial on using the SysListPanel class.

That’s exactly what I am going to write about in my next blog entry. After all, there is a new class extending from the SysListPanel class in the DEV_SysTableBrowser project.