2013年2月10日星期日

How to achieve allow user selections multiple value on Dialog Field


用户要求在执行某个动作之前弹出一个对话框,让他选择一些供应商,只针对这些供应商去做动作,一下就会想到用Dialog这个类去做,于是写出如下代码:

static void dialogDemo(Args _args)
{
    Dialog              dialog 
= new Dialog();
    DialogField         dialogField;
    VendAccount         vendAccount;
    boolean             retValue;
    ;
    
    dialogField 
= dialog.addField(typeId(VendAccount));
    retValue 
= dialog.run();
    
    
if(retValue)
    {
        vendAccount 
= dialogField.value();
        
//do something else
        
    }

}
一切都看似完美,但是有个问题,只能选择一个供应商,用户要求的是选择多个供应商,控件的replaceOnLookup属性是用来控制这个的,但是dialogField没有这个方法,咋办?添上。
在类DialogField上添加方法replaceOnLookup,如下所示:

void replaceOnLookup(boolean r)
{
    str name;

    
// If properties exists then we are on server
    if (properties)
    {
        name 
= #PropertyReplaceonlookup;
        
if (! properties.exists(name))
            properties.add(name,
true);
        properties.value(name,r);
    }
    
else
        
this.fieldControl().replaceOnLookup(r);
}
修改一下unpack方法,加上我们新增的属性
case #PropertyReplaceonlookup:
     
this.replaceOnLookup(unpackedProperties.valueIndex(i));
     
break;
这样就可以调用这个方法来改变控件的属性了。

static void dialogDemo(Args _args)
{
    Dialog              dialog 
= new Dialog();
    DialogField         dialogField;
    VendAccount         vendAccount;
    boolean             retValue;
    ;
    
    dialogField 
= dialog.addField(typeId(VendAccount));
    dialogField.replaceOnLookup(
false);
    retValue 
= dialog.run();
    
    
if(retValue)
    {
        vendAccount 
= dialogField.value();
        
//do something else
        
    }

}
效果如下图所示:

How to using x++ to modify sales line Qty

 Question
 Hi All, I tried to use X++ code to modified sales line qty, qty can be 
changed but how can I change others, like Delivery remainder and on order? 
the code I am using like below: 

SalesLine salesline; 
; 
ttsbegin; 
select forupdate salesline where salesline.salesid=="xxxxx" && 
salesline.linenum ==1; 
salesline.SalesQty = 20; 
SalesLine::modifySalesQty(); 
salesLine.update(); 
ttscommit; 

after the code is run, on over view tab of sales line, I can see the qty is 
changed, but not effect to Deliver remainder which is in Quantity tab. how 
can I correct that? 

thank you.

--------------------------------------------------------------
Hello, 

try and add InventMovement::bufferSetRemainQty(salesLine); before you update 
the salesline. 

SalesLine::modifySalesQty(salesline, salesline.inventDim()); 
InventMovement::bufferSetRemainQty(salesLine); 
salesLine.update(); 


Regards 
--

How to using class remake splitter control on Form

AX2009中有关 splitter 控件的示例应用在 AOT\form\tutorial_Form_Split中能够找到,基本思路如下:
  1:首先在需要被 Split 的两个控件中的第一个控件之后加入一个 Group;
  2:在该 form 中申明一个变量(如 SysFormSplitter_Y _formSplitter),然后在 Init 事件中进行初始化( _formSplitter = new SysFormSplitter_Y(groupSplitter,groupTop,element) );
  3:重载创建 Group控件中的方法:MouseUP、MouseDown 和 MouseMove;
    
    偶尔使用一两个 Splitter 控件这样也可以了。如果经常使用 Splitter 控件,都要创建一个“没用的” Group 控件,并且都要重载并重复 copy 、plaste 相同的 MouseUP、MouseDown 和 MouseMove 代码,实在是太麻烦。

    其实完全可以改造,基本思想如下:
  1:创建一个 Class,重写 new 方法,并传入一个控件,该控件为需要被 Split 的两个控件中的第一个控件;
  2:在 new 方法中,用代码创建一个 Group 控件,该 Group 控件当然要放在参数控件之后, 并设置好相应的属性;
  3:在 new 方法中,用代码为 Group 控件增加事件处理接口;
  4:加入Group控件的 MouseUP、MouseDown 和 MouseMove 处理代码;
  5: 在需要 split 的 form 的 init 事件中调用该 Class 的 new 方法。
class PushCsSplitterY
{
    SysFormSplitter_Y   splitterY;
    FormRun             formRun;
}
     
void new(FormControl    _control)
{
// Add splitter Y between _control and its next control
    FormGroupControl    groupControl;
    ;
    formRun = _control.owner();  //根据传入的控件,得到相应的 formRun
    groupControl = formRun.design().control(_control.containerId()); // 根据传入的控件,得到它的父控件
    if (!groupControl)
        return; // 如果没有父控件直接返回,否则下面的代码要出错
    groupControl = groupControl.addControl(formControltype::Group,'SplitterGroup',_control); // 在传入控件后面的位置新创建一个 Group 控件
    groupControl.autoDeclaration(true);      // 设置新创建的 Group 控件的相应属性
    groupControl.widthMode(FormWidth::ColumnWidth);
    groupControl.height(5);
    groupControl.alignControl(true);
    groupControl.frameType(formFrameType::Raised3D);
    groupControl.backgroundColor(windowspalette::WindowBackground);
    groupControl.hideIfEmpty(false);
    groupControl.alignChild(false);
    formRun.controlMethodOverload(true);            // 表示该窗口中的事件要进行重载
    if (!formRun.controlMethodOverloadObject())   
        formRun.controlMethodOverloadObject(this);  /* 指明窗口中事件的响应代码在什么地方寻找,这里就是这个 Class。注意,寻找具体的响应代码方法名称规则: 控件名称+下划线+事件名称。该 Class 中,自动增加的控件名称为 SplitterGroup,事件 MouseUp 的响应代码的方法名称为:SplitterGroup_mouseUp ,也即需要增加一个名为 SplitterGroup_mouseUp 的方法,否则相应的事件不能响应了。*/
    splitterY = new SysFormSplitter_Y(groupControl,_control,formRun);
}

int SplitterGroup_mouseUp(int x, int y, int button, boolean ctrl, boolean shift)
{
    ;
    formRun.controlCallingMethod().mouseUp(x, y, button, ctrl, shift);   // 先响应调用者本来的方法,这里就是调用用代码创建的 Group 控件的MuseUp方法
    Return splitterY.mouseUp(x, y, button, ctrl, shift);                             // 然后才调用 Splitter 本身的方法
}

int SplitterGroup_mouseDown(int x, int y, int button, boolean ctrl, boolean shift)
{
    ;
    formRun.controlCallingMethod().mouseDown(x, y, button, ctrl, shift);
    Return splitterY.mouseDown(x, y, button, ctrl, shift);
}

int SplitterGroup_mouseMove(int _x, int _y, int _button, boolean _Ctrl, boolean _Shift)
{
    ;
    formRun.controlCallingMethod().mouseMove(_x,_y,_button,_Ctrl,_Shift);
    return splitterY.mouseMove(_x,_y,_button,_Ctrl,_Shift);
}

只需在每一个需要使用 splitter 的 form 的 init 事件中用一句代码调用即可, 一劳永逸,Enjoying ~~~~~~~~ !

public void init()
{
    super();
    new PushCsSplitterY(BOMConfiguration);  //  BOMConfiguration 为一 grid 控件,其后面还有一个 grid 控件
}

How to using displayOption method switch color on Form

首先要在 form 中的 datasource 中重载 displayoption方法。
1:控制行的颜色
public void displayOption(Common _record, FormRowDisplayOption _options)
{
    PushTbBOMConfiguration  config;
    config = _record;
    if (config.Color=='Red')
    {
        _options.textColor(WinAPI::RGB2int(255,0,0));
    }
    super(_record, _options);
}
  效果图:

图片

2:控制列的颜色
public void displayOption(Common _record, FormRowDisplayOption _options)
{
    PushTbBOMConfiguration  config;
    config = _record;
   // PushTbBOMConfiguration_Size02 为表格中的某一个列
    PushTbBOMConfiguration_Size02.colorScheme(FormColorScheme::RGB);       
    PushTbBOMConfiguration_Size02.foregroundColor(WinAPI::RGB2int(255,0,0));       
    _options.affectedElementsByField(fieldnum(PushTbBOMConfiguration,size02));
       
    super(_record, _options);
}
  效果图:


图片
3:控制单元格的颜色
public void displayOption(Common _record, FormRowDisplayOption _options)
{
    PushTbBOMConfiguration  config;
    config = _record;
   // PushTbBOMConfiguration_Size02 为表格中的某一个列
    if (config.Color=='Red')
    {
         _options.affectedElementsByControl(PushTbBOMConfiguration_Size02.id());
         _options.textColor(WinAPI::RGB2int(255,0,0));
    }
    super(_record, _options);
}
  效果图:

图片

How to using Temporary tables in AX

Temporary tables are used for non-persistent storage in Microsoft Axapta.
They are useful in two common situations
  1. As the datasource for a form or report, where the original data is too complex to be easily queried.
  2. As temporary storage during complicated processing, to hold the results midway through the process.

Scoping rules for temporary tables

In general, each instance of a temporary table, and it's associated data, will only exist while the buffer variable used to access it is in scope.
You can point multiple buffer variables to the same instance of a temporary table by using either the .setTmpData() method or by directly assigning the buffers to each other, identically to normal tables. In this way, even if your original buffer variable goes out of scope, your data will be retained while one of the other referencing variables remains.
Be aware that static table methods - such as find() - will not work with temporary tables unless you pass through the buffer variable to the method.
For example, this method will not work on a temporary table, as the tempTable variable used is newly created and will always contain no records.
// This won't work on temporary 

table
public static TempTable find

(AccountNum _accountNum, boolean _forUpdate = false)
{
    TempTable   tempTable;
    ;
 
    if (_accountNum)
    {
        tempTable.selectForUpdate(_forUpdate); 
        select firstonly tempTable
        where tempTable.AccountNum      == _accountNum;
    }
 
    return tempTable;
}
If you want to have a find() method on your temporary table, then you will need to modify it slightly to pass through a reference to our populated temporary table.
// Use this pattern instead
public static TempTable find

(
AccountNum _accountNum, TempTable _tempTable, boolean _forUpdate = false)
{

    if (_accountNum)
    {
        _tempTable.selectForUpdate(_forUpdate);

        select firstonly _tempTable
        where _tempTable.AccountNum      == _accountNum;
    }

    return _tempTable;
}
Some examples of populating and using temporary tables can be found in Image:TRG TempTablesGeneral.xpo project.

Creating temporary tables

In the AOT

Set the Temporary property to Yes to create a table which will always be temporary.
Note that any existing data will be permanently deleted if you do this!
Of course, you can no longer use the Table Browser to check the data, as the data is stored only per scoped instance of this table.

Making an existing table temporary

You can convert a normal table to a temporary table in code. For example, if you wish to create a temporary copy of the inventory table:
InventTable    inventTable;
;

inventTable.setTmp();
Doing so will remove all data from the temporary copy of the table. If you wish to create a populated temporary copy of a standard table, you can do the following:
InventTable    

inventTable;
InventTable    inventTableTmp;
;

inventTableTmp.setTmp();
while select inventTable
{
   inventTableTmp.data(inventTable.data());
   inventTableTmp.doInsert();
}
You can now add, modify or delete data from the table without affecting the real contents stored in the database.

Temporary tables in forms

Using temporary tables in forms requires the use of the .setTmpData() method.
For example:
The temporary table data is populated in a static class method (running server side), which is called from the form and returns the populated table. We could populate a form-level buffer with the temporary data if needed, or else just call the populating method directly from the setTmpData() call as shown below.
In the form datasource init(), we use .setTmpData() to instruct the datasource query to use our temporary table. Our datasource name in this example is TempTable.
public 

void init()
 {
     super();  
     TempTable.setTmpData(tmpTableClass::populateTmpData())
;
 }
See Image:TRG TempTablesForm.xpo for an example of a working form based on a temporary table.
It is also possible to add a table to a form which is not a temporary table, but the data that is shown must be temporary. Compared to the previous example there are not a lot of changes; just make sure the table is made temporary using .setTmp(). As in the previous example the temporary table data is populated in a static class method (running server side), which is called from the form and returns the populated table.
In the following example the InventTable is used.
public void init()
 {
     super(); 
 
     InventTable.setTmp();
     InventTable.setTmpData(InventTableClass::populateTmpData())
;
 }

Temporary tables in reports

The correct method of using temporary tables in reports is slightly different from that of forms.
The most important difference is the use of .setRecord() instead of .setTmpData(). A simple example follows:
public boolean 

fetch()
 {
     boolean ret;
     ;
     this.queryRun().setRecord(tmpTableClass::populateTmpData())

;
 
     ret = super();
 
     return ret;
 }
As there is often already a supporting RunBaseReport class being used to run the report, it is easy to integrate the population of the temporary data into that existing class. This is particularly useful if you need the data in the temporary table to be dependent on information entered into the report dialog prompt by the user.
See Image:TRG TempTablesReports.xpo for an example of using a RunBaseReport class to run a report based on a temporary table.

Temporary table performance

Data being stored in temporary tables is stored in a temporary physical file in the file system. The file itself is created when the first record is being inserted in that particular instance of the temporary table. Hence, in a 3 tier environment, the file will be maintained on server or client side, depending on where the first record is inserted. From a performance standpoint this is a concern when using temporary tables.

Indexes on temporary tables

As with normal tables, indexes can be created on temporary tables. When a temporary copy of a normal table is used with .setTmp(), then the existing indexes will also be created on the temporary version. For new temporary tables (with the Temporary property set to Yes), you must create any desired indexes through the AOT in the normal way.
Indexes have a substantial effect on temporary table performance. For temporary tables with a lot of records you will experience major performance limitations when searching on non-indexed fields.

Security on temporary tables

You can assign a SecurityKey to a temporary table, like any other. The security key will work well, limiting access. However, temporary tables never show up in the tree for assigning permissions, so it's not possible to actually enable them for users. Therefore it's important not to put a security key on any temporary table or users will never be able to use it.

Database transactions (tts) on temporary tables

Temporary tables are not included in Dynamics Ax's normal transaction processing capabilities. If you include population of a temporary table inside a ttsBegin/ttsCommit which then aborts, changes made to the temporary table will not be aborted.
To activate transaction capabilities on temporary tables, use the local ttsBegin and ttsCommit methods on the temporary table buffer themselves. These work as expected.