2015-03-24

QlikView

My blog dedicated to developing in QlikView

2009-02-18

A few words about sorting codes

In AX each item may be assigned by its own sort code. On the other hand each WMS location has its own sort code and there is a functionality for updating sort codes.

Let's analyse how do they work together.

Take a look at WMSAisle table which has following fields:

  • inventLocationId
  • sortCode
  • sortDescending
  • aisleId
  • aisleNo

A sample of WMSAisle table:

By the way combination inventLocationId and aisleId fields is unique (see \Data Dictionary\Tables\WMSAisle\Indexes\AisleIdx).

Every time when we try to update WMSLocation sort codes (e.g. \Menus\Invent\Periodic\Locations\Sort codes) updateSortCodes() method of WMSAisle table is invoking. The sort codes are updating in two steps:

- initialSortCode seeking;

- updating sort codes;

Determining of initialSortCode

If WMSParameters.manualSortCode is true the initialSortCode is assigned by WMSAisle.sortCode. Else initialSortCode will be equal maximal value of WMSLocation.sortCode for all aisles less than current aisleNo.

To understand how does it work suppose there is an inventLocation 00001 with several aisles (WMSAisle table)

inventLocationId        aisleId         aisleNo
    00001                   1               1
    00001                   2               2
    00001                   3               3
    00001                   4               4
    00001                   5               5
    00001                   6               6
    00001                   7               7
    00001                   8               8
    00001                   9               9

WMSLocation is filtered by inventLocationId = '00001'

inventLocationId        wmsLocationId           sortCode        aisleId
    00001                   1-07-2-1                11              1
    00001                   1-07-2-2                12              1
    00001                   1-07-2-3                13              1
………
    00001                   1-07-2-20               30              1

    00001                   1-07-2-21               31              2
    00001                   1-07-2-22               32              2
………
    00001                   1-07-2-32               42              2

    00001                   1-07-2-33               43              3
    00001                   1-07-2-34               44              3
………
    00001                   1-07-2-39               49              3

………

    00001                   1-07-2-91               101             9
    00001                   1-07-2-92               102             9
………
    00001                   1-07-2-92               112             9
 

We are trying to determine initialSortCode for aisleId = 3. Aisles less 3 are 1 and 2. For these aisles (1 and 2) maximal sortCode is 42. Therefore in our example initialSortCode is 42.

Updating sort codes

Updating sort codes is pretty easy and works according following algorithm:

sortCode = initialSortCode;
WMSLocation.sortCode = sortCode;
sortCode++;

i.e. each record updating aisle WMSLocation.sortCode is incrimenting by 1.

If WMSAisle.sortDescending is true all the records before updating are arranged by descended rack, ascended level and ascended position.

If WMSAisle.sortDescending is false records are arranged by ascended rack, ascended level and ascended position.

It is useful to mention that records with WMSLocation.manualSortCode = true are excluded from initialSortCode seeking and sort codes updating. And now take a look at WMSOrderTrans table (reservation). At Tables\WMSPickingRoute\Methods\makePickingLine WMSPickingRoute.makePickingLine method we may watch on followng lines:

    select firstonly forupdate WMSOrderTransCopy
        index hint RouteIdx
        where WMSOrderTransCopy.routeId         == WMSOrderTrans.routeId    &&
              WMSOrderTransCopy.fullPallet      == NoYes::No                &&
              WMSOrderTransCopy.recId           == WMSOrderTrans.recId;

    WMSOrderTransCopy.routeId                   = this.pickingRouteID;
    WMSOrderTransCopy.sortCode                  = (WMSLocation) ? WMSLocation.sortCode :

WMSOrderTrans.WMSLocation().sortCode;
    WMSOrderTransCopy.itemSortCode              = inventTable.sortCode;
    WMSOrderTransCopy.volume                    = WMSOrderTrans.qty * inventTable.grossVolume();
    WMSOrderTransCopy.expectedExpeditionTime    = expectedPickTime;
    WMSOrderTransCopy.fullPallet                = NoYes::No;

Here WMSLocation.sortCode and inventTable.sortCode are stored. And the result of sorting codes we may watch in WMSPickForm form where records are arranged by itemSortCode and sortCode.

    queryBuildDataSource.addSortField(fieldNum(WMSOrderTrans, routeId));
    queryBuildDataSource.addSortField(fieldNum(WMSOrderTrans, itemSortCode));
    queryBuildDataSource.addSortField(fieldNum(WMSOrderTrans, sortCode));
    queryBuildDataSource.addSortField(fieldNum(WMSOrderTrans, itemId));
    queryBuildDataSource.addSortField(fieldNum(WMSOrderTrans, dataAreaId));

\Forms\WMSPickForm\Data Sources\WMSOrderTrans\Methods\init()

Therefore itemSortCode is more prevalent than Location sortCode as described in Logistic documentation.

Copyright © 2009 Ruslan Goncharov

2008-08-27

Storing report design in database

Today I'd like to say a few words about packDesign()/unpackDesign() methods.

As a matter of fact we may store report design just in database. All we need is new container field in table where report design is storing.

Following job demonstrates storing technique.

When restore is false - we store the design. Else design restoring.

P.S. FieldReportDesign field should be Container.

static void JobReportDesign(Args _args)
{     Report              report = new Report();     ReportRun           reportRun;     ReportDesign        reportDesign;     ReportSection       reportSection;     ReportTextControl   reportTextControl;     Table1              Table1; //    boolean             restore = true;     boolean             restore = false;     BinData             binData;     void runReport(ReportRun _reportRun)     {         ReportDesign    _reportDesign = _reportRun.report().design();         int sCount    = _reportDesign.sectionCount();         int i;         ;         for(i=1; i<=sCount; i++)         {             _reportDesign.section(i).executeSection();         }     }     ;     reportDesign = report.addDesign();     reportRun = new ReportRun(report);     // ReStore the report -->     if(restore)     {         select Table1             where Table1.ItemId == '000000000001';         reportRun.unpackDesign(Table1.FieldReportDesign);         runReport(reportRun);         report.interactive(false);         reportRun.run();         return;     }     // ReStore the report <--     reportSection = reportDesign.addProgrammableSection(1);     reportTextControl = reportSection.addTextControl('Small');     reportTextControl.width100mmInclBorder(20120);     reportTextControl.fontSize(15);     reportTextControl.bold(10);     reportTextControl.alignment(3);     reportSection.executeSection();     reportSection = reportDesign.addProgrammableSection(2);     reportTextControl = reportSection.addTextControl('report');     reportTextControl.width100mmInclBorder(20120);     reportTextControl.fontSize(15);     reportTextControl.bold(10);     reportTextControl.alignment(3);     reportSection.executeSection();     report.interactive(false);     // Store the report -->     if(!restore)     {         ttsbegin;         select forupdate Table1             where Table1.ItemId == '000000000001';         Table1.FieldReportDesign = reportRun.packDesign();         Table1.update();         ttscommit;     }     // Store the report <--     reportRun.run(); }
Copyright © 2008 Ruslan Goncharov

2008-07-31

SysListSelect

In AX there is an easy tool for creating simple lists with following selection needed items called SysListSelect. In this form we may check/uncheck every item separately or whole list by clicking at Yes to All or No to All buttons.

Some information about SysListSelect we may find in MSDN SysListSelect class, but unfortunately description is very compact and it's hard even for experienced developer to understand how to fully use this tool. Following article is meant to fill this gap in knowledge.

To call SysListSelect form we don't need write any code. In Global class there is corresponding wrapper selectMultiple() method:

/*
Returns container with the status of how the form is closed plus the selected ids.
*/
static client container selectMultiple(
    Caption     caption,
    str         info,                   // An info text displayed in the top of the form
    container   choices,
    container   headers = conNull()     // If null, the list view is used
    )
The first and second parameters are evident. There are form's caption and info respectively. At first glance third parameter is obvious too: choices container contains items we need to select. But be aware that each item consists from three fields:

label - (string) in fact this is an item we are selecting
id - (integer) item's identifier
set - (boolean) determines default state of item (checked\unchecked)

before passing choices into selectMultiple() we need to pack every item into container using sysListSelect::packChoice()

static container packChoice(
    str                 label,
    int                 id,
    boolean             set
    )
{
    return [label,id,set];
}
Here we create our first row.
con+=[sysListSelect::packChoice(label, id, set)];
The fourth parameter headers is used to show headers. That means that we may visualise more than one column. But how to pass these columns?
The answer is simple. If we need to pass some columns we just need to "divide" each label at columns by '\n' symbol:

label = column1+'\n'+column2+'\n'+...+columnN

Thats all. Now we are ready to launch SysListSelect form.

static void sysListSelectSampleJob(Args _args)
{
    container con;
    container ret;
    boolean             ok;

    str                 label;
    int                 id;
    boolean             set;
    ;

    label   = 'label_11'+'\n'+'label_12';
    id      = 1;
    set     = false;
    con+=[sysListSelect::packChoice(label, id, set)];

    label   = 'label_21'+'\n'+'label_22';
    id      = 2;
    set     = true;
    con+=[sysListSelect::packChoice(label, id, set)];

    [ok, ret] = selectMultiple('Caption','Info', con, ['First column', 'Second column']);
    conView(ret);
}


I absolutely forgot to say that selectMultiple() returns container which first parameter is boolean (in our example this is ok) pointing whether was pressed OK button or not and container of selected id's.


Here we selected 2 elements.

And here we try to work with AX breakpoints (something similar with native SysBreakpoints form)

static void sysListSelectBreakpointsJob(Args _args)
{
    container con;
    container ret;
    boolean             ok;

    str                 label;
    int                 id;
    boolean             set;

    container           breakpoints;
    int                 i, bpLen;
    ;

    breakpoints = infolog.breakpoint();
    bpLen = conLen(breakpoints);
    for(i=2; i<=bpLen; i++)
    {
        switch ((i mod 3))
        {
            case 2:
                label   = conpeek(breakpoints, i);
            break;

            case 0:
                id      = conpeek(breakpoints, i);
            break;

            case 1:
                set     = conpeek(breakpoints, i);
            break;
        }
        con+=[sysListSelect::packChoice(label, id, set)];
    }
    [ok, ret] = selectMultiple('Caption','Breakpoints', con, ['Path']);
}



I'd like to point at fact that using SysListSelect is very rare. I've found in AX it in one place only: \Classes\SysDataImport\deleteTablesCheck (AX 3.0) so this tool is still waiting for developers :-)

I've discovered too that there is a small bug in \Classes\SysListSelect\selected() method.
When we launch the form from a wrapper and try to close form (not click at OK or Cancel) AX falls at endless loop(AX 3.0). The matter of fact in this case listView is destroyed already and impossible to get listView items correctly.



To avoid this I dropped a line into selected() method:
container selected()
{
    Counter         i;
    FormListItem    item;
    container       selected;
    ;

    
  if(listView.getCount())// added by GRR
  {
    for (i=listView.getNextItem(FormListNext::ALL); i>=0; i=listView.getNextItem(FormListNext::ALL,i))
    {
        item = listView.getItem(i);
        if (item && item.image() == this.imageOn())
            selected += item.data();
    }
  }
    return selected;
}
Copyright © 2008 Ruslan Goncharov

2008-05-30

Stereogram in AX

Following job demonstrates random dot stereogram technique in AX.

static void RDStechniqueJob(Args _args)
{
        #define.image("C:\\Images\\pattern.bmp")

        #define.imageSizeX(300) // width*2
        #define.imageSizeY(300) // height

        #define.depth(5)

        Form            form;
        FormRun         formRun;
        FormDesign      formDesign;

        Args            args;
        FormBuildDesign formBuildDesign;

        FormBuildWindowControl formBuildWindowControl;
        FormWindowControl pane;

        Image image     = new Image();
        Image pattern   = new Image();
        int     _x, _y;


        void Draw()
        {
            Random  rnd = new Random();
            int     color;
            ;

            for(_x=0; _x<#imageSizeX/2; _x++)
            {
                for(_y=0; _y<#imageSizeY; _y++)
                {
                    color = rnd.nextInt()*0xFF;
                    image.setPixel(_x, _y, color);
                    image.setPixel(_x+#imageSizeX/2, _y, color);
                }
            }
        }

        void CreateStereogram()
        {
            int     color;
            int     d;
            ;

            pattern.loadImage(#image);

            for(_x=0; _x<#imageSizeX/2; _x++)
            {
                for(_y=0; _y<#imageSizeY; _y++)
                {
                    if(!(pattern.getPixel(_x, _y) mod 2))
                    {
                        color = image.getPixel(_x, _y);
                        image.setPixel(_x+#imageSizeX/2-#depth, _y, color);
                    }
                }
            }
        }
        ;

        form = new Form();
        formBuildDesign = form.addDesign('design');
        formBuildDesign.hideToolbar(true);

        formBuildDesign.columns(1);
        formBuildDesign.topMode(1); // Auto
        formBuildDesign.leftMode(1); // Auto
        formBuildDesign.widthMode(1); // Auto
        formBuildDesign.heightMode(1); // Auto
        formBuildDesign.width(1.1*#imageSizeX);

        formBuildWindowControl = formBuildDesign.addControl(FormControlType::IMAGE, 'pane');
        formBuildWindowControl.height(#imageSizeY);
        formBuildWindowControl.width(#imageSizeX);
        formBuildWindowControl.backgroundColor(0);

        args = new Args();
        args.object(form);
        formRun = classFactory.formRunClass(args);
        formRun.init();
        formRun.resetSize();
        formDesign = formRun.design();
        formRun.resetSize();
        formrun.formOnTop();
        formRun.run();


        formRun.design().caption('Random-dot stereogram');
        pane = formRun.control(formBuildWindowControl.id());

        image.saveType(ImageSaveType::BMP_UNCOMP);
        image.createImage(#imageSizeX, #imageSizeY, 24);

        Draw();

        CreateStereogram();
        pane.image(image);

        formRun.wait();
}

Notes:
in this job
- pattern.bmp should be monochrome.
- imageSizeX should be twice more than width of bitmap file.
- depth -- z-axis (depth).

Copyright © 2008 Ruslan Goncharov

2008-05-23

From Microsoft Dynamics AX Programming newsgroup tricks

TreeNode Alternative for FormControls

I want to create a Control on a Form just like I would with a TreeNode in AX 3.0. But the TreeNode.AOTAdd doesn't work for FOrmControls. Is there an Alternative how I can create a Control? These Fields should notbe temporary like in the Tutorial_Form_AddControl. They should be in the AOT after being created.

    TreeNode                formlist;
    Form                    formNode;

    FormBuildDesign         formDesign;

    ;

    formlist = infolog.findNode('Forms');
    formNode = formlist.AOTfindChild('Form1');

    formDesign = formNode.design();
    formDesign.addControl(FormControlType::String, 'StringEdit');
    formNode.save();
 

Saved form settings

When we import new objects, the user defined form settings are lost. It's possible to prevent that from happening?

static void JobSettingsStoringAndRestoring(Args _args)
{
    syslastvalue    sysLastValue;
    container       dataContainer;
    BinData         binData;

    container       blobContainer;
    ContainerClass  containerClass;
    str             settingsFileName = "c:\\settings.txt";
    ;

//  Store settings -->
    dataContainer = xSysLastValue::getValue(
        curExt(), curUserId(), UtilElementType::Usersetup, 'AddressCheck', 'myDesign');

    containerClass = new ContainerClass(dataContainer);
    blobContainer = containerClass.toBlob();

    binData = new BinData();
    binData.setData(blobContainer);

    binData.saveFile(settingsFileName);
//  Store settings <--

//  Restore settings -->
/*    binData = new BinData();
    binData.loadFile(settingsFileName);

    dataContainer = containerClass::blob2Container(binData.getData());
    xSysLastValue::putValue(dataContainer,
        curExt(), curUserId(), UtilElementType::Usersetup, 'AddressCheck', 'myDesign');*/
//  Restore settings <--
}
Copyright © 2008 Ruslan Goncharov

2008-04-18

Dynamic enabled() property for StringEdit and arrows

Sometimes we need to enable or disable some field depending on value from another field.

The first way is using Group object where FrameOptionButton property is set to Check. For details see \Forms\tutorial_Form_groupOption

The second approach is setting enabled() property for StringEdit object.

void enableControls()
{
    StringEdit_1.enabled(myTable.Field);
}

Unfortunately we may notice that the arrow object of StringEdit doesn't work properly. Sometimes the arrow is vanish away from control.

The small modification should improve the arrow's behaviour.

In fact the arrow is the child window of StringEdit. The idea consists in managing state of parent and child window trough winApi.

All what we need to do is:

1. Add new enableWindow() method in WinApi class:

// Created by GRR
client static boolean enableWindow(int _handle, boolean _value)
{
    DLL         _DLL             = new DLL('USER32');
    DLLFunction _enableWindow    = new DLLFunction(_DLL, 'EnableWindow');

    _enableWindow.returns(ExtTypes::DWord);
    _enableWindow.arg(ExtTypes::DWord);
    _enableWindow.arg(ExtTypes::DWord);

    return _enableWindow.call(_handle, _value);
}

2. Rewrite our enableControls() method:

void enableControls()
{
    void controlEnabled(FormControl control, boolean _value)
    {
        #WinApi

        int ctrlHwnd    = control.hWnd();
        int childHwnd   = WinApi::getWindow(ctrlHwnd, #GW_CHILD);
        ;

        WinApi::showWindow(ctrlHwnd, #SW_SHOW);
        WinApi::showWindow(childHwnd, #SW_SHOW);

        WinApi::enableWindow(ctrlHwnd,  _value);
        WinApi::enableWindow(childHwnd, _value);
    }
    ;

    controlEnabled(StringEdit_1,   myTable.Field);
    controlEnabled(StringEdit_2,   myTable.Field);
}

AX 3.0 SP4

Copyright © 2008 Ruslan Goncharov

2008-03-09

Scrolling in AX 3.0 without dll

As is well known in AX 3.0 it's unable to scroll objects (first of all Groups etc.) in the form. This feature is present since AX4 and supported, as far as I know by axScroll.dll.

Honestly, I'm not a big worshipper of using side components. On the other hand I've been thinking about how to implement scrolling feature in AX3.

About one year ago there was published very interesting project Capturando el WindowProc desde Axapta by Manekaze

In this project the author showed a possibility to trap keyboard events using API in AX. So why don't capture the scrollbar events.

What we have to do:
1. Apply scrollable style for the window which we are going to scroll.
2. Trap scroll events in AX.
3. Scroll the window according scrollbar positions.

Let's go.

- Apply scrollable style for the window which we are going to scroll

Setting scrollable style for the window is pretty easy:

WinApi::showScrollbar(handle, #SB_BOTH, true);

By the way we need to set some parameters for vertical and horizontal scrollbars

//                            pos pSize  min max
    WinApi::setScrollInfo(handle, 0, 20,    0, 100, true, #SB_HORZ);
    WinApi::setScrollInfo(handle, 0, 20,    0, 100, true, #SB_VERT); 
and get back these parameters
   cScrollInfoH = WinApi::getScrollInfo(handle, #SB_HORZ);
    xOffset      = conpeek(cScrollInfoH, #pos);
    xScroll      = conpeek(cScrollInfoH, #maxValue);
    xSize        = conpeek(cScrollInfoH, #pageSize);
   cScrollInfoV = WinApi::getScrollInfo(handle, #SB_VERT);
    yOffset      = conpeek(cScrollInfoV, #pos);
    yScroll      = conpeek(cScrollInfoV, #maxValue);
    ySize        = conpeek(cScrollInfoV, #pageSize);
 
In my case page size of each scrollbar is 20, minimum scrolling position is 0 and maximum scrolling position is 100.

- Scroll the window according scrollbar positions

Here I don't like to reinvent something new and I used standard library, which I've found here with small modifications As a matter of fact we need only two methods:
int Scroll_SetVert(ScrollInfo_t* pScroll, HWND hWnd, WORD wScrollCmd, WORD wScrollPos)
int Scroll_SetHorz(ScrollInfo_t* pScroll, HWND hWnd, WORD wScrollCmd, WORD wScrollPos)

int Scroll_SetVert(ScrollInfo_t* pScroll, HWND hWnd, WORD wScrollCmd, WORD wScrollPos)
        {
        int               nScrollAmt ;

        switch (wScrollCmd)
                {
                case SB_TOP:
                        nScrollAmt = -pScroll->yOffset ;
                        break ;

                case SB_BOTTOM:
                        nScrollAmt = pScroll->yScroll - pScroll->yOffset ;
                        break ;

                case SB_PAGEUP:
                        nScrollAmt = -pScroll->ySize ;
                        break ;

                case SB_PAGEDOWN:
                        nScrollAmt = pScroll->ySize ;
                        break ;

                case SB_LINEUP:
                        nScrollAmt = -pScroll->yChar ;
                        break ;

                case SB_LINEDOWN:
                        nScrollAmt = pScroll->yChar ;
                        break ;

                case SB_THUMBPOSITION:
                        nScrollAmt = wScrollPos - pScroll->yOffset ;
                        break ;

                default:
                        return ( FALSE ) ;
                }
        if ((pScroll->yOffset + nScrollAmt) > pScroll->yScroll)
                nScrollAmt = pScroll->yScroll - pScroll->yOffset ;
        if ((pScroll->yOffset + nScrollAmt) < 0)
                nScrollAmt = -pScroll->yOffset ;
        ScrollWindow( hWnd, 0, -nScrollAmt, 0, 0 ) ;
        pScroll->yOffset = pScroll->yOffset + nScrollAmt ;
        SetScrollPos( hWnd, SB_VERT, pScroll->yOffset, TRUE ) ;

        return ( TRUE ) ;

        } // end of Scroll_SetVert()

int Scroll_SetHorz(ScrollInfo_t* pScroll, HWND hWnd, WORD wScrollCmd, WORD wScrollPos)
        {
        int               nScrollAmt ;

        switch (wScrollCmd)
                {
                case SB_TOP:
                        nScrollAmt = -pScroll->xOffset ;
                        break ;

                case SB_BOTTOM:
                        nScrollAmt = pScroll->xScroll - pScroll->xOffset ;
                        break ;

                case SB_PAGEUP:
                        nScrollAmt = -pScroll->xSize ;
                        break ;

                case SB_PAGEDOWN:
                        nScrollAmt = pScroll->xSize ;
                        break ;

                case SB_LINEUP:
                        nScrollAmt = -pScroll->xChar ;
                        break ;

                case SB_LINEDOWN:
                        nScrollAmt = pScroll->xChar ;
                        break ;

                case SB_THUMBPOSITION:
                        nScrollAmt = wScrollPos - pScroll->xOffset ;
                        break ;

                default:
                        return ( FALSE ) ;
                }
        if ((pScroll->xOffset + nScrollAmt) > pScroll->xScroll)
                nScrollAmt = pScroll->xScroll - pScroll->xOffset ;
        if ((pScroll->xOffset + nScrollAmt) < 0)
                nScrollAmt = -pScroll->xOffset ;
        ScrollWindow( hWnd, -nScrollAmt, 0, 0, 0 ) ;
        pScroll->xOffset = pScroll->xOffset + nScrollAmt ;
        SetScrollPos( hWnd, SB_HORZ, pScroll->xOffset, TRUE ) ;

        return ( TRUE ) ;
        } // end of Scroll_SetHorz()
 
In my WinApi class I haven't found scrollWindow() method. And I've added it into WinApi class
// Created by GRR
client static int scrollWindow(int     hwnd,
                               int     XAmount,
                               int     YAmount)
{
    DLL         _winApiDLL     = new DLL('USER32');
    DLLFunction _scrollWindow  = new DLLFunction(_winApiDLL, 'ScrollWindow');

    _scrollWindow.returns(ExtTypes::DWord);
    _scrollWindow.arg(ExtTypes::DWord,
                       ExtTypes::DWord,
                       ExtTypes::DWord,
                       ExtTypes::DWord,
                       ExtTypes::DWord);

    return _scrollWindow.call(hwnd, XAmount, YAmount, 0,0);
}
 
Now we are ready for third step

- Trapping scroll events I've designed it as a new class xApplyScrolling // Begin of xApplyScrolling class -->

#define.minValue(1)
#define.maxValue(2)
#define.pageSize(3)
#define.pos(4)

#define.xChar(5)
#define.yChar(5)

class xApplyScrolling
{
    #WinApi

    TrucosAx_TrapperWndProc     WndProcHScroll, WndProcVScroll;
    HWND                        handle;

    container    cScrollInfoH, cScrollInfoV;
    int          xOffset, yOffset;
    int          xScroll, yScroll;
    int          xSize,   ySize;
}

//In new() method we are installing hooks for WM_HSCROLL and WM_VSCROLL separately
void new(FormRun _element, FormControl _formControl)//HWND _HWnd
{
    handle = _formControl.hWnd();

    WndProcHScroll = new TrucosAx_TrapperwndProc(_element, handle, true);
    WndProcHScroll.InstallHook(#WM_HSCROLL, 'scrlMessageH');
    WndProcHScroll.setObject(this);

    WndProcVScroll = new TrucosAx_TrapperwndProc(_element, handle, true);
    WndProcVScroll.InstallHook(#WM_VSCROLL, 'scrlMessageV');
    WndProcVScroll.setObject(this);

    this.setScrollable();
}

//Here we set scrollable style for the window
protected void setScrollable()
{
    ;
    WinApi::showScrollbar(handle, #SB_BOTH, true);

//                 pos pSize  min max
    WinApi::setScrollInfo(handle, 0, 20,    0, 100, true, #SB_HORZ);
    WinApi::setScrollInfo(handle, 0, 20,    0, 100, true, #SB_VERT);
cScrollInfoH = WinApi::getScrollInfo(handle, #SB_HORZ); xOffset = conpeek(cScrollInfoH, #pos); xScroll = conpeek(cScrollInfoH, #maxValue); xSize = conpeek(cScrollInfoH, #pageSize);
cScrollInfoV = WinApi::getScrollInfo(handle, #SB_VERT); yOffset = conpeek(cScrollInfoV, #pos); yScroll = conpeek(cScrollInfoV, #maxValue); ySize = conpeek(cScrollInfoV, #pageSize); }
//Here we scroll the window protected str translateHCode(int _nScrollCode) { int nScrollAmt ; str strCode; ; switch (_nScrollCode) { case #SB_BOTTOM: // Scrolls to the lower right. //nScrollAmt = pScroll->xScroll - pScroll->xOffset; nScrollAmt = xScroll - xOffset; strCode = 'SB_BOTTOM'; break; case #SB_ENDSCROLL: // Ends scroll. strCode = 'SB_ENDSCROLL'; break; case #SB_LINELEFT: // Scrolls left by one unit. nScrollAmt = -#xChar; strCode = 'SB_LINELEFT'; break; case #SB_LINERIGHT: // Scrolls right by one unit. nScrollAmt = #xChar; strCode = 'SB_LINERIGHT'; break; case #SB_PAGELEFT: // Scrolls left by the width of the window. //nScrollAmt = -pScroll->xSize; nScrollAmt = -xSize; strCode = 'SB_PAGELEFT'; break; case #SB_PAGERIGHT: // Scrolls right by the width of the window. //nScrollAmt = pScroll->xSize; nScrollAmt = xSize; strCode = 'SB_PAGERIGHT'; break; case #SB_THUMBPOSITION: // Scrolls to the absolute position. The current position is specified by the nPos parameter. //nScrollAmt = wScrollPos - pScroll->xOffset; nScrollAmt = -xSize; strCode = 'SB_THUMBPOSITION'; break; case #SB_THUMBTRACK: // Drags scroll box to the specified position. The current position is specified by the nPos parameter. strCode = 'SB_THUMBTRACK'; break; case #SB_TOP: // Scrolls to the upper left. //nScrollAmt = -pScroll->xOffset; nScrollAmt = -xOffset; strCode = 'SB_TOP'; break; default: //return int2HEX(_nScrollCode); nScrollAmt = _nScrollCode/0x10000 - xOffset; strCode = int2HEX(_nScrollCode); } if ((xOffset + nScrollAmt) > xScroll) nScrollAmt = xScroll - xOffset ; if ((xOffset + nScrollAmt) < 0) nScrollAmt = -xOffset ; // ScrollWindow( hWnd, -nScrollAmt, 0,) ; WinApi::scrollWindow(handle, -nScrollAmt, 0); xOffset+=nScrollAmt ; // SetScrollPos( hWnd, SB_HORZ, pScroll->xOffset, TRUE ) ; WinApi::setScrollPos(handle, #SB_HORZ, xOffset, true); WinApi::invalidateRect(handle); return strCode; } protected str translateVCode(int _nScrollCode) { int nScrollAmt ; str strCode; ; switch (_nScrollCode) { case #SB_BOTTOM: // Scrolls to the lower right. nScrollAmt = yScroll - yOffset;//nScrollAmt = pScroll->yScroll - pScroll->yOffset ; strCode = 'SB_BOTTOM'; break; case #SB_ENDSCROLL: // Ends scroll. return 'SB_ENDSCROLL'; break; case #SB_LINEDOWN: // Scrolls one line down. nScrollAmt = #yChar;//nScrollAmt = pScroll->yChar; strCode = 'SB_LINEDOWN'; break; case #SB_LINEUP: // Scrolls one line up. nScrollAmt = -#yChar;//nScrollAmt = -pScroll->yChar; strCode = 'SB_LINEUP'; break; case #SB_PAGEDOWN: // Scrolls one page down. nScrollAmt = ySize;//nScrollAmt = pScroll->ySize; strCode = 'SB_PAGEDOWN'; break; case #SB_PAGEUP: // Scrolls one page up. nScrollAmt = -ySize;//nScrollAmt = -pScroll->ySize; strCode = 'SB_PAGEUP'; break; case #SB_THUMBPOSITION: // Scrolls to the absolute position. The current position is specified by the nPos parameter. //nScrollAmt = wScrollPos - pScroll->yOffset; nScrollAmt = -ySize; strCode = 'SB_THUMBPOSITION'; break; case #SB_THUMBTRACK: // Drags scroll box to the specified position. The current position is specified by the nPos parameter. return 'SB_THUMBTRACK'; break; case #SB_TOP: // Scrolls to the upper left. nScrollAmt = -yOffset;// nScrollAmt = -pScroll->yOffset; strCode = 'SB_TOP'; break; default: nScrollAmt = _nScrollCode/0x10000 - yOffset; strCode = int2HEX(_nScrollCode); } if ((yOffset + nScrollAmt) > yScroll) nScrollAmt = yScroll - yOffset; if ((yOffset + nScrollAmt) < 0) nScrollAmt = -yOffset; WinApi::scrollWindow(handle, 0, -nScrollAmt); // pScroll->yOffset = pScroll->yOffset + nScrollAmt ; // SetScrollPos( hWnd, SB_VERT, pScroll->yOffset, TRUE ) ; yOffset+=nScrollAmt; WinApi::setScrollPos(handle, #SB_VERT, yOffset, true); WinApi::invalidateRect(handle); return strCode; } // Here we process scrolling messages protected void scrlMessageH() { map MMsgH; ; MMsgH = WndProcHScroll.GetMsg(); this.translateHCode(MMsgH.lookup('WPARAM')); } protected void scrlMessageV() { map MMsgV; ; MMsgV = WndProcVScroll.GetMsg(); this.translateVCode(MMsgV.lookup('WPARAM')); } // Here we finalize our class void finalize() { WndProcHScroll.RemoveMainProc(); WndProcVScroll.RemoveMainProc(); }
// <-- End of xApplyScrolling class

By the way there was modificated a bit TrucosAx_TrapperWndProc class.
In method processMessages() in our case we need to work with class object, not with FormRun object. Here are modifications:

class TrucosAx_TrapperWndProc extends TrucosAx_TrapperBase
{
 #define.CodeSize(159) // Total bytes de codigo
  #define.InternalDataSize(16)
  #define.MaxDataelements(100) // Si se cambia se debe cambiar el codigo asm
  #Define.DataSize(16) // +16 bytes de buffer
  #Define.offsetcounter(#CodeSize + 4)
  #Define.offsetlastmsg(#CodeSize + 8)
  #Define.offsetCallwndproc(#CodeSize + 12)

  map hooks;
  #define.maxHooks(20) //si se cambia se debe cambiar el codigo asm
  Binary HooksBuffer;

    Object  object;//Created by GRR
}

//Created by GRR
void setObject(Object _object)
{
    ;
    object = _object;
}

protected void processMessages()
{ map Mmsg;
  int msgId;


  void Ejecuta( str methodname )
  {
    str func = @"void FireHook(object obj)
    {
     ;
      obj.%1();
    }";
    func = strfmt(func,methodname);
    //RunBuf(func, xelement); //Commented by GRR

    // Created by GRR -->
    if (object)
        RunBuf(func, object);
    else
        RunBuf(func, xelement);
    // Created by GRR <--
  }
;

 do
 {
  MMsg = this.GetMsg();
  if (! MMsg) continue;
  MsgId = MMsg.lookup('MSG');
  if (hooks.exists(MsgId))
  {
    Ejecuta(hooks.lookup(MsgId));

   // Si se queda el mensaje debe devolver algo en 'RESULT' en el MMSG
    if ( ! MMsg.exists('RESULT'))
     this.CallbackOriginalProc(MMsg);
  }
 } while (MMsg && this.nextmessage());
}
That's all. To work with scrollable objects we need drop a line in run() method:
public void run()
{
    super();

    applyScrolling = new xApplyScrolling(element, element.control(control::HRMPersonal));
}

// don't forget to finalize ApplyScrolling class
public void close()
{
    applyScrolling.finalize();

    super();
}

// and declare applyScrolling variable in ClassDeclaration 
xApplyScrolling applyScrolling;
Following picture demonstrates scrolling TabPage:HRMPersonal EmplTable form
P.S. This project is raw enough. For example it doesn't support form resizing.
Another words... to be continued.
Copyright © 2008 Ruslan Goncharov

2008-02-24

Posting InventJournal from X++

Once I was faced the problem to post InventJournal from code.
At first glance it's easy. Let's open AOT. Then we find corresponding Menu Item in \Menu Items\Action\InventJournalPost. Open class InventJournalCheckPost... And looking at main() method of this class we are coming to conclusion that this class is tightly tied to the journalForm. What's a pity!

So let's try to write own code.

Look at main() method:

static void main(Args args)
{
        InventJournalCheckPost journalCheckPost;
        journalForm journalForm;

        ;

        journalForm = journalForm::fromArgs(args);
        journalCheckPost = InventJournalCheckPost::newFromForm(args,journalForm);

        journalForm.runbaseMainStart();

        if (!journalCheckPost.PROMPT())
        {                 if (! journalCheckPost.BatchInfo().parmBatchExecute())                         journalForm.runbaseMainCancel();                         return;         }         try         {                 journalCheckPost.run();                 journalForm.runbaseMainEnd(journalCheckPost,false);
        }         catch (Exception::Error)         {                 journalForm.runbaseMainEnd(journalCheckPost,true);         } }
First of all we need to get rid of journalForm.
Let's rewrite newFromForm() method. The parameters wich passing into this method are used to achieve InventJournalTable. So we may just pass InventJournalTable into our new method.
JournalCheckPost getJournalCheckPost(InventJournalTable _inventJournalTable)
{
        // \Menu Items\Action\InventJournalPost
        switch(inventJournalTable.journalType)
        {
                case InventJournalType::Movement:
                case InventJournalType::LossProfit:
                case InventJournalType::Transfer:
                case InventJournalType::BOM:
                case InventJournalType::Count:
                case InventJournalType::project:
                case InventJournalType::Asset:
                        journalCheckPost_Mov = InventJournalCheckPost_Movement::newJournalCheckPost(
                        false,true, JournalCheckPostType::Post, _inventJournalTable.tableId,                    _inventJournalTable.journalId);
                        // journalTransData = _journalForm.JournalTransData();
                        // if (journalTransData)
                        // journalCheckPost_Mov.parmVoucher(journalTransData.journalTrans().voucher);
                return journalCheckPost_Mov;

                case InventJournalType::TagCounting:
                        journalCheckPost = InventJournalCheckPost_Tag::newJournalCheckPost(
                        false, true, JournalCheckPostType::Post, inventJournalTable.tableId,                    _inventJournalTable.journalId);
                return journalCheckPost_Tag;
        }
}
 
Look at JournalFormTable class. It extends journalForm class.

Inside of runbaseMainStart() method we may find following line
journalTableData.updateBlock(JournalBlockLevel::None,JournalBlockLevel::System,false);

inside of runbaseMainCancel() method:
journalTableData.updateBlock(JournalBlockLevel::System,JournalBlockLevel::None,false);

and inside of runbaseMainEnd() method:
journalTableData.updateBlock(JournalBlockLevel::System,JournalBlockLevel::None,false);

That's enough. Now we may rewrite main() method:

// Posting start. According to \Classes\InventJournalCheckPost
// journalForm.runbaseMainStart();
        journalTableData::updateBlockServer(
        inventJournalTable, JournalBlockLevel::None, JournalBlockLevel::System, false);

        journalCheckPost = getJournalCheckPost(inventJournalTable);
        if (!journalCheckPost.PROMPT())
        {
                if (! journalCheckPost.BatchInfo().parmBatchExecute())
                {
                        // journalForm.runbaseMainCancel();
                        journalTableData::updateBlockServer(
                        inventJournalTable, JournalBlockLevel::System, JournalBlockLevel::None, false);
                }
                return;
        }

        try
        {
                journalCheckPost.run();
                // journalForm.runbaseMainEnd(journalCheckPost,false);
                journalTableData::updateBlockServer(
                        inventJournalTable, JournalBlockLevel::System, JournalBlockLevel::None, false);
        }
        catch (Exception::Error)
        {
                // journalForm.runbaseMainEnd(journalCheckPost,true);
                journalTableData::updateBlockServer(
                inventJournalTable, JournalBlockLevel::System, JournalBlockLevel::None, true);
        }
// Posting end
Finally, we are ready to write our remarkable job:
static void JobInventJournalPost(Args _args)
{
        InventJournalCheckPost_Movement journalCheckPost_Mov;
        InventJournalCheckPost_Tag journalCheckPost_Tag;

        InventJournalCheckPost journalCheckPost;

        InventJournalTable inventJournalTable;
        InventJournalId inventJournalId = 'Inv002372';

        JournalCheckPost getJournalCheckPost(InventJournalTable _inventJournalTable)
        {
                // \Menu Items\Action\InventJournalPost
                switch(_inventJournalTable.journalType)
                {
                        case InventJournalType::Movement:
                        case InventJournalType::LossProfit:
                        case InventJournalType::Transfer:
                        case InventJournalType::BOM:
                        case InventJournalType::Count:
                        case InventJournalType::project:
                        case InventJournalType::Asset:
                                journalCheckPost_Mov = InventJournalCheckPost_Movement::newJournalCheckPost(
                                false,true, JournalCheckPostType::Post, _inventJournalTable.tableId,                            _inventJournalTable.journalId);
                                // journalTransData = _journalForm.JournalTransData();
                                // if (journalTransData)
                                // journalCheckPost_Mov.parmVoucher(journalTransData.journalTrans().voucher);
                                return journalCheckPost_Mov;

                        case InventJournalType::TagCounting:
                                journalCheckPost = InventJournalCheckPost_Tag::newJournalCheckPost(
                                false, true, JournalCheckPostType::Post, _inventJournalTable.tableId,                           _inventJournalTable.journalId);

                                return journalCheckPost_Tag;
                }
        }
        ;

        inventJournalTable = InventJournalTable::find(inventJournalId);
        if(inventJournalTable)
        {
                // Posting start. According to \Classes\InventJournalCheckPost
                // journalForm.runbaseMainStart();
                journalTableData::updateBlockServer(                 inventJournalTable, JournalBlockLevel::None, JournalBlockLevel::System, false);                 journalCheckPost = getJournalCheckPost(inventJournalTable);                 if (!journalCheckPost.PROMPT())                 {                         if (! journalCheckPost.BatchInfo().parmBatchExecute())                         {
                                // journalForm.runbaseMainCancel();                                 journalTableData::updateBlockServer(                                 inventJournalTable, JournalBlockLevel::System, JournalBlockLevel::None, false);                         }
                        return;
                }                 try                 {                         journalCheckPost.run();                         // journalForm.runbaseMainEnd(journalCheckPost,false);                         journalTableData::updateBlockServer(                         inventJournalTable, JournalBlockLevel::System, JournalBlockLevel::None, false);                 }                 catch (Exception::Error)                 {                         // journalForm.runbaseMainEnd(journalCheckPost,true);                         journalTableData::updateBlockServer(                         inventJournalTable, JournalBlockLevel::System, JournalBlockLevel::None, true);                 }                 // Posting end  }
Copyright © 2008 Ruslan Goncharov