Pages

Thursday, November 27, 2008

Set focus on a particular field on an EP page

With the AX 2009 Enterprise Portal you now have an easy way to set focus on a particular field on a page.

These are the steps you need to go through:
  1. The field must be created as or converted to a TemplateField. The "Dynamics AX Bound Field Designer" helps you to that. Add the field to the "Selected Field" list, make it active and click "Convert to Template field"
  2. Switch to the Source view of the page.
  3. It seems like you have to add the label of the fields yourself. This is done by adding the following section to the XML of the TemplateField: HeaderText="<%$ AxLabel:@SYSxxxxx %>
  4. Finally add code to set the focus:

    TextBox TextBox = (TextBox)this.AxGroup1.FindControl("TextBox1");
    if (TextBox != null)
    {
    TextBox.Focus();
    }

Wednesday, November 5, 2008

EP 2009: Edit a record outside a grid

Here is what you can do if you want to edit a record that is not part of a grid.


The record needs to be shown through an AxForm control and on this control you can set the "AutoGenerateEditButton" to True to get an Edit/Update and Cancel button automatically added to the form.


Next you may need to add event handlers to these buttons if you want the system to do something special in case these are clicked.


This is how the event handler for update could look, if you want to run an additional action on the DataSet:


private
void AxForm_MyForm_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)

{


if (e.Exception == null)

{


// Run the approval code


this.MyDataSet.GetDataSet().DataSetRun.AxaptaObjectAdapter.Call("MYMethodOnTheDataSet");



// Redirect to the list of invoices


AxUrlMenuItem listpage = new
AxUrlMenuItem("MyMenuItem");

Response.Redirect(listpage.Url.OriginalString);

}

}


And here is how the code for the Cancel button could look:


private
void AxForm_MyForm_ItemCommand(object sender, DetailsViewCommandEventArgs e)

{


if (e.CommandName == "Cancel")

{


this.RedirectToPreviousPage();

}

}


The RedirectToPreviousPage method is a method you have to add yourself. It could look like this:

private
void RedirectToPreviousPage()

{


// Return to the previous page displayed.


if (!String.IsNullOrEmpty(AxWebSession.GetPreviousURL(this.Page)))

{

Response.Redirect(AxWebSession.GetPreviousURL(this.Page));

}


else

{


// The previous page is not available, so return to a known page.


// Use the URL Web Menu item from the AOT to specify which


// page will be displayed.


AxUrlMenuItem listpage = new
AxUrlMenuItem("MyMainPage");

Response.Redirect(listpage.Url.OriginalString);

}

}



And finally you must add you new event handler to the event handlers for the two buttons. This is done in the Page_Init method:


protected
void Page_Init(object sender, EventArgs e)

{


this.AxForm_MyForm.ItemUpdated += new
DetailsViewUpdatedEventHandler(AxForm_MyForm_ItemUpdated);


this.AxForm_MyForm.ItemCommand += new
DetailsViewCommandEventHandler(AxForm_MyForm_ItemCommand);

}

Friday, October 31, 2008

Setting up E-mail parameters on the AX 2009 VPC July 2008 image

In order to send e-mail within this image, you need to setup SMTP parameters in AX:

Go to Administration / Setup / E-mail parameters:

Default in the form you'll find the follwing settings:
SMTP port number: 25
Attachment size limit (MB): 10

Enter the following new values:
Outgoing mail server: contoso.com
Local computer name: dynamicsvm

And here is a small job to see if mail can be sent:

static void testMail(Args args)
{
SysMailer   sysMailer = new SysMailer();

;
sysMailer.quickSend("administrator@contoso.com", "administrator@contoso.com", "Subject", "Body");
}


Please note that the test mail might end up in your Outlook junk-mail folder.

Thursday, October 23, 2008

Finding unused labels

Here is a small job for finding labels, from a particular label file, that are not used in the application. You need an updated cross reference to use the job.
static void findUnXedLabels(SysOperationProgress _progress = null)
{
Label label = new Label('da'); // The language to use for finding the labels
str 250 labelId = label.searchFirst('');
Map mapLabels = new Map(types::String, types::String);
MapEnumerator mapEnumerator;
;

setPrefix("Finding UnX'ed labels");

while (labelId)
{
if (label.moduleId(labelId) == "XYZ") // The particular label file
{
if ((select xRefNames
index hint Name
where xRefNames.Kind == xRefKind::Label &&
xRefNames.Name == labelId &&
xRefNames.TypeName == '').RecId == 0)
{
mapLabels.insert(labelId, label.extractString(labelId));
}
}
labelId = label.searchNext();
}

// The maps is used to sort the lables
mapEnumerator = mapLabels.getEnumerator();

while (mapEnumerator.moveNext())
{
info (strFmt("%1 %2", mapEnumerator.currentKey(), mapEnumerator.currentValue()));
}
}

In standard AX you can run this check for all labelfiles and save the result to a file, by using the \Classes\SysApplCheck\findUnXedLabels method.

Monday, October 20, 2008

Saving the reference to a unique record as a string

Sometimes you have have to store a unique reference to record as a string, for example if you want to include this reference in an XML file.

Merely converting the RecId to a string is not always a good idea. The best choice for identifying the record may be another unique key of the table, especially if the table doesn't have a RecId index.

On the Global class there is a method to analyze the unique keys of a table and give you the unique key as a string. This is the record2Dynakey method.

The key is encoded in the string like this:
[fieldid:strvalue][fieldid2:strvalue][fieldid3:strvalue]

Later when you want to convert the string, representing the key, to an actual record, just call the dynaKey2Record method.

You should not use this technique for long term storage of keys, since renaming primary keys or changing the datamodel might break the ability to restore the string into the right record or restoring it in the most sufficient way.

Friday, October 10, 2008

Manage Enterprise Portal deployments

In a multi server setup of EP 2009, you could be tempted to install the .NET Business Connector as the only AX part on the IIS.

It turns out that you must also install a regular client, because the "Manage deployments" form only will run from the box where EP is deployed...

Sunday, September 28, 2008

Thursday, September 25, 2008

ImageListAppl classes

The idea with the ImageListAppl classes is that you load and cache a list of images once in for example a form. For each record where you want to show an image, through a display method you just lookup the image in the cached list.

However I often see code where the image list is loaded in the display method itself, putting some overhead on the display method.

Steps to use an ImageListClass
First of all, create a form window control to hold the images. These would be appropriate properties if you place this control in a grid:
PropertyValue
AutoDeclarationYes
AllowEditNo
Width14
Height14
EnabledNo
SkipYes
AlignControlNo
ImageModeSize to fit
ShowLabelNo
DataSourceYour controlling datasource
DataMethodYour method to select the right image

In the ClassDeclaration of the form declare your ImageListAppl class:
ImageListAppl_MyImageList imageListAppl;


In the init method of the form, initialize your ImageListAppl object:


imageListAppl = new ImageListAppl_MyImageList();


And still in the init method pass the list of images to your window control:

myWindowControl.imageList(imageListAppl.imageList());


Implement your display method driving which image to show:

//BP Deviation Documented
display ImageRes myDisplayMethod(MyRecord _myRecord)
{
ImageRes res = -1;
#resAppl;
if (_myRecord.someConditioin())
{
res = imageListAppl.image(#MyImage);
}
return res;
}


That's it...

Wednesday, August 13, 2008

SharedView troubleshooting

If you have the problem that people joining your SharedView sessions only see scramled images, setting the hardware acceleration of your graphics hardware til None might do the trick:


Thanks to Poppastring.com for having published this solution.

See the SharedView forum for more troubleshooting tips.

Friday, July 11, 2008

Sample union query from AX 2009

Queries build with the Query classes now supports unions, meaning that you can combine the result from several tables into one result set. The results you want to combine from the different tables must be structured the same way for all tables.

You could for example create a query combining CustTable and VendTable. This would be particularly useful if you need to present for example a lookup form showing both customers and vendors in the same grid. In earlier version you’d have to push customer and vendor data to a temporary table before being able to present the combined data in one grid.

Here is an example on how to build and use a union query from X++:

static void union(Args _args)
{
Query query;
QueryBuildDataSource qbdsCustTable;
QueryBuildDataSource qbdsVendTable;
QueryRun queryRun;
CustTable custVendTable;
Map mapTableBranches = new Map(types::Integer, typeId2Type(typeId(TableId)));
SysDictTable dictTable;
;



// The map is used to match the UnionBranchID with a table id
mapTableBranches.insert(1, tableNum(CustTable));
mapTableBranches.insert(2, tableNum(VendTable));


query = new Query();
query.queryType(QueryType::Union);


qbdsCustTable = query.addDataSource(tableNum(CustTable));
qbdsCustTable.unionType(UnionType::UnionAll); // Include duplicate records
qbdsCustTable.fields().dynamic(false);
qbdsCustTable.fields().clearFieldList();
qbdsCustTable.fields().addField(fieldNum(CustTable, AccountNum));
qbdsCustTable.fields().addField(fieldNum(CustTable, Name));


qbdsVendTable = query.addDataSource(tableNum(Vendtable));
qbdsVendTable.unionType(UnionType::UnionAll); // Include duplicate records
qbdsVendTable.fields().dynamic(false);
qbdsVendTable.fields().clearFieldList();
qbdsVendTable.fields().addField(fieldNum(VendTable, AccountNum));
qbdsVendTable.fields().addField(fieldNum(VendTable, Name));


queryRun = new QueryRun(query);
queryRun.prompt();

while (queryRun.next())
{
custVendTable = queryRun.getNo(1);
dictTable = SysDictTable::newTableId(mapTableBranches.lookup(custVendTable.unionAllBranchId));

info (strFmt("%1 %2 (%3)", custVendTable.AccountNum,
custVendTable.Name,
dictTable.name()));
}
}

Thursday, May 29, 2008

Wednesday, May 28, 2008

Using the AutoRun parameter to open a form in AX

In AX 4.0 and later you can use a start up parameter called AutoRun and combined with an XML file you can automate a lot of stuff in AX after starting the client.

The command line for AutoRun looks like this:
ax32.exe –StartupCmd=AutoRun_ c:\folder\filename.XML

The XML file holds descriptions of the commands to execute when the client is started.

The following is an example on how you can use this to open the CustTable form immediately after login.

First create the XML fle:

The exitWhenDone tag controls if AX should shut down after completing the tasks listed in the XML file. We don't want that in this case.

I'm saving the file as C:\OpenCustTable.XML.

And then you could create a specfic configuration file to use this start up parameter:

You could of course also just point to this at the command prompt.

That's it...

You can add as many commands as you want to the XML file, so you can really automate a lot of stuff.

The supported commands includes, amongst others:

  • Runnning installation, upgrade or configuration check lists
  • Loading license information
  • Compiling the application
  • Syncronize the database
  • Importing data
  • Importing XPO's
  • Running specific elements (like described on the above example)

Search the Administrator Help for "SysAutoRun" to read the included documentation.

Tuesday, May 27, 2008

Change the appearance of printed report ranges

In AX you can order selected reports asking the system to print the ranges of your query.

To try this, go to Accounts Receivable / Reports / Base data / Customer.

  • Click Select
  • Click Print Options
  • Check the “Print ranges” check box.

The report header ends up looking something like this example:



Technically this is a fine solution, but it’s not very sexy. So how do you go about changing this section? Take a look at the method \Classes\SysReportRun\buildPrintRanges. This is where AX on the fly builds a new report section with the needed fields and it executes the section to print the requested information.

For a regular no-nonse report like the Customer report, this feature is called from the report template behind the report: \Reports\Report Templates\InternalList\PageHeader:PgHdr_1\Methods\executeSection:

public void executeSection()
{;
SysReportRun::executePrintRangeSection(element);
super();
}
If you switch the two lines in this method, you will get the ranges printed under the regular page header, which I think is a bit nicer:



You can add the same lines to your own reports that may not use this template, as long as it is called from the page header section.

With a few lines of additional code in \Classes\SysReportRun\buildPrintRanges you can create cool effects (well, semi-cool):

This will produce a result like this (assuming you have also changed the order of the lines in \Reports\Report Templates\InternalList\PageHeader:PgHdr_1\Methods\executeSection as described above):

Transform a date into a number of months from a specific offset date

Here is a small algorithm to transform a date into a number of months from a base date. You can for example use this if you need to create a report with numbers places in a column per month starting with the month of a specific date.

The algorithm to transform the date into a month is this:
if (columDate < dateEndYr(offSetDate))
{
column = mthOfYr(columDate) - mthOfYr(offSetDate) + 1;
}
else
{
column = 12 - mthOfYr(offSetDate) + mthOfYr(columDate) +1;
}

And here is a job to illustrate the usage:

static void date2Column(Args _args)
{
Date offsetdate;
Date columDate;
int column;
int counter;
;

offSetDate = 30\09\2008;
columDate = offSetDate;
while (counter <= 11)
{
counter++;
// The actual algorithm -->
if (columDate < dateEndYr(offSetDate))
{
column = mthOfYr(columDate) - mthOfYr(offSetDate) + 1;
}
else
{
column = 12 - mthOfYr(offSetDate) + mthOfYr(columDate) +1;
}
// The actual algorithm <--
print strFmt("Date: %1 Month: %2", columDate, column);
columDate = dateMthFwd(columDate, 1);
}
pause;
}


UPDATE: As you can see from the comments to this post, I could have simply used the following built in function:
intvNo(columDate, offsetDate, IntvScale::YearMonth) + 1

Friday, May 23, 2008

AX 2009 RTM

AX 2009 RTM is now available on PartnerSource (requires PartnerSource account).

The download is located here.

Tuesday, May 13, 2008

Data Migration Tool - Microsoft XAL to Microsoft Dynamics AX

Since support for XAL soon ends, hopefully many XAL customers will make the switch to AX.

To ease the switch, MS has updated their data migration tool to now support AX 4.0.

The tool can be downloaded from PartnerSource (requires PartnerSource login)

Tuesday, May 6, 2008

Dynamic date ranges in queries

When you send recurring jobs to the batch queue, there will be cases where you’d like some of your date criteria to be dynamic to match the system date.

You can use the “d” shortcut as criteria when you enter query selections and the system will convert “d” to the system date when the batch job is executed.

You can also use “d” in ranges:
..d = From date null (1/1/1900) until today
..d, !d = From date null (1/1/1900) until yesterday

I have tested these criteria on 4.0, I’m not sure you can do this with earlier versions. But please let me know if you can.

Changing the keyboard layout for the login of a VPC image

The VPC images we get form MS are always set up with US keyboard layout, so when you login to the the VPC image you have to know where the special characters in the password are located on a US keyboard layout.

Click here to get a solution on how you change the keyboard layout for the VPC image, so it also affects the login screen.

Thank you Joris, for this great tip.

Wednesday, April 2, 2008

Read data from other databases

Here is a small exsample on how to do that from version 4.0 and greater:
ODBCConnection                connection;
SqlSystem sqlSystem = new SqlSystem();
LoginProperty loginProperty = sqlSystem.createLoginProperty();
Statement statement;
ResultSet resultSet;
SqlStatementExecutePermission sqlStatementExecutePermission;
str sqlStatement;
int columnId;
;
loginProperty = sqlSystem.createLoginProperty();
loginProperty.setServer('MyServer');
loginProperty.setDatabase('MyDatabase');

connection = new ODBCConnection(loginProperty);

statement = connection.createStatement();

sqlStatement = "select * from MyTable";

sqlStatementExecutePermission = new SqlStatementExecutePermission(sqlStatement);
sqlStatementExecutePermission.assert();

resultSet = statement.executeQuery(sqlStatement);

while (resultSet.next())
{
// Get data from colum 2
columnId = 2;
print resultSet.getString(columnId);
}
pause;

Make sure the code is executed on the AOS, as only the AOS is allowed to access databases outside AX.

Thursday, March 6, 2008

Microsoft SharedView

Stuff like this is seen before, but now MS has made it really easy to use. Microsoft SharedView allows you to share documents and screen views with groups of other people. I think this could be a great tool for AX customers on remote sites. You can use it to show them how to do stuff in AX if they have particular questions and you can use it the other way if they have some AX issues you'd like to see them reproduce.

Here is a link to the download and more information: http://www.connect.microsoft.com/content/content.aspx?ContentID=6415&SiteID=94

Tuesday, February 26, 2008

Spell checking from AX

The AX class SysSpellChecker is a wrapper for the spellchecker of Word. Using this class you can offer spellchecking from AX.

Here is some sample code, checking text from a form string control:

SysSpellChecker spellChecker = SysSpellChecker::newCurrentLanguage();
TextBuffer textBuffer = new TextBuffer();
int wordStart;
int startSeparator;
int endSeparator;
str wordToCheck;
List spellingSuggestions;
ListEnumerator listEnumerator;
;
super();
startLengthyOperation();
setPrefix("Spell check");
textBuffer.setText(stringEdit.text());
startSeparator = 1;

while (startSeparator)
{
wordStart = textBuffer.find('[^ \n\t\\<\\>\\!\\\'\\\\\\#\\¤\\%\\&\\/\\(\\)\\=\\?\\,\\.\\:\\;\\*\\}\\{\\]', startSeparator) ? textBuffer.matchPos():0;

if (!wordStart)
break;

endSeparator = textBuffer.find('[ \n\t\\<\\>\\!\\\'\\\\\\#\\¤\\%\\&\\/\\(\\)\\=\\?\\,\\.\\:\\;\\*\\}\\{\\]', wordStart)? textBuffer.matchPos():0;

wordToCheck = textBuffer.subStr(wordStart, (endseparator ? endseparator : textBuffer.size()+1) - wordStart);

if (spellChecker.checkSpelling(wordToCheck) == false)
{
warning (strfmt("@SYS84009", wordToCheck));
spellingSuggestions = spellChecker.getSpellingSuggestions(wordToCheck);
listEnumerator = spellingSuggestions.getEnumerator();

while (listEnumerator.moveNext())
{
info (strFmt("Suggestion: %1", listEnumerator.current()));
}
}
startSeparator = endSeparator;
}

spellChecker.finalize();

info ("Spell check done");

endLengthyOperation();

Unfortunately there is a bug in the checkSpelling method of the COM object from Word, so it will always use the default language of your Office setup as the language to check your text against. So you can't spellcheck for other languages than your default language of Office. You can change the default language from the Office Tools however.

Monday, February 25, 2008

What to do if Virtual PC won’t restore its windows

This may be slightly off topic, but I'm sure we will receive more and more VPC images from MS for miscellaneous AX setups in the future.

I have had this problem a few times, especially when working with two monitors. All of sudden Virtual PC just will not restore its windows. You can start the program, but can't work with it.

The cause is that VPC somehow stores totally weird values for where to open the windows. So when you try to open the windows they kind of do open, but with coordinates that is nowhere near your own neighborhood.

The solution is to edit the options.xml file located in the %appdata%\Microsoft\Virtual PC folder. In the <window> <console> section, you should change the values for <left_position> and <top_position>, for example to 10, 10 which would place the window in the top left corner of your screen.

Wednesday, January 16, 2008

Fill in PDF based forms from AX

I stumbled accross the AX class PDFFiller the other day, while investigating some PDF stuff.

It turns out that you can use this report to fill in PDF based forms. The forms must of course be created so it allows the reader to fill in form fields.

The data from the form fields are stored in a file with XFDF extension, an XML file, and this paired with the initial PDF file gives you the filled in form to show on screen or print.

The PDFFiller class is in the standard only used by the Austrian tax report. See \Classes\TaxReport_ReportAT\fillPDFFile.

Also here is a short sample, assuming the PDF file has a "FieldA" and "FieldB" field:
static void PDFFiller(Args _args)
{
PDFFiller PDFFiller;;

PDFFiller = PDFFiller::construct();

PDFFiller.parmPdfFileName("C:\\testpdf.pdf");

PDFFiller.addStrField("FieldA", "This is for the first field");
PDFFiller.addStrField("FieldB", "This is for the second field");

PDFFiller.showFDF();
}

Monday, January 14, 2008

Print PDF files from AX to specific printer

This job illustrates how you can print an external PDF file to a printer choosen in AX:

static void pdfprint(Args _args)
{
PrintJobSettings printJobSettings = new PrintJobSettings();
Dialog dialog = new Dialog();
DialogField dialogFileName;
str adobeExe;
str adobeParm;
;
dialogFilename = dialog.addField(typeid(FilenameOpen));

if (dialog.run())
{
printJobSettings.printerSettings('SysPrintForm');
adobeExe = WinAPI::findExecutable(dialogFileName.value());

adobeParm = strFmt(' /t "%1" "%2" "%3" "%4"',
dialogFileName.value(),
printJobSettings.printerPrinterName(),
printJobSettings.printerDriverName(),
printJobSettings.printerPortName());

winAPI::shellExecute(adobeExe, adobeParm);
}
}

Monday, January 7, 2008

Get the literal label of an enum value

I couldn't find a function in the standard to give me the literal label (@SYSxxxx) of an enum value, so I came up with this prototype:

static void literalLabelsFromEnum(Args _args)
{  
    #Properties         
    SysDictEnum sysDictEnum = new SysDictEnum(enumNum(SysDimension));  
    TreeNode    treeNode;  
    int         x;     
    ;
     
    for (x = 0; x <= SysDictEnum.lastValue(); x++) 
    {           
        treeNode = TreeNode::findNode(sysDictEnum.path() + '\\' + sysDictEnum.index2Symbol(x));           
        print sysDictEnum.index2Label(x)+ '  ' + findProperty(treeNode.AOTgetProperties(),#PropertyLabel);     
    }     
    pause;
}