venerdì 28 settembre 2012

AX 2009 - Ordinare in modo decrescente le righe di un form

Ax in automatico ordina le righe della grid di un form in modo crescente. Se vogliamo cambiare l'ordine in decrescente, la procedura è semplice. Basta modificare il metodo "init" nel datasource della form in cui ci sono i dati della grid.


 public void init()  
 {  
   super();  
   this.query().dataSourceTable(tableNum(SalesTable)).addSortField(fieldNum(SalesTable, RecId),SortOrder::Descending);  
 }  

AX 2009 - Lookup con lista dei campi di tabella

Vi serve visualizzare una lista di tutti i campi di una determinata tabella? Ecco il codice da implementare nel metodo "lookup" (override sul campo del datasource in cui vogliamo visualizzarla).

 public void lookup(FormControl _ctrl, str _filterStr)  
 {  
   SysTableLookup       sysTableLookup = SysTableLookup::newParameters(Tablenum(Utilelements),_ctrl);  
   Treenode             formnode;  
   Query                query = new Query();  
   QueryBuildDataSource queryBuildDataSource;  
   Treenode             datasource;  
   TableName            tableName;  
   TableId              tableId;  
   ;  
    
   formnode = treenode::findNode(@'\forms\SalesTable\data sources'); //form dove è contenuto il datasource in questo caso SalesTable
   
   datasource = formnode.AOTfirstChild();  
   tableName = datasource.AOTgetProperty('Table');  
   if (datasource)  
   {  
     tableId = tablename2id(tableNam);//id di SalesTable  
     sysTableLookup.addLookupfield(fieldnum(UtilElements, Name));  
     queryBuildDataSource = query.addDataSource(tablenum(UtilElements));  
     queryBuildDataSource.addRange(fieldnum(utilelements,recordtype)).value(queryvalue(UtilelementType::TableField));//range sul campo RecordType, dove specifichiamo che vogliamo i campi di una tabella
     queryBuildDataSource.addRange(fieldnum(Utilelements,ParentId)).value(queryvalue(tableId));  // range sul ParentId dei campi, gli specifichiamo quello di SalesTable
     sysTableLookup.parmQuery(query);  
     sysTableLookup.performFormLookup();  
   }  
 }  
A presto il lookup che visualizza la lista delle tabelle!

lunedì 24 settembre 2012

AX 2009 - Aggiunta e gestione di immagini come campo di tabella


L'argomento che andiamo a trattare è il caricamento e la visualizzazione di un immagine.
Un caso frequente di utilizzo sono, ad esempio, dei listini articoli con l'immagine dell'articolo dentro AX.

Per prima cosa dovremo creare sulla nostra tabella un nuovo campo di tipo Container,

e impostare le proprietà del campo come segue:


le modifiche a livello di tabella finisco qui, vediamo ora come fare per visualizzare e modificare l'immagine da un form.
Sul form avremo bisogno di due controlli, un campo di tipo Windows e Button.

 impostiamo le proprietà dei controlli come segue:


Adesso  ci mancano solo i metodi per gestire il tutto.
Iniziamo con l'override del metodo clicked del bottone appena creato.

 void clicked()
 {
   FilenameFilter filter = ['Image Files','*.bmp;*.jpg;*.gif;*.jpeg'];
   BinData binData = new BinData();
   str extention, path, nameOfFile;
  
   super();
  
   imageFilePathName = WinAPI::getOpenFileName(element.hWnd(),filter,'', "@SYS53008", '','');
   if (imageFilePathname && WinAPI::fileExists(imageFilePathName))
   {
  
     [path, nameOfFile, extention] = fileNameSplit(imageFilePathName);
     if (extention == '.bmp' ||
       extention == '.jpg' ||
       extention == '.gif' ||
       extention == '.jpeg')
     {
       binData.loadFile(imageFilePathName);
       imageContainer = binData.getData();
       element.showImage();
       element.insert();
     }
     else
     {
       throw error("@SYS89176");
     }
   }
 }  

In seguito dobbimo realizzare i due metodi 'showImage' e 'insert' che abbiamo appena utilizzato. Questi andranno creati a livello dei metodi globali del form. Il metodo 'showImage' serve a visualizzare l'immagine appena caricata mentre il metodo 'insert' ad inserirla e salvarla sul database.

 void showImage()  
 {  
   Image  logoImage;  
   ;
  
   try  
   {  
     element.lock();
  
     if (imageContainer)  
     {  
       logoImage = new Image();  
       logoImage.setData(imageContainer);  
       Image.image(logoImage);  
     }  
     else  
     {  
       Image.imageResource(0);  
       Image.widthValue(32);  
       Image.heightValue(32);  
     }
  
     element.resetSize();  
     element.unLock();  
   }  
   catch (Exception::Warning)  
   {  
     throw error(strfmt("@SYS19312", imageFilePathName));  
   }  
 }  

 void insert()  
 {  
   ;
  
   ttsbegin;
  
   line = MyTable::find(MyTable_ds.KeyField, true);  
   if(line)  
   {  
     line.Image = imageContainer;  
     line.update();  
   }
  
   ttscommit;  
 }  

E' buona norma non mettere l'immagine nella griglia della panoramica principale ma inserirla in uno degli altri tab del nostro form in modo da non appesantire lo scorrimento del dataSource, dovremo allora aver cura anche di effettuare l'override del metodo 'pageActivated', del tab dove inseriremo l'immagine, come segue:

 public void pageActivated()  
 {  
   ;
  
   super();  
   element.loadImage();
 }  

 void loadImage()  
 {  
   Image    img;  
   MyTable  table;  
   ;
  
   table= MyTable::find(MyTable_ds.KeyField);  
   if (table.Image)  
   {  
     img = new Image();  
     img.setData(table.Image);  
     Image.image(img);  
   }  
   else  
   {  
     Image.image(null);  
   }  
 }  

Il risultato che otterremo sarà qualcosa di simile al seguente... :)


giovedì 20 settembre 2012

AX 2012 - D365FFO - Framework propagazione del valore di testata ordine cliente a tutte le righe

La propagazione di un dato presente in testa ordine cliente a tutte le righe è uno scenario molto frequente in AX. Per questo motivo esite un framework standard che AX mette a disposizione per effettuare questa operazione.

In questo articolo vedremo come utilizzare questo framework estendendo il funzionamento ad un nuovo campo (myField di tipo stringa) che aggiungeremo sia in testata ordine che in riga. L'articolo si riferisce alla versione AX 2012 ma il funzionamento è rimasto invariato sia in AX 4.0 che in AX 2009.

Per prima cosa andiamo in: Contabilità clienti -> impostazioni -> parametri contabilità clienti -> aggiornamenti e clicchiamo sul pulsante "Aggiorna righe ordine":



In questo form sono presenti i campi di testata che attualmente sono gestiti da tale framework. Per ogni campo sono presenti tre possibili azioni:

Mai: Il valore non viene mai propagato alle righe
Sempre: Il valore viene sempre propagato alle righe
Avviso: Il valore viene propagato se l'utente autorizza l'azione

1)Per prima cosa occorre aggiungere il campo "MyField" a SalesTable ed a SalesLine.

2) Aggiungete il campo SalesTable.MyField al FieldGroup HeaderToLineUpdate

3)Modifichiamo la classe aXSalesTable aggiungendo nella class declaration la varianbile che conterrà il valore del campo e successivamente creiamo il metodo parm per tale variabile:

 public str parmMyField(str_MyField= '')  
 {  
   if (!prmisDefault(_MyField))  
   {  
     this.setField(fieldNum(SalesTable, MyField), _MyField);  
   }  
   return SalesTable.MyField;  
 }  

4)Modifichiamo poi la classe aXSalesLine:

 public str parmMyField(str_MyField= '')  
 {  
   if (!prmisDefault(_MyField))  
   {  
     this.setField(fieldNum(SalesLine, MyField), _MyField);  
   }  
   return SalesLine.MyField;  
 }  

 protected void setMyField()  
 {  
      if (this.isMethodExecuted(funcName(), fieldNum(SalesLine, MyField)))  
      {  
           return;  
      }  
      this.setAxSalesTableFields();  
      if (this.isAxSalesTableFieldsSet() || this.AxSalesTable().isFieldModified(fieldNum(SalesTable,MyField)))  
      {  
           this.parmMyField(this.axSalesTable().parmMyField());  
      }  
 }  

5)aggiungere nel metodo setTableFields() il nostro metodo in coda agli altri

 this.setMyField();  

6)Modifichiamo infine il metodo lineUpdateDescription() della classe SalesTable2LineFiled aggiungendo allo switch il case che gestisce il nostro campo:

 case fieldnum(SalesTable, MyField):  
 return fieldid2pname(tableNum(SalesLine), fieldNum(SalesLine, MyField));  

Riaprendo il form dei parametri di aggiornamento dovremmo ora vedere il nuovo campo:


A questo punto occorre solo rigenerare il contenuto della tabella SalesTable2LineParameters per far sì che AX posso gestire l'aggiornamento . Dobbiamo quindi per prima cosa cancellare tutto il contenuto per poi rigenerarlo tramite il metodo initiate(). Questa operazione và fatta per ogni company. Possiamo scrivere il seguente job che esegue tutte le operazioni:

 static void SalesTable2LineParametersInit(Args _args)  
 {  
   DataArea DataArea;  
   SalesTable2LineParameters SalesTable2LineParameters;  
   ;  
   ttsBegin;  
   while select DataArea where dataArea.isVirtual == NoYes::No  
   {  
     changeCompany(DataArea.id)  
     {  
       delete_from SalesTable2LineParameters;  
       SalesTable2LineParameters::initiate();  
     }  
   }  
   ttsCommit;  
   info("terminato");  
 }  

D365FFO:

Per effettuare la stessa modifica su D365 i passaggi sono quasi analoghi, ovviamente i passi 3) 4) e 5) vanno implementati tramite CoC, mentre per il punto 6) occorre sottoscrivere il delegato "lineUpdateDescriptionDelegate" così:

 [SubscribesTo(classStr(SalesTable2LineField), delegateStr(SalesTable2LineField, lineUpdateDescriptionDelegate))]  
   public static void SalesTable2LineField_lineUpdateDescriptionDelegate(FieldId _fieldId, TableId _tableId, EventHandlerResult _result)  
   {  
     FieldLabel       description;  
     switch(_fieldId)  
     {  
       case fieldNum(SalesTable, MyField) :  
         description = fieldId2pname(tableNum(SalesLine), fieldNum(SalesLine, MyField));  
         _result.result(description);  
         break;  
     }  
   }  

martedì 18 settembre 2012

Accedere alle proprietà di un datasource via codice

Molto spesso è utile poter accedere alle proprietà di un datasource via X++. Supponiamo di voler modificare run-time la proprietà "Allow edit" del campo "ItemName":
 inventTable_DS.object(fieldNum(InventTable,ItemName)).allowEdit(condition);  

lunedì 3 settembre 2012

AX 2009 - Aggiungere un campo array come range di un QueryBuildDataSource

Una delle cose più semplici su Ax è definire un range ad un determinato QueryBuildDataSource da voi definito, in modo da filtrare la vostra query. Infatti:




   Query                query     = new Query();  
   QueryBuildDataSource queryBuildDataSource;
   str                  value;
   ;  
   queryBuildDataSource = query.addDataSource(tablenum(CustTrans));  
   
   queryBuildDataSource.addRange(fieldnum(CustTrans, TransDate)).value(queryValue(today()));  
  


Non del tutto banale è definire un campo array come range (come ad esempio il campo Dimension). Ecco la semplice ma non troppo conosciuta soluzione:


 queryBuildDataSource.addRange(FieldId2Ext(FieldNum(CustTrans,Dimension),1)).value(queryValue(value));  

AX 2009 - Lookup custom su dialog

In questo articolo vedremo come effettuare l'override del metodo lookup su un campo di una dialog. Attenzione: non è possibile effettuare l'override del metodo lookup se si stà lanciando un report

Supponiamo di voler effettuare la lookup custum sul campo itemId di una dialog e di vol filtrare solo gli articoli di tipo formula.

La nostra classe, oltre i metodi ( dialog(), getFromDialog(), pack() e unpack()) dovà contenere l'override del metodo dialogPostRun():

 public void dialogPostRun(DialogRunbase _dialog)  
 {  
   ;  
   _dialog.dialogForm().formRun().controlMethodOverload(true);  
   _dialog.dialogForm().formRun().controlMethodOverloadObject(this);  
   _dialog.formRun().controlMethodOverload(true);  
   _dialog.formRun().controlMethodOverloadObject(this);  
   super(_dialog);  
 }  


a questo punto facciamo tasto destro sul controllo per vedere il nome che AX gli ha assegnato:


Come vediamo AX ha assegnato al controllo il nome "Fld1_1". Di conseguenza per effettuare l'override della lookup bisogna aggiungere il metodo Fld1_1_lookup(), se avessimo voluto fare l'override del modified avremmo dovuto aggiungere un metodo chiamato Fld1_1_modifie, in generale quindi la sintassi è <nome controllo>_nome_metodo():

 boolean Fld1_1_lookup()  
 {  
   FormStringControl control = dialog.formRun().controlCallingMethod();  
   SysTableLookup     sysTableLookup =  SysTableLookup::newParameters(tablenum(InventTable), control);  
   Query          query = new Query();  
   QueryBuildDataSource  queryBuildDataSource;  
   QueryBuildRange     queryBuildRange;  
    ;  
   //lista dei campi da mostrare nella lookup  
   sysTableLookup.addLookupfield(fieldnum(InventTable, ItemId));  
   sysTableLookup.addLookupfield(fieldnum(InventTable, ItemName));  
   sysTableLookup.addLookupfield(fieldnum(InventTable, ItemType));  
   //filtri  
   query.addDataSource (tableNum( InventTable) ).addRange( fieldNum( InventTable,ItemType)).   value(queryValue(ItemType::Formula));
  
   sysTableLookup.parmQuery(query);  
   sysTableLookup.performFormLookup();
  
   return true;  
 }   


 A questo punto la nostra lookup sugli articoli mostrerà solo quelli di tipo "Formula":



La stessa cosa potrebbe essere fatta in teoria anche su un campo del "Seleziona",anche se è sconsigliabile farlo, qualora la lookup standard non fosse disponibile:


La nostra classe dovrà così essere modificata:

 class LIL_TestQueryLookup extends RunBaseBatch  
 {  
   Range      range;  
   SysQueryRun   queryrun;  
   DialogRunbase  dialog;  
   #define.CurrentVersion(1)  
   #define.Version1(1)  
   #localmacro.CurrentList  
     range  
   #endmacro  
 }  

 public Object dialog()  
 {  
   FormBuildStringControl queryField;  
   ;  
   dialog = super(dialog);  
   //settiamo le proprietà del controllo   
   //per renderlo attivo, editabile e far apparire il pulsante di lookup  
   queryField = Dialog.dialogForm().control("Fld1_1");  
   queryField.enabled(true);  
   queryField.allowEdit(true);  
   queryField.lookupButton(FormLookupButton::Always);  
   queryField.text("");  
   return dialog;  
 }  

 public boolean getFromDialog()  
 {  
   FormBuildStringControl queryField;  
   boolean ret;  
   ret = super();  
   queryField = Dialog.dialogForm().control("Fld1_1");  
   range = queryField.text();  
   return ret;  
 }  

conviene infine disattivare il pulsante "Select"  ovverridando il metodo showQuerySelectButton() per evitare che l'utente, cambiando range sulla query, possa sovrascrivere il nostro controllo "Fld1_1"

 boolean showQuerySelectButton()  
 {  
   return false;  
 }