2015-03-24
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();
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 - Trapping scroll events
I've designed it as a new class xApplyScrolling
// Begin 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: 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
#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 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++
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())First of all we need to get rid of journalForm.
{ if (! journalCheckPost.BatchInfo().parmBatchExecute()) journalForm.runbaseMainCancel(); return; } try { journalCheckPost.run(); journalForm.runbaseMainEnd(journalCheckPost,false);
} catch (Exception::Error) { journalForm.runbaseMainEnd(journalCheckPost,true); } }
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 endFinally, 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