Monday, April 12, 2010

Communicate with the RunBase Dialog

Sometimes you need to communicate with the RunBase object's dialog. Here are some information what you have to know to make it run well.

The Caller
Say, your RunBase dialog calls a custom form which contains special selection settings for the RunBase object (which f.ex. cannot be integrated in the RunBase dialog due layout specifications). So you've placed a menu item button to your dialog. That way the user can call the custom form with the special selection settings:

DialogRunbase ret;
;
ret = super(dialog, forceOnClient);
ret.addMenuItemButton(MenuItemType::Display, 
    menuItemDisplayStr(MyRunBaseSelectionForm));

But once the custom form is open, how to geht a handle to the RunBase object? To grab the RunBase object you need to dig the callers:

FormRun         formRunCaller;
DialogRunBase   dialogRunBase;
MyRunBaseClass  caller;
;
formRunCaller = element.args().caller(); // returns the dialog's form

// returns a DialogRunBase object
dialogRunBase = formRunCaller.args().caller();


// finally returns the RunBase object
caller = dialogRunBase.runBaseBatch(); 

Once you have the handle to the RunBase object you can acceed all public parameter methods and even the RunBase's QueryRun object.

Notify the caller
If the user decide to close your custom form with confirmation, so you need to notify the RunBase object about the new data. The best way is to place this code in the method closeOK() on your custom form:

public void closeOk()
{
    QueryRun qr = caller.queryRun();
    ;
    qr.whatEverYouHaveToManipulate(...);
    caller.parmThisAndThat(...);
    super();
}

Update the dialog values
As the user shall see the setting changes, you need to update the dialog controls. That for you can use the method dialogUpdate():

public void closeOk()
{
    QueryRun qr = caller.queryRun();
    ;
    qr.whatEverYouHaveToManipulate(...);
    caller.parmThisAndThat(...);
    caller.dialogUpdate(); // updates at least query range fields
    super();
}

If you want a little tutorial, you can download this sample project, demonstrates just what I tried to explain. Import the XPO file and open the class MyRunBaseClass, then click on the button "Pick customer".
Keep in mind, it's only a tutorial, the code is neither BP nor fool proofed!

Hope this can improve your coding skills :)

Mit dem Dialog eines RunBase-Objektes kommunizieren

Manchmal ist es nötig mit dem Dialog eines RunBase-Objekts zu kommunizieren. Nachstehend ein paar nützliche Informationen damit es auch gelingt.

An den Aufrufer gelangen
Angenommen der RunBase-Dialog ruft ein weiteres Formular auf, welches über spezielle Selektionseinstellungen für das RunBase-Objekt verfügt (z.B. wenn die Darstellung dieser speziellen Einstellungen im RunBase-Dialog nicht möglich ist). So wird dem Dialog also ein Menu-Item-Button hinzugefügt, damit der Benutzer das Formular mit den speziellen Einstellungsmöglichkeiten aufrufen kann:

DialogRunbase ret;
    ;
    ret = super(dialog, forceOnClient);
    ret.addMenuItemButton(MenuItemType::Display, menuItemDisplayStr(MyRunBaseSelectionForm));

Wie erhält nun aber das Formular Zugriff auf das RunBase-Objekt? Dafür muss durch die Aufrufer 'gegraben' werden:

FormRun         formRunCaller;
    DialogRunBase   dialogRunBase;
    MyRunBaseClass  caller;
    ;
    formRunCaller = element.args().caller(); // returns the dialog's form
    dialogRunBase = formRunCaller.args().caller(); // returns a DialogRunBase object
    caller = dialogRunBase.runBaseBatch(); // finally returns the RunBase object

Wenn man über das Handle des RunBase-Objekts verfügt, hat man Zugriff auf alle öffentlichen Parametermethoden und auch auf das QueryRun-Objekt des RunBase-Objekts.

Den Aufrufer benachrichtigen
Wenn der Benutzer nun das Formular mit den speziellen Selektionskriterien mit OK schliesst, muss das RunBase-Objekt mit den allenfalls vorgenommenen Einstellungen aktualisiert werden. Am besten wird dies in der closeOK()-Methode auf dem Formular implementiert:

public void closeOk()
{
    QueryRun qr = caller.queryRun();
    ;
    qr.whatEverYouHaveToManipulate(...);
    caller.parmThisAndThat(...);
    super();
}

Dialogfelder aktualisieren
Damit der Benutzer die gemachten Änderungen auf dem RunBase-Dialog sieht, müss zusätzlich die Dialogfelder aktualisiert werden, dies wird mit der Methode dialogUpdate() sichergestellt:

public void closeOk()
{
    QueryRun qr = caller.queryRun();
    ;
    qr.whatEverYouHaveToManipulate(...);
    caller.parmThisAndThat(...);
    caller.dialogUpdate(); // updates at least query range fields
    super();
}

Ein kleines sample steht als XPO-Datei zum Download bereit und demonstriert, was ich hier zu erklären versucht war. Nach dem Importieren der XPO-Datei einfach die Klasse MyRunBaseClass öffnen und auf die "Pick customer"-Schaltfläche drücken.
Dieser Beispielcode wurde weder auf Optimale Verfahren geprüft, noch ist er gegen Fehler gefeit!

Hoffe das dieser Beitrag den Entwicklerhorizont ein klein wenig erweitert :)

Monday, March 22, 2010

Die firstonly-Falle

Dies ist eine Übersetzung des englischsprachingen Beitrags the firstonly trap.

Die Verwendung des Firstonly-Qualifizierer in einem X++ Select Statement kann die Ausführungsgeschwindigkeit erheblich steigern. Also warum den Qualifizierer nicht auch für update_recordset und delete_from anwenden? Das war meine Idee und so begann ich folgenden Code umzuschreiben:

ttsbegin;
select firstonly forupdate myTable where myTable.NotUniqueField == 'anyValue';
myTable.delete();
ttscommit;

nach

delete_from firstonly myTable where myTable.NotUniqueField == 'anyValue';

Leider entsprach das Resultat nicht den Erwartungen. Alle Datensätze die mit den Abfragekriterien übereinstimmten wurden gelöscht. Zuerst dache ich, delete_from firstonly leide an der gleichen Ungenauigkeit wie select firstonly und deshalb sei nicht sichergestellt, dass immer nur ein Datensatz verarbeitet würde. Aber in Wirklichkeit sieht es eher so aus, dass der Firstonly-Qualifizierer weder für update_recordset noch für delete_from implementiert wurde (auch wenn der X++ Kompiler es akzeptiert).

Andrerseits, T-SQL bietet die Möglichkeit das TOP-Schlüsselwort in Select-, Update- und auch Delete-Anweisungen zu verwenden - und warum sollte der Kompiler schlucken was keinen Sinn macht? Ich könnte mir vorstellen, dass diese Funktionalität in späteren Versionen zur Verfügung steht. Ich werden den Firstonly-Qualifizierer weiterhin gebrauchen, aber nur wenn die Where-Kriterien einen eindeutigen Datensatz referenzieren.

delete_from firstonly myTable where myTable.UniqueField == 'keyValue';
// and
update_recordset firstonly myTable setting AnyField = 'anyValue' where myTable.UniqueField == 'keyValue';

Vielleicht wird Code von heute auch in späteren AX-Versionen verwendet, wenn update_recordset und delete_from den Firstonly-Qualifizierer unterstützen. Ausserdem finde ich, dass es das Lesen des Codes ein wenig erleichtert.

Also tappt nicht auch in diese Falle ;)

The firstonly trap

Using the firstonly qualifier on a X++ Select Statement can increase speed performance. So why don't use it for update_recordset and delete_from too? This was my Idea and I was going on to rewrite this code:

ttsbegin;
select firstonly forupdate myTable where myTable.NotUniqueField == 'anyValue';
myTable.delete();
ttscommit;

into that

delete_from firstonly myTable where myTable.NotUniqueField == 'anyValue';

Unfortunately, the result was not as expected: All records which matched the condition were lost. First I thought the problem was, that a delete_from firstonly has the same inaccuracy as a select firstonly and therefore it wouldn't be granted to proceed only one record. But actually it seems that the firstonly qualifier is neither implemented for update_recordset nor for delete_from (even the X++ compiler accept it).

On the other hand, T-SQL provides the TOP clause in select, update and delete statement too - and why the compiler should swallow it, even it makes no sense? I could imagine, that this features will be implemented in later versions. I will use the firstonly qualifier again, but only if the where conditions refers a unique record.

delete_from firstonly myTable where myTable.UniqueField == 'keyValue';
// and
update_recordset firstonly myTable setting AnyField = 'anyValue' where myTable.UniqueField == 'keyValue';

May be, code from today is still active in later AX versions, when update_recordset and delete_from are supporting the firstonly qualifier. Further I like it as a hint when reading and try to understand source code.

So be warned to fall into that trap :)

Monday, March 8, 2010

Einfacher X++ Taschenrechner

Vielen Lesern dürfte die Rechenfunktion des Steuerlements vom Typ FormRealControl bekannt sein. Falls nicht, hier eine kleine Einführung: Wenn man in einem Feld vom Typ Gleitkommazahl eine komplette Kalkulation eintippt (Abb. a) und anschliessend die Eingabe bestätigt, wird dem Feld der ausgerechnete Wert zugewiesen (Abb. b).

Abb. a:

Abb. b:

So dachte ich, den Nutzen dieses Features dürfte auch durch x++ möglich sein. Das Resultat war schlussendlich eine statische Methode zum Auswerten von Kalkulationen.

So sieht die statische Methode aus:
/// <summary>
/// Calculates a simple math task
/// </summary>
/// <param name="_expression">
/// A simple mathematic expression. +-/* operators and brackets can be used
/// </param>
/// <returns>
/// The calculated result. If the expression does not match the expected format, 
zero is returned.
/// </returns>
/// <remarks>
/// Uses functionality from <c>FormRealControl</c>, therefore client execution 
is required.
/// </remarks>
static client real calcExpression(str _expression)
{
    SysFormRun formRun;
    Args args = new Args();
    FormBuildControl buildCtrl;
    FormRealControl realCtrl;
    
    ;
    args.name(formstr(Dialog));
    formRun = classFactory.formRunClass(args);
    
    buildCtrl = formRun.form().design().addControl(FormControlType::Real, 
    classstr(FormRealControl));
    formRun.init();
    realCtrl = formRun.design().control(buildCtrl.id());
    realCtrl.pasteText(_expression);
    return realCtrl.realValue();
}

Und so kann sie verwendet werden:
info(strfmt('%1', MyClass::calcExpression('5+(5*2)')));

Als Anmerkung bleibt zu sagen, dass die Funktion über alle Einschränkungen verfügt, über die auch das Rechen-Feature des FormRealControl-Objekt verfügt.

Simple x++ Calculator

May be you know the AX GUI calculator of the FormRealControl. If not, I will explain in a short way: If you enter a proper calculation expression in a real field (picture a) and commit the input expression will be calculated and the result set to the field (picture b).

picture a:

picture b:

So I found this functionality can also be used trough x++. The result was a static method which can evaluate an expression to be calculated.

That's the code behind:
/// ;
/// Calculates a simple math task
/// ;
/// ;
/// A simple mathematic expression. 
/// +-/* operators and brackets can be used
/// 
/// 
/// The calculated result. If the expression
/// does not match the expected format, 
/// zero is returned.
/// ;
/// ;
/// Uses functionality from 
/// FormRealControl, 
/// therefore client execution 
/// is required.
/// ;
static client real calcExpression(str _expression)
{
    SysFormRun formRun;
    Args args = new Args();
    FormBuildControl buildCtrl;
    FormRealControl realCtrl;
    
    ;
    args.name(formstr(Dialog));
    formRun = classFactory.formRunClass(args);
    
    buildCtrl = formRun.form().design()
        .addControl(FormControlType::Real, 
        classstr(FormRealControl));
    formRun.init();
    realCtrl = formRun.design()
        .control(buildCtrl.id());
    realCtrl.pasteText(_expression);
    return realCtrl.realValue();
}
And that's how we use it:
info(strfmt('%1', 
    MyClass::calcExpression('5+(5*2)')));

Keep in mind, this function inherits all limits which the FormRealControl calculation feature owns.

Saturday, February 20, 2010

SmartStart 3000 V 1.6 for Dynamics AX

This Article is an announcement about the new Version of SmartStart 3000. You don't know what the SmartStart 3000 is? It's a free configuration manager for Dynamics AX. If you are working with more than just one AX configuration, this tool can make your life easier. A Full feature list can be found on the SmartStart 3000 Central web page.

About the new Version 1.6
It tooks some extra time to publish the new release (or even more time than I tought it would take :).

What's new?
  • Support for Business Connector configurations
  • Possibility to switch current/default configuration (for Client and BC configurations as well)
  • Source filter
  • Extended export functionality
  • Quick access button for custom tools/shortcuts
  • Auto remove for invalid favorites
  • Configuration drag'n'drop-to-target possibility for deletion and export
  • When deleting registry based configurations, they will collected in recycle bin as registry export files (so you can restore them if needed)
  • Global keyboard shortcut for application lookup
Bugfixes
  • Fixed favorite storage (AX version is now included, file storage fixed)
  • Fixed registry export (even the configurations work proper for Dynamics AX Client, user was not able to edit them in the Dynamics AX 2009 Configuration Utility)
  • Fixed unexpected flash flare on start up phase
  • Prohibition of deleting original, installed configurations

Language support
Refreshed language support for the following tongues: Czech, Danish, German, English, Spanish, French, Norwegian, Polish and Swedish. A great thank-you for the dedicated translators!

Documentation
The PDF documentations is updated to the new V 1.6 functionality and shipped with inside the application and also downloadable from the Central web page

Remarks
Due the fixed favorite storage process, all favorites will be cleared when running V 1.6 first time

SmartStart 3000 Argument Configurator
The online Argument Configurator has been extended with preview windows

Known Issues
When using SmartStart 3000 on terminal servers with very low color settings (due slow connection), SmartStart 3000 wont start up. In this case, just use the parameter /noGDI. I'm working to fix that bug, but currently I haven't located the bug source.

Download
Download the new release as usual at: SmartStart 3000 Central

Next week I'm out of office, but I'm looking forward to get your feedback!