Monday, December 21, 2009

How to set the language used on a report

When printing a report, Ax sets the used language automatically based on the user's settings. The selection of available languages for the client interface depends on the installed language license keys.

But sometimes, it may be necessary to change the default settings for reports. With reports needed for external use for example. Like invoices and packings slips you send to your customers. These documents require to be made up in the language of your customer. Ax is designed with this scenario in mind.

The language to use in the report can be set with following command:

element.design().languageID();

So for example:

element.design().languageID('en-us');


Place the code in the init method of your report, or in the fetch method.


You can use this in your own custom reports as well, with no limitation regarding the language license keys you bought. All you need are the appropriate label files. (Otherwise you get error messages like 'label @SYS123 not found in language xyz'.)

Got some time to spare? It can be fun to see how your documents look like in he - Hebrew, with its right to left printing.

Recurring issue: str2num

There is a general problem with string to numeric conversion in Ax, which seems to resurface now and again.
Last time around when we faced this problem, we were working in the Product Builder modules. We had variables setup with default values like this: 3.200,00 mm
But when Ax loaded the configuration form, the string values were converted to 3 (no decimal value setup - values divided by 1000). Ax had interpreted the thousand separator as the decimal separator. It's easy to test:



test=str2num("3.200,00");



As the values stored for the configurated product models are all converted to string values (table PBATableInstance), there are 2 possible approaches to this problem.

1. Make sure the saved value is stored without the thousand separator. ("3200,00" converts ok to 3.200,00)

2. Make sure the retrieved value is converted back to a numeric value in the right way.



We choose the latter approach. We edited Class PBABuildForm\fillDataSource, where the default values of the product model are set up.



pbaTmpBuildForm.Comma = str2num(strrem(pbaTableInstance.Value,'.'));



(Ax 2009 SP1 - no HF)

Use colors in a report

In my last post, I talked about the use of colors in a grid. You can use the discussed example to add some spice to your forms.
But the same can be applied to reports as well. Anyone remembers those old dot matrix printers, with that huge stacks of print paper? The paper had green lines, in order to improve the readability.
You can get the same effect, with almost any report in Ax. This is some coding I did a couple of years ago for a user that complained about his report, saying the characters where "all dancing around on the paper".

Create a static method on a class, for example the Global class. (this way you can reuse this code on multiple reports)

static void SetReportLine(ReportSection _ReportSection)
{ int controlcount;
int counter;

int usecolor;
int oldcolor;

object reportobject;

;
controlcount=_ReportSection.controlCount();

oldcolor=_ReportSection.foregroundColor();

if(oldcolor==WinApi::RGB2int(255,255,255))
usecolor=WinApi::RGB2int(220,255,220);
else
usecolor=WinApi::RGB2int(255,255,255);

_ReportSection.foregroundColor(usecolor);

for(counter=1;counter<=controlcount;counter++) { reportobject=_ReportSection.controlNo(counter); reportobject.backgroundColor(usecolor); } }


Now all you have to do, is call this static method from your report. Call it from the executed section in the report, before the call to super, like this

public void executeSection()
{ ;
Global::SetReportLine(this);

super();
}


The result will look something like this:








Eco tip: Again, don't overdo the use of this effect. As all coloring needs to be printed, ink is used. The darker you color your lines, the more ink gets used. So this tip isn't that eco friendly, as it is retro style.

Saturday, December 19, 2009

How to use color in a grid

How to color records in a grid is a common user request that can be found on the web.


Actually, this is quite simple to achieve in Ax, with only a small modification. The trick is to override the displayOption method on the form's datasource.



Non-overriden, it looks like this:



public void displayOption(Common _record, FormRowDisplayOption _options)
{
super(_record, _options);
}



By using the options object, an object of class FormRowDisplayOption, you can set the background color.



_options.backColor(myColor);





You can use the record information from _record to set any background you want, where each row in the grid can have a different background color.


For that reason, it may be better to change it's type to the specific table used in the datasource.


Like this:





public void displayOption(CustTable _CustTable,FormRowDisplayOption _options)


{ int myColor=WinApi::RGB2int(50,255,50);
;

if(_CustTable.Currency=='EUR')


_options.backColor(myColor);



super(_CustTable, _options);
}



In the example above, we color code our customers in the form CustTable. (Remember: Changes go in the methods of the form's datasource.) Customers with currency EUR get a green color.


The result looks like this:





By using colors, you can enhance the visual representation of your data. Making it easier for users to grab their attention, focusing them on certain data. The data gets processed faster.

Just make sure you don't overdo it, making it hurt your users eyes ;-)

Monday, December 14, 2009

Jubilee!

Oops, missed an anniversary.








Over 50 blog posts (and counting).




So a big thank you to my blog readers, my blog followers and commenters. Much appreciated!




When I started this blog, I wanted to give something back to the Ax community.


When I had questions about Ax in the past, I didn't always find the answer in the manuals. But the web with it's many Ax professionals provided me with answers on many occasions. I hope this blog gives someone out there some answers as well.

How to generate random numbers in Ax

Ax has functionality built-in to generate random numbers. You can use the Random class for this purpose. Like this:

static void RandomGenerator(Args _args)
{ Random Random = new Random();
int myrandomnumber;
;
myrandomnumber=1 + Random.nextInt() MOD 100;
info('Number '+int2str(myrandomnumber));
}

In the above example, Ax will generate a random number in the range from 1 to 100.
Though the random class gives you integer values, you can use it to generate random real values as well.

static void RandomGenerator(Args _args)
{ Random Random = new Random();
real myrandomnumber;
;
myrandomnumber=(1 + Random.nextInt() MOD 1000)/1000;
info('Number '+num2str(myrandomnumber,0,3,1,0));
}

This example generates random numbers in the range 0.001 to 1.000.

The use of this function comes with a warning:
The chosen numbers are not completely random because a definite mathematical algorithm is used to select them but they are sufficiently random for practical purposes.

So, good enough to use in an ERP system.

Saturday, December 5, 2009

Product Builder perfomance tip

With the Product Builder modules from Ax, your users (internal or external) can configure their own products. These products can range from electronic devices (demo company) to basically everything you can imagine.

Loading the form for configuration of the product model can take some time. If you have lots of variables (complex product model), it can take more than 20 seconds. And that's an eternity for some users in this day and age.

So I thought to share this little performance tip with you.

In Ax, setting the properties of a form control can take quite some time. The more variables you have, the more form control properties to set. Think of enabled Yes/No, hidden Yes/No.
Now there's no need to set the property, if it's already set. That's a waste of time. So if a field is already hidden, don't hide it again. If it's enabled, don't enable it again.
But unfortunately, that is exactly what the standard code is doing. But we can change this behaviour: Go the the class PBAFrontEndControl, method SetControlProperties.

Change this part:

dataSource.object(valueTmpBuildForm.activeField()).enabled(enabled);
dataSource.object(valueTmpBuildForm.activeField()).mandatory(mandatory);
dataSource.object(valueTmpBuildForm.activeField()).visible(visible);

into this part:

if(dataSource.object(valueTmpBuildForm.activeField()).enabled()!=enabled)
dataSource.object(valueTmpBuildForm.activeField()).enabled(enabled);
if(dataSource.object(valueTmpBuildForm.activeField()).mandatory()!=mandatory)
dataSource.object(valueTmpBuildForm.activeField()).mandatory(mandatory);
if(dataSource.object(valueTmpBuildForm.activeField()).visible()!=visible)
dataSource.object(valueTmpBuildForm.activeField()).visible(visible);

Similar code can be found in the method with the same name in class PBAFrontEndControlWin.

These small modifications reduced the load time of the form to below 5 seconds for us, where it was first 20 seconds +.

Do you have any performance tips as well? Share them in the comments!

Wednesday, November 25, 2009

From Image to Image: The best of both worlds

Ax offers the Image class for manipulation of bitmap images. This class has some nice features, like cropping, rotating, changing the image size, ...

On the other hand, we can use CLR Interop. That way, we can use the Windows builtin functions to manipulate images.

And maybe, you want a bit of both. In that case, you can start with the Image class in Ax, and pass the image along to your CLR object and modify it even further. You can do this by using following code:

static void Image2Image(Args _args)
{ Image AxImage;
System.Drawing.Image ClrImage;
System.IntPtr ImagePtr;
;
AxImage = new Image();
AxImage.loadImage(@'C:\MyBitmap.bmp');
ImagePtr= new System.IntPtr(AxImage.exportBitmap());
ClrImage = System.Drawing.Image::FromHbitmap(ImagePtr);
}

We use the ExportBitmap method on the Image class in Ax, to get a pointer to the Image object.
With this pointer, we instantiate the Image CLR object. No need to save the image into an intermediate file, or reload the data from file.

Enumerating with CLR Interop

Like seen in previous posts on this blog, with CLR Interop, a whole world of programming libraries are available to Ax.
But these come with little annoyances as well. Like when you try to use an enum in your programming library.
As an example, take a look at following code:

static void ClrEnum(Args _args)
{ System.Drawing.Image myImage;
System.Drawing.RotateFlipType myRotateFlipType;
;
myImage = System.Drawing.Image::FromFile(@'C:\MyBitmap.bmp');
myRotateFlipType=System.Drawing.RotateFlipType::Rotate90FlipNone();
myImage.RotateFlip(myRotateFlipType);
myImage.Save(@'C:\NewBitmap.bmp');
}

Everything looks OK. When typing in the code, IntelliSense even added the enumerations, but now if fails to compile. Saying

The class System.Drawing.RotateFlipType does not contain this function.

When working with CLR Interop, you might get this error from time to time. You check and doublecheck, there is no typing error.

So this wouldn't be much of a blog post if it didn't offer some workaround.

You can use the ClrInterop::parseClrEnum command.
With this command, you can convert a string value of an enumerated constant to an equivalent CLRObject instance.

Like this:

myRotateFlipType=ClrInterop::parseClrEnum('System.Drawing.RotateFlipType','Rotate90FlipNone');

Now that compiles! So now, enumerations in assemblies won't bother you again.

Monday, November 9, 2009

An alternative to the WinApi functions

The WinApi class holds some nice functions for file manipulation, like fe



WinApi::fileExists

WinApi::copyFile

WinApi::deleteFile

WinApi::moveFile



I think every programmer has used one of those functions in Ax at one time or another, as they can be really helpful.

They have one big drawback though: They need to be executed on the client tier. Trying to use these functions in batch processing (so on the AOS), will result in errors.

Now you might wanna have a look at the WinAPIServer class. This class holds similar functions as the WinApi class, but these methods run on the server. This is a good alternative when you need to do some file manipulation in batch mode, but overall functionality is a bit limited here. Of course, you are free to extend the WinAPIServer class with your own, desired methods.



Or you can take a shortcut and use the CLRInterop way. Use the System.IO.File and System.IO.Directory namespace.



Examples:



System.IO.File::Copy

System.IO.File::Delete

System.IO.File::Exists

System.IO.File::Move

System.IO.Directory::Exists

System.IO.Directory::CreateDirectory



Assigning your CLR Interop permissions, and you're good to go!

Error - Multiple calls of CodeAccessPermission.Assert

When dealing with asserting access permissions, a simple line of code may be sufficient. For example like this:

new FileIOPermission(myfilename, 'r').assert();

Above line may be used for assigning rights for reading a CSV file with Ax, with the ASCIIIO class.
But sometimes, this ain't enough and you need to assign more permissions. If you just combine statements like the one above, you may run into following error:

Multiple calls of CodeAccessPermission.Assert

Ax does not support multiple, successive calls to assert in the same calling code. You either have to use the revertAssert method (in between calls), or use assertMultiple.
An example of this last method:

Set permissionSet;
permissionSet = new Set(Types::Class);
permissionSet.add(new InteropPermission(InteropKind::ClrInterop));
permissionSet.add(new FileIOPermission(myfilename1, 'r'));
permissionSet.add(new FileIOPermission(myfilename2, 'rw'));
CodeAccessPermission::assertMultiple(permissionSet);

// do your thing

CodeAccessPermission::revertAssert();

Thursday, November 5, 2009

User 'UserName' is not authorized to update a record in table 'TableName'. Request denied.

You thought you were the administrator.
You thought it was your data.
You thought you could do with it what you want.

You thought wrong!

Even as an administrator, you may come across these kinds of error:

User 'UserName' is not authorized to insert a record in table 'TableName'. Request denied.
Cannot create a record in TableLabel (TableName). Access Denied: You do not have sufficient authorization to modify data in database.

Or, from real life:

User Admin is not authorized to update a record in table LedgerTrans. Request denied.
Cannot update a record in LedgerTrans. Access Denied: You do not have sufficient authorization to modify data in database.

The reason behind this, is a security measure. The AOS is guarding the access to the table data. This has been set up by using the AOSAuthorization property on the table.

And, is there a workaround available? Yes, there is.
But first of all: Beware! Changing or inserting data in tables that are protected like this should be done with extreme caution, as essential business logic is at risk.

The easiest thing to do is to change the AOSAuthorization property on the specified table. STOP! Don't do that, don't put your data at risk like that. Badly written code can easily mess up your data, and that's not something you want with a table like fe LedgerTrans. You only want to modify this data in a very controlled setup.

A better solution is to use the unchecked statement. With this statement, you can switch off any AOSAuthoratization setup on the table, only for the current code block.

Example:

LedgerTrans.skipTTSCheck(true);
select firstonly forupdate
LedgerTrans where LedgerTrans.RecId==123;

if(LedgerTrans.RecId)
{
LedgerTrans.Txt="Hello";
unchecked(Uncheck::TableSecurityPermission)
{
lLedgerTrans.doupdate();
}
}


All this should get you interested in Ax and it's security setup. A recommended reading for that can be found over on the Microsoft site.

Error - Client: Unexpected target (0)

When posting a packing slip in the Sales module, we got confronted with following error:

Client: Unexpected target (0)

The packing slip got posted however, only the printout failed.

For some users, everything went smooth and the packing slip got posted and printed. For others, it failed at printing. That's why we tought of a security issue involved, but testing with different security rights did not solve the problem.

When searching the web for a solution, we came across the blog of Palle Agermark, which featured a post about this particular error message. Unfortunately, no such luck for us, the problem persisted. (Excellent blog though, definitely recommended.)

Next focus for a solution: SysLastValue. Lots of time before when we ran into problems with reports, deleting the SysLastValue for the user solved the problem. Only in this case, we could not find an entry for the report SalesPackingSlip. Searching harder, we found an entry for SalesFormLetter_PackingSlip. And bingo: When we deleted the user data for SalesFormLetter_PackingSlip, our problem was solved.

Once again, deleting the appropriate values in SysLastValue (usage data) solved the problem.

Friday, October 30, 2009

Alternative method to open a webpage from Ax

When you want to show your users a webpage from within Ax, you can use the ShellExecute command. Like this

WinApi::shellExecute('www.blogger.com');

You can use this format to open/show a specific XML file from disk as well. Like this:

WinApi::shellExecute('yourfile.xml','',@'C:\XMLFolder');

This will fire up your XML editor/viewer (possibly MS Internet Explorer) and display the specified file.
As an alternative, you can use the infolog for this as well. Like this:

infoLog.urlLookup('www.blogger.com');

or

infoLog.urlLookup(@'C:\XMLFolder\yourfile.xml');

This alternative method does not require extended security measures, like required when you use the WinAPI functions.

Yes, it's compatible with Windows 7!


As everyone expected, Ax 2009 is compatible with Windows 7. The Ax system requirements have been updated to reflect the changes with the coming of this new OS.
The Professional and Ultimate Edition of Windows 7 are supported, in 32 and 64 bit versions.
But not only Ax 2009 is officially supported, also Ax Version 4 SP2 is certified to be used with Microsoft's latest client OS (as can be read on the MS Dynamics Ax UK blog, over here).
Good news indeed, as we in IT are in a constant loop to upgrade both applications and os to keep them running nicely together, in an officially supported environment. This gives Ax V4 users a chance to try out Windows 7, without the immediate need to upgrade their Ax environment to Ax 2009 as well. If it ain't broken, don't fix it.

Friday, October 9, 2009

How to add leading zeros to a string

When sorting numeric values in an alphanumeric way, you may become unexpected results. Like this

1
11
2

In such cases, it may be helpful to add leading zeros to your string.

And tadaah! Ax to the rescue, again there is a helpful built-in function available, strRFix.
How it works? You specify your value to convert, the desired lenghth of the string and the character to use as a "filler".

Example:

info(strRFix('1',4,'0'));

gives '0001'.
The above numeric values sorted with their alphanumeric counterparts look like this:

0001
0002
0011

Use the function like this:

strRFix(int2str(intvalue),4,'0');

(Replace the intvalue with your variable. )

Basically, you can use this function to right align a string, based on a fixed length.
And don't forget you can specify your own character to 'fix' the string. So dots are allowed, as well as a space.

This function is relatively new in Ax, and even goes without any documentation in the standard help files and on Microsoft's MSDN. Too bad, as unknown is unloved.

Edit:
As someone pointed out in the comments, not so new function at all. After lookin' into it, I found out it's even available in Ax V3 (maybe even earlier?). Lacking good documentation for sure!

Friday, October 2, 2009

Classic artwork - Nostalgic post

Recently I read this post over at DynamicsWorld. It is a post about the timeline of Dynamics Ax, aka Axapta, with all the various milestones of its existense.

The post made me think about the old days and all the different versions of Axapta we've had a chance of working with in the past. And yes, it made me a bit nostalgic. I thought I share with you some of the artwork of old Axapta versions. Maybe you can remenisce as well, or just get an idea on where Ax comes from.




The Axapta program icon, as used in versions 1.0 and 1.5.
(Note: The demo data included at that time, we are speaking 1998-1999, was a whopping big 5Mb DAT file...)




The main menu from version 1.5 has, for todays standards, a very basic look-and-feel.


With version 2.5, you could say Axapta went into its "orange" phase.

The Axapta program icon in version 2.5

The splash screen from version 2.5:

The main menu, as used in version 2.5.

By that time, besides building it's own modules, functionality from third parties was already integrated in Axapta (see the Shop Floor modules in the print screen).

As you can see by the number of tabs in the main menu, less was more those days.

After the release of version V.25, Damgaard regrouped the functionality. (For example: customers and sales go together, like items and BOM.) This made the size of the main menu somewhat controllable in version 3.0.

Note: We never really went LIVE with version 1.0 or version 1.5. We used version 1.5 for demo purposes internally, used version 2.0 to test and to start up with. Pretty soon after that came version 2.1.

Tuesday, September 29, 2009

Syntax error in Product Builder module

Recently we came across a small but annoying problem in the product builder modules.
Our product model, perfectly working for quite some time, refused to compile after a minor modification. The error message read:

Compilation error: *** Error: -1, Syntax error.

The error popped up when we compiled a code treenode from the product model. We checked our code, everything looked OK. We didn't make a typing error.

One of the product builder variables we used was named 'DDB'. No problem so far, but when we tried to use this variable in the code segment of our treenode, the error came up. Why?
When used in the code treenode, Ax will declare a variable in the class it creates for your product model, with the same name as used in the product model.
Only, DDB is a reserved name, as this is the name of a function in Ax (it calculates the accelerated depreciation of an asset). Variables in classes cannot be named after built-in functions.

So our solution: rename variable DDB to something else.
Problem solved, something learned.

Friday, September 25, 2009

How to print a report straight to the printer (and not to screen)

When a user runs a report, it's a good idea to first print it to screen. When the outcome is not what was expected, the user can change his criteria or parameters and run the report again. When the report meets the expectations, a hardcopy can be printed. Save paper, think green!

But sometimes, you'll want a report to go straight to the printer, without a user intervention. For example when you run a report by code (in a production environment, an automatic generated loading list, ...).

In these scenario's, you'll want to adjust the printer settings.

1. Set the desired printer
Add following code in the init method of your report

element.deviceName(yourprinternamegoeshere);

Make sure a printer by that name exists on the PC where the report runs.

2. Don't print to screen, print straight to printer
Add following code in the init method of your report

element.setTarget(PrintMedium::Printer);

This will make the report go straight to the printer (instead of the screen).
You can add additional settings, like the number of copies as well.
Like this:

element.printJobSettings().copies(2);

For automation purposes, the above code examples may be useful to run reports.

How to run a report by code

After last post, how to open a form by code, we gonna do the same thing for reports.
First the short version

new MenuFunction(MenuItemOutPutStr(Cust),MenuItemType::Output).run();

The above code will start the Cust report (a list with the basic information for a selection of customers).
If you want some information passed to your report, you can use the args class.
Like this

static void RunReportByCodeA()
{ Args args = new Args();
;
args.parm('yourparmhere');
new MenuFunction(MenuItemOutPutStr(Cust),MenuItemType::Output).run(args);
}


Just like in the previous post, even more control over your report may be required. Then you can use the long version.

static void RunReportByCodeB()
{ ReportRun reportRun;
Args args = new Args(reportstr('Cust'));
;

// args.caller(this);
// args.record(parmRecord);
// args.parm(parmStr);

reportRun = new Reportrun(args);
reportRun.init();
reportRun.run();
}


The above version is exactly like the short version we saw earlier. But it shows where we are going with this.

static void RunReportByCodeB()
{ Object reportRun;
Args args = new Args(reportstr('Cust'));
;
reportRun = new Reportrun(args);
reportRun.init();
reportRun.yourmethodgoeshere();
reportRun.run();
}

We changed the type of reportRun to Object.
Now we have even more control over the report, as we can execute any method defined on it. Use it to provide extra information to the report (use a specific design depending on data, print straight to printer instead of screen, ...).

Again, there is one drawback: While programming, your method doesn't show up in the IntelliSense, showing all the available methods. So be carefull of typo's. (They don't give a compile error, but they will give you a run-time error.)

Wednesday, September 23, 2009

How to open a form by code

A short tutorial on how to open a form in Dynamics Ax (Axapta) by code.

In the shortest example, all it takes is one line of code.

new MenuFunction(MenuItemDisplayStr(CustTable),MenuItemType::Display).run();

The above code will open the CustTable form. That's all it takes, it's that simple.
Now if you want to supply some arguments to the opening form, this is also possible with the optional args parameter.
Like this for example:

static void OpenFormByCodeA()
{ Args args = new Args();
;
args.record(CustTable::find('ABC'));
new MenuFunction(MenuItemDisplayStr(CustTable),MenuItemType::Display).run(Args);
}

This code will open the CustTable form and filter out the customer with accountnumber ABC.
Use the args methods like parm and parmEnum to provide your target form with more data.

If you want even more control on opening the form from code, this is also possible.
This next example gives the same result as the previous one.

static void OpenFormByCodeB()
{ FormRun formRun;
Args args = new Args();
;
args.name(formstr(CustTable));
args.record(CustTable::find('ABC'));

formRun = ClassFactory.formRunClass(args);
formRun.init();
formRun.run();
formRun.wait();
}


Now if we tweak this a little bit, we can add our code
Like this:

static void OpenFormByCodeB()
{ Object formRun;
Args args = new Args();
;
args.name(formstr(CustTable));
args.record(CustTable::find('ABC'));

formRun = ClassFactory.formRunClass(args);
formRun.init();

formRun.yourmethodgoeshere(); /* !!

formRun.run();
formRun.wait();
}


By changing the type of formRun from class FormRun to class Object, we can implement and execute extra methods on our destination form! This gives us extra possibilities for customizations. You can pass along extra parameters for example.
Only drawback: While programming, your method doesn't show up in the IntelliSense, showing all the available methods. So be carefull of typo's. (They don't give a compile error, but they will give you a run-time error.)

Next up: Run a report by code

Sunday, September 20, 2009

Error: Data source name not found and no default driver specified

When working with ODBC connections in Ax, you may come across this error message:

[Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified

Or the complete error message:

ODBC operation failed.

Unable to logon to the database.

[Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified

Object 'OdbcConnection' could not be created

Then you check the control panel - Administrative tools - ODBC connections.
And you see your datasource is there. You check the name, and it's spelled ok. So what could be wrong? (Always check the most probable cause of your error message first, a typing error.)

As you might know, the Ax client is a 32 bit application. And nowadays, you might have a 64 bit operating system. Datasources created the default way in a 64 bit operating system cannot be used by Ax. So you need to create a 32 bit datasource.
There is an alternative panel applet to create a 32 bit datasource, fe in Windows Server 2008. You'll find this applet in the C:\Windows\SysWOW64 folder, called odbcad32.exe. (Now watch out, a file by the same name might be available in the C:\Windows\System32 folder. This is NOT the version needed.)

Start the executable C:\Windows\SysWOW64\odbcad32.exe and create your 32 bit datasource. Afterwards, re-execute your code. Did that help make the error go away?
(Some Microsoft info about the issue.)

Create an ODBC connection in Ax

In Ax, you are never on an island. You have lots of options to bring in data from other systems, whether it's through AIF, reading an XML file or.. directly connecting to another database. In this post, I'll show how to create an ODBC connection to a external database.
For this, we'll use the standard available ODBCConnection class, as opposed to CLR Interop and rely on the .NET functions.
Here goes, in 3 simple steps.
  1. Set the login property
  2. Create the connection
  3. Read from a sample table

static void myODBCConnection(Args _args)
{ ODBCConnection myODBC;
Statement myStatement;
LoginProperty myLoginProperty;
Resultset myResultset;

str mySQLStatement;
str myConnectionString;

str myDSN="yourdatasourcenamehere";
str myUserName="yourusername";
str myPassword="yourpassword";
;
myConnectionString=strfmt("DSN=%1;UID=%2;PWD=%3",myDSN,myUserName,myPassword);

myLoginProperty = new LoginProperty();
myLoginProperty.setOther(myConnectionString);

try
{
myODBC = new OdbcConnection(myLoginProperty);
myStatement=myODBC.createStatement();

mySQLStatement="SELECT Field1,Field2 FROM myTable";
myResultSet=myStatement.executeQuery(mySQLStatement);
while (myResultSet.next())
{
info(myResultSet.getString(1));
info(int2str(myResultSet.getInt(2)));
}
} catch
{
error('Unexpected error');
}
}


In earlier versions from Ax, the LoginProperty came with the methods to set the loginname and password. But Microsoft has cut these features (security reasons?). But as you can see in the example, we work around that by using the 'SetOther' method.

There's one thing you need to consider. The Ax client requires a 32 bit datasource. On a 64 bit system like Windows 2008, this may result in an error 'Data source name not found'. That's something for my next post.

Tuesday, September 15, 2009

How to populate a date variable

Some quick tips on how to populate a date variable.

Example 1: The use of function mkdate

TransDate myDate=mkdate(15,9,2009);

This function creates a date based on three integers, which indicate day, month and year. So mkdate(day,month,year).

As an alternative, you can use this:

Example 2: The use of function str2date

TransDate myDate=str2date('15/09/2009',123);

The function str2date converts a text string to a date value. The second argument gives the used formatting of the date string. (Where 1 stands for day, 2 for month and 3 for year.)

There is a similar function to create a DateTime value, namely str2datetime.
Use:

TransDateTime myDateTime=str2datetime( "15/09/2009 20:39:00" ,123 );

So, plenty of choice, whichever suits you.

Thursday, September 10, 2009

Slowly leaving X++, going .NET

Hi,

Everyone knows that, since the acquisition of Navision in 2002, Microsoft has pushed its technology set further and further into the ERP system. They are still working on the 'Project Green' and one single common code base for all the ERP systems.

Now altough Damgaard (the original Axapta founders) has always been very oriented towards Microsoft technology (they did provide an Oracle database option for Axapta, but few have chosen that path), you can do always more. The road ahead is clear, if you see at where Ax is going with the Business Intelligence solutions, all neatly aligned with Microsoft standards.

Talk that the end of X++ (the development language of Ax) is near has been going on for years now. And now the end seems a bit closer again.

Have a look at this video over at Channel 9, talking about X++ and MSIL (MSIL stands for Microsoft Intermediate Language).
So basically, moving closer to .NET again.

Error: "Request for the permission of type 'InteropPermission' failed."

Hi there,

Here's another error message you may come across in Dynamics Ax:

Error: "Request for the permission of type 'InteropPermission' failed."

You have written some code, tested it and everything went fine. Now the project has gone live, and all of a sudden, the code fails with the error message from above.
The code is probably executed in another context as when you tested it. For example by another user, or in batch.
The reason for the failure is the security measures that were implemented in Ax 4.0 and up.
What do you need to do? Assign permissions to execute the code (CLR Interop). Like this:

InteropPermission permission = new InteropPermission(InteropKind::ClrInterop);
;
permission.assert();


If your error message is regarding a COM object that you are referencing, you can use this alternative:

InteropPermission permission = new InteropPermission(InteropKind::ComInterop);
;
permission.assert();

You can read more about CAS or Code Access Security over at MSDN.

xPropertySetData::unpack : Property not found : 1024

We recently came across a very annoying error message:



xPropertySetData::unpack : Property not found : 1024

This happened after an upgrade process. When a user started a specific report and wanted to set a query, the client crashed (Ax32.exe - Fatal Application Exit).
This was only happening to specific users, and with the queries of specific reports. The report itself could be run by the user, as long as the user didn't try to modify the query.
We immediately thought of the last used value, but clearing these for the users didn't help. Clearing the cache: didn't help. Stopping and restarting the AOS (even with deleting the index file): didn't help. Recompile: Nope.
So we investigated this issue a bit further. We found out that the client crashed when executing code in class SysQueryForm, method QueryUnpack.
When trying to create a new query object with old saved data, the client crashed. (new Query(queryPack_V25))
So an easy workaround for us was not to use those old saved queries, just use those of the current version. (replace if (queryPack_V25) with if(false)).

Version: Ax 2009 SP1.
(and as always, use at own risk!)

Thursday, August 20, 2009

How to copy the favorites from one user to another

The favorites menu is a popular thing in Dynamics Ax. It has been around for quite some time now in Ax, in one form or another.
A common request that I get from users is: "Hey, I like this guy's favorites menu, can't I copy it to me own settings?'.
The answer in standard Ax is: No, you can't. But as with almost anything in Ax: It can be done with a little modification.

In order to be able to copy the favorites menu, you have to know where it is stored. This is done in the system table SysPersonalization, in the 'Buffer' field. We are looking for records in this table with the elementtype of UserMenu.

This next job will copy the favorites menu from user A to user B. (If user B has a favorities menu setup, it will be lost, as it first gets deleted.)

server static void FavoritesJob(Args _args)
{ SysPersonalization FromSysPersonalization;
SysPersonalization ToSysPersonalization;
UserId FromUserId='UserA';
UserId ToUserId='UserB';
;

ttsbegin;

// step 1 - delete current favorites menu from user
while select forupdate ToSysPersonalization
where ToSysPersonalization.ElementType==UtilElementType::UserMenu
&& ToSysPersonalization.UserId==ToUserId
{
ToSysPersonalization.doDelete();
}

// step 2 - duplicate from user A
while select FromSysPersonalization
where FromSysPersonalization.UserId==FromUserId
&& FromSysPersonalization.ElementType==UtilElementType::UserMenu
{
ToSysPersonalization.data(FromSysPersonalization);
ToSysPersonalization.UserId=ToUserId;
ToSysPersonalization.doInsert();
}

ttscommit;
}

(use at own risk)

Note: I have used the methods dodelete() and doinsert(), instead of delete() and insert(). Reason: You would run into trouble with access permissions otherwise.

Tuesday, August 18, 2009

Check the state of a Windows service

This is yet another example of the use of CLR Interop from within Dynamics Ax. This time, we're going to retrieve the current state of a Windows service (Spooler, Telnet, ...) on any given Windows machine.
I'm using the ServiceController class from the .NET framework for this. For this example to work, make sure you have added a reference in the AOT to the System.ServiceProcess assembly.

static boolean GetServiceState(str _servicename,str _machinename)
{ System.ServiceProcess.ServiceController ServiceController;
System.ServiceProcess.ServiceControllerStatus ServiceStatus;
str myStatus;

InteropPermission permission = new InteropPermission(InteropKind::ClrInterop);
;

if(!_servicename !_machinename)
return false;

try
{
permission.assert();

ServiceController= new System.ServiceProcess.ServiceController(_servicename,_machinename);
ServiceStatus=ServiceController.get_Status();
myStatus=ServiceStatus.ToString();

if(myStatus=='Running')
return true;
} catch
{

}

return false;
}

Make sure you have appropriate rights for executing this code.

This code can be modified to start and stop services as well.

Monday, August 17, 2009

Create a XML file in Ax

This blog post is dedicated to XML files. A simple example, showing you how to create an XML file. Luckily, Ax already contains the framework to create an XML file, so it can do most of our work.


static void XML_Basic(Args _args)
{ XMLDocument xmlDoc;
XMLNode nodeRoot, commentNode;

XMLNode PanelNode;

XMLNode LengthNode;
XMLNode WidthNode;

FileName xmlFileName;

;

// create XML
xmlDoc = XMLDocument::newBlank();
nodeRoot = xmlDoc.documentElement();

// create XML nodes
commentNode = xmlDoc.appendChild(xmlDoc.createComment('Comment'));
commentNode.text('XML file from
http://dynamics-ax-live.blogspot.com');

PanelNode = xmlDoc.appendChild(xmlDoc.createElement('Panel'));
LengthNode = PanelNode.appendChild(xmlDoc.createElement('Length'));
LengthNode.text('2440');
WidthNode = PanelNode.appendChild(xmlDoc.createElement('Width'));

WidthNode.text('1220');

// write XML to file
xmlFileName=@'C:\MyXML.XML';
new FileIoPermission(xmlFileName, 'rw').assert();
xmlDoc.save(xmlFileName);
CodeAccessPermission::revertAssert();}


There are 3 basic steps in this small 'tutorial'. Step 1, create the XML document. Step 2, add the XML nodes. And step 3, save the XML document in a file. This example will create a very basic XML file, which introduces you to the world of tags, elements and attributes.






As XML files are a common way of exchanging information, you may want to get familiar to the terminology over here.

Sunday, August 2, 2009

Bug with Workspace toolbar

There seems to be a bug in Ax 2009 SP1, regarding the Workspace toolbar.
For common users to see it, access to the security key SysDevelopment is required. A bit strange, as this is a 'No access' - 'Full control' kind of security key. When giving plain users access to this key, and even when you switch off all it's subkeys, you still give your users access to the AOT. Doesn't seem to be desired functionality here now, is it?


Sunday, July 19, 2009

Referencing a table field

This is a little post about the various ways you can address fields from tables.

Here are some examples.

This could be a static methode, declared on the table CustTable. The goal is to retrieve the customer name, based from the account code.


static Name GetCustName(CustAccount _custAccount)

{ CustTable custTable;
;
select firstonly custTable
index hint AccountIdx
where custTable.AccountNum == _custAccount;

return custTable.Name;
}

This is probably the most common way used.
There are some alternatives for this:

static Name GetCustName(CustAccount _custAccount)
{ CustTable custTable;
;
select firstonly custTable
index hint AccountIdx
where custTable.AccountNum == _custAccount;

return custTable.(2);
}

As you can see here, you can get access to the field by using it's fieldid. You can look up the fieldid in the AOT, as it is the first property displayed for a field of a table.

But maybe you don't have the fieldid, and would like to retrieve the value by using the field name instead. As always, Ax has a function that comes in handy. You can use fieldname2id.


static Name GetCustName(CustAccount _custAccount)
{ CustTable custTable;
;
select firstonly custTable
index hint AccountIdx
where custTable.AccountNum == _custAccount;

return custTable.(fieldname2id(tablenum(CustTable),'Name'));
}

Maybe a bit less known: You are not required to declare the CustTable table identifier. You can write code like this as well.

static Name GetCustName(CustAccount _custAccount)
{ ;
return (select firstonly custTable
index hint AccountIdx
where custTable.AccountNum == _custAccount).Name;
}


Make sure you use the brackets before and after the select statement.

So, plenty of options, whichever approach works for you.

Wednesday, July 15, 2009

Dynamics Ax and security setup

Microsoft Dynamics Ax has a nice security setup. It hasn't changed much in the last few years, coming from Axapta version 3 to the current version Ax 2009. Basically it comes down to this: You put users together in user groups. Users can be members of multiple user groups. Then you assign security rights to these user groups. There is some more information, like domain setup and record level security, but they are optional.




I would like to focus this post on the rights given to user groups.


This information is collected in the table AccessRightsList.





Now although I am a fan of the security system in Ax, I also believe there is always room for improvement. So here is my case, based on two simple examples.

1) The form LedgerTable.

In this form, access to the control AccountBalance is controlled by the security key LedgerMisc. If you want your users to have access to the account balances, the setup for the security key LedgerMisc is required. But by giving access to this key, you also give access to all the child keys. That's now what you want. So you are forced to turn the parent key (LedgerMisc) on, and it's child keys off. Bad practice in my opinion.

2) Another example for the same scenario, the form Purchtotals.

This form displays the totals for a given purch order. It requires access to the table Common. To be able to use this form, the user must have access to the security key 'AdminTables'. This key has lots of child keys. Certainly you don't want your users to have access rights to all these tables from Admin. So you are forced to turn the parent key (AdminTables) on, and it's child keys off. Again, bad practice in my opinion.



My tips for Microsoft:

1) The inheritance principle is nice. Switch the parent key on, and you get all the child keys. But for a security setup, this is not recommended behaviour. But it would be even better not to assign security keys of the parent type to elements in forms.

2) You promote the use of 'roles' in the program heavily. A lot of marketing material uses these roles, these personas in the Contoso company (the CEO, the clerk, the order processor). Why not deliver a security setup for these various roles? Give the customer the data (security groups with permissions) for these various roles, so the customer doesn't have to start from scratch settting up his security?

Thursday, July 9, 2009

Convergence EMEA 2009

Earlier this year Microsoft cancelled it's Convergence EMEA 2009 event. It was supposed to take place in November, in the beautiful city of Vienna. Reason for the cancellation according to Microsoft was the economic downturn. But Microsoft vowed it would rework the concept, and come with something new. They kept promise, details for Convergence EMEA 2009 are now slowly becoming available.

Instead of one big event in one city, spanning multiple days, there are now multiple one day events, all over Europe. The idea behind it? Microsoft brings Convergence to you, so you don't have to go to them. More localized content, less travel expenses, shorter duration. All for the benefit of the customers.
The selected cities are London, Vienna, Frankfurt and Rotterdam. It's all taking place end of October, beginning of November. Find out more on the dedicated website.

I got to attend Convergence in Munich a couple of years ago and last year in Copenhagen. Lots of good sessions, lots of information, great quality speakers. I am curious to see if the change in concept has affected the overall quality.

Tuesday, July 7, 2009

Changing form layout by users

In Ax, users can change the layout of forms without any knowledge of programming. This can be done by any user, without writing a single line of code.
Users can for example hide/unhide predefined fields, change the sequence of fields and so on. All these customizations are user specific.

If for some reason you want to limit this functionality, you have various options.
For example, you can change the settings for a specific form, by changing the properties of the design node of the form. Set the property AllowUserSetup to No, so that no user modification to the layout is allowed.

If a specific user is not able to hide/unhide fields or to make changes to the form setup (of any form), you might want to check security setup. Make sure access to the key Admin \ Misc \ SysFormPersonalization is switched on.

Thursday, July 2, 2009

SAP bashing

Wow! It’s SAP bashing time again.
Apparently some disgruntled customer of an SAP vendor feels left out in the cold.
So he decides to turn his anger and frustration into other use and launches a website, http://saphorrorstories.com/

Now if you search Google for “SAP horror stories” you get a 57.300 hits.
Just for fun, look up “Oracle horror stories”. That racks up 147.000 hits!
Now google “Dynamics horror stories”…
Puts things in perspective for all the Microsoft believers, not?

I have never used any of SAP’s products, and I don’t have any experience with the vendor that is so heavily targeted with this site.

But how come the number 1 ERP player in the world (yes, SAP), fails so miserably? After all, the SAP reseller in question claims “it can help run SAP for small and mid-sized companies”.

I can understand that an ERP implementation goes wrong. As these things costs time and money, it will end up with an unhappy customer.
But the site’s designer is more than unhappy, he’s pretty angry.
How could have gotten things this far? I believe there are two sides to every story. And as different parties are involved in an erp migration project, there sure are different sides here.
Every ERP implementation has it’s milestones: Missing deadlines in an early stage of a project is big warning.
What about the match between required functionality according to the customer and the available functionality in the proposed solution? These are things that can be included in the contract between customer and vendor, before the implementation starts. Just like the required training of personnel. Don’t leave anything in the dark, but it in black and white.

With year 2000 projects and the euro conversion well behind us, one would think that the slogan “no such thing as a bad erp implementation” is reality. But as products get more mature and complex, contain more and more functionality, implementation costs rise. And in today’s market, the need for qualified, trained business consultants grows.

Lots of things in nature come with waves: sometimes draught for weeks, then days of pooring rain.
Similar to that I do believe we are on the rise regarding this issue: More ERP projects will fail the next few years, or not meet predefined targets.

So my advice: Make sure you do your homework before you even start looking for an erp suite.

Friday, June 26, 2009

Email from Ax by CLR interop

In my previous post, I talked about the CLR Interop possibilities from Dynamics Ax. As you might know, CLR interop is used to get access to assemblies installed with the .NET Framework, or even the assemblies you create on your own with Visual Studio.

Here's another example. This time, we are going to send an email from Ax with it. (Yes I know, this kind of functionalities are provided with Ax out-of-the-box as well.)

void Email()
{ System.Net.Mail.MailMessage myMailMessage;
System.Net.Mail.SmtpClient myMail;
System.Net.Mail.MailAddress myMailFrom;
System.Net.Mail.MailAddress myMailTo;
System.Net.NetworkCredential myNC;

str mySubject="Mail from Ax 2009";
str myMailBody="The email body goes here";
str myMailFromStr="youremail@yourdomain.com";
str myMailToStr="youremail@yourdomain.com";
str mysmtpServer="yourmailservername";
str mylogin="loginname";
str mypassword="loginpassword";
;

myMailFrom = new System.Net.Mail.MailAddress(myMailFromStr,myMailFromStr);
myMailTo = new System.Net.Mail.MailAddress(myMailToStr,myMailToStr);
myMailMessage = new System.Net.Mail.MailMessage(myMailFrom,myMailTo);
myMailmessage.set_Subject(mySubject);
myMailmessage.set_Body(myMailBody);
myMail = new System.Net.Mail.SmtpClient(mysmtpServer);
myNC= new System.Net.NetworkCredential(mylogin,mypassword);
myMail.set_Credentials(myNC);
myMail.Send(myMailmessage);
}


Supplying of the network credentials is optional. If not specified, the default network credentials will be used. Depending on your email server settings (relay on/off), only emails to the local domain are allowed.


This example can be extended, to include file attachments for example.


Greetingz,



Willy

Network ping command by CLR interop

With the use of CLR interop, there doesn't seem to be a limit to what you can do with Ax. Yes, in the past you could write your own COM wrappers to access those DLL functions. But now, you have much more control. And it's faster. Your basic toolbox just got a LOT bigger. All this .NET components are waiting to be used.

Here's a small example. It demonstrates the well known ping command, to check if a computer is online.
Parameters are the Ip address (string format like 127.0.0.1) and the timeout.

static boolean Ping(str myIpAddressStr,int myTimeOut=4000)
{ System.Net.NetworkInformation.Ping myPing;
System.Net.NetworkInformation.PingReply myPingReply;
System.Net.IPAddress myIPAddress;
System.Net.NetworkInformation.IPStatus myIpStatus;
str myIPStatusStr;
;
if(!myIpAddressStr)

return false;

myIPAddress = System.Net.IPAddress::Parse(myIPAddressStr);
myPing = new System.Net.NetworkInformation.Ping();
myPingReply=myPing.Send(myIPaddress,myTimeOut);
myIpStatus=myPingReply.get_Status();
myIPStatusStr=myIpStatus.ToString();

if(myIPStatusStr=='Success')
return true;

return false;
}



I'll post some more samples later on.

Greetz,

Willy

Wednesday, June 24, 2009

Chart Object: Invalid license

After our upgrade to Ax 2009, one of our forms showed an error message when opening. The error message was:

Invalid License (1000000). Please contact SoftwareFX Support.


The error message got us puzzled for a while.

It was a custom form, where different chart objects were added during runtime.

Other forms with charts worked well. The form Tutorial_Form_Graph worked as well. No license issue. Strange thing: After opening another form with graphics, the error form suddenly started working! Until we restarted the client...
After lots of testing, we came up with a workaround: We added a hidden ActiveX control to the form, class Chart Object. Now our form always runs without errors.
Do you know the correct solution? Let me know!
Greetz,
Willy

Friday, June 19, 2009

Terminate a clien session

I was recently involved in a discussion over at the Microsoft Business Community newsgroups. The subject was the terminating of a client session.

I can suggest two approaches. Use the first job to kill the session of a specified user id. This involves the use of the xSession class, which I blogged of earlier on.

static void TerminateSession(UserId UserId)
{ xSession xSession;
int xSessionId;
;
xSessionId = (select * from SysClientSessions where SysClientSessions.userId==UserId && SysClientSessions.Status==1).SessionId;


xSession = new xSession(xSessionId);
if (xSession)
xSession.terminate(xSession.loginDate(), xSession.loginTime());
}

If you would like to end the current session by code, you can do it like this:

static void ShutDown(Args _args)
{ Info Info = new Info();
;
Info.shutDown(true);
}

If you can think of other means, feel free to post them in the comments.

Saturday, June 13, 2009

Check to see if a user is logged on

In my previous post I talked about the class xSession. The help files for this class in Ax show a nice example for this: Check to see if a specific user id is online.

server static boolean isUserOnline(userId userId)
{ xSession session;
int counter;
int maxSessions = Info::licensedUsersTotal();
;

if (!userId)
{
return false;
}

for(counter = 1; counter < session =" new">

This example is a nice illustration for the use of the xSession class.
I came up with an alternative, shorter version for the example above:

server static boolean isUserOnline(userId userId)
{ ;
return (select count(recid) from SysClientSessions where SysClientSessions.userId==UserId && SysClientSessions.Status==1).RecId;
}

So, both functions give you an easy way of checking if a specific user is online in Axapta.

Current session id

You can retrieve the current client session id by using the following function: SessionId().

So, for example in a job, to show the current session id

static void ClientSessionJob(Args _args)
{ ;
info(strfmt('This client session id is %1',int2str(SessionId())));
}

You can use this function together with the class xSession to collect more info.
The class xSession is responsible for retrieving various session information. Like the logon date and time:

static void LogonDateTime(Args _args)
{ xSession xSession;
;
xSession = new xSession(SessionId());
info(strfmt('The logon date and time were %1',DateTimeUtil::toStr(xSession.loginDateTime())));
}

Or the current AOS server name you're logged onto:

static void CurAOSServerName(Args _args)
{ xSession xSession;
;
xSession = new xSession();
info(strfmt('Current AOS server name logged onto is %1',xSession.AOSName()));
}

Instantiate the class xSession without a session id to get info for the current session.

The user id used to create the session is also available. But there is an alternative for this one:

static void ClientUserId(Args _args)
{ ;
info(strfmt('Current logged on user id %1',curUserId()));
}


Try out for yourself!

Thursday, June 11, 2009

Gartner Magic Quadrant in ERP

Gartner recently released a new version of its report on midmarket ERP systems. One of the featured products is Microsoft Dynamics Ax.


Not only does it get a good review, it's also the only product to be in the leader quadrant. Nice!

Gartner sees some trends in the ERP market, like

  • industry-specific functionality or verticalization
  • globalization, no longer being exclusive for large enterprises
  • consolidation in the market of ERP vendors

For both point 1 and 2, you can see Ax has grown the past few years. Offering a wealth of vertical solutions, certified and all. Microsoft also worked on the stability and performance of the product, making Ax even more suited to global operating companies, enterprise size or not. (fe Unicode support, multiple-time zones)

Some points why Ax scored so well in the report:

  • the user interface is considered to be very intuitive and easy to learn and use + it's role tailored
  • broad range of functionality, both horizontal and vertical
  • strong worldwide partner channel, certified
  • completeness of vision from Microsoft
  • scalable product, easy to customize for both enhancements and proces changes

All in all, a very good report. Nice to know for companies who have invested or are planning to invest in this offering.


You can read more of the report, which includes reviews of products from Oracle, SAP and Microsoft (with Dynamics NAV as well) over here.


Edit:
The report scores well with the press guys at Microsoft. But over the internet, a lot of criticism can be heard. Like on zdnet, cio.com, and The Enterprise System Spectator. They score some good arguments to question the outcome of the report (like no SaaS vendors included).

What can be learned from that? Don't use the Magic Quadrant as a buying guide for products. After all, if the solution you have an eye on has a perfect fit for your functional requirements, what do you care if the vendor has no worldwide presence if your a local operating company? Use the outcome of the report (pro and contra's) and verify them with your own findings/requirements. What are YOU looking for in an ERP suite and how is this evaluated by Gartner?

Wednesday, June 3, 2009

Checking table access from code

If you need to check if the current user has access to a certain table in Ax, this little job will help you out:


boolean CheckSecurityTable(TableId myTableId)

{ SysDictTable SysDictTable;
;
SysDictTable = new SysDictTable(myTableId);
if(SysDictTable.rights()<AccessType::View)

return false;


return true;
}


If you like your code a little more condensed, you could always use the built-in function hastableaccess. For example:

if(hastableaccess(tablenum(SalesLine),AccessType::View))
return true;


Something similar, but now with a menuitem:


boolean CheckSecurityMenu(MenuName myMenuName)

{ SysDictMenu SysDictMenu;
;
SysDictMenu = SysDictMenu::newMenuItem(myMenuName,MenuItemType::Display);
if(SysDictMenu.rights()<AccessType::View)

return false;


return true;
}



Greetz,


Willy

Tuesday, June 2, 2009

Colors in Ax

When using Ax, sooner or later you gonna use your own colors. But different functions in Ax (and extensions) require different definitions of these colors.

There are different coding system for colors. Some functions require the hexadecimal format, others expect an integer and so on.
Luckily, Ax comes with some built in functions to do the necessary conversions for you. Like this one:

WinApi::RGB2int() - Convert the red, green and blue values to an integer.
WinApi::RGBCon2int() - Dito, but with a container input
WinApi::RGBInt2con() - The opposite, converting from an integer to separate red, green and blue values

Or create your own functions, like this one for example:

static str int2RGBhex(int color)
{ container colorcon;
str colorhex;
;
colorcon=WinApi::RGBint2Con(color);
colorhex=Global::int2Hex(conpeek(colorcon,1),2);

colorhex+=Global::int2Hex(conpeek(colorcon,2),2);
colorhex+=Global::int2Hex(conpeek(colorcon,3),2);

return colorhex;
}

This function will convert an integer color value to a hexadecimal one.

Happy coding,


Willy

Thursday, May 28, 2009

The Task Recorder, your friend in tutorial need

Yes, Ax comes with a manual. And yes, they have become better over the years. But then and now, there always was the need to create custom help documents for users.

Standard Ax now comes with a real friend in need: The Task Recorder.

What it does? Record all your actions in Ax, creating screen shots included.

How it works? Easy.



Open the Task Recorder form. You can find it under the Tools menu, Extra option.





Press the red button on the left to start recording. (pretty old skool huh?)


Then just perform the tasks you want to cover in your instruction set. For example go to the AR module, open the customers form and create a new customer. Perform the necessary steps to create a new customer account.


When you're done, press the second icon from the left on the Task Recorder to stop recording. That's all folks.


Now when you want to create a nice document explaining the process of creating a new customer, you use the icon on the right in the Task Recorder. You get an overview of all recorded tasks. Use the button Generate document to let Axapta generate a complete Word document, containing screenshots, navigation instructions, headers and so on. Yes, it's that easy and that fast!



This nifty little tool was previously only available for partners through Partnersource, but in Ax 2009 SP1, it is generally available in standard Axapta.


So, what are you waiting for? Go create those tutorials.





Greetz,





Willy