| ProfileDynamics AX GeekBlogLists | Help |
|
December 14 Finding files with WinAPIAxapta’s WinAPI class has a bunch of static methods to handle files. The code example below shows how to utilize some of these methods to find files. The two methods used to fetch all files matching the search criteria are findFirstFile() and findNextFile(). Don’t forget to clean up after yourself with findClose().
The code also uses three different find methods:
The example uses the infolog for output. As with any infolog beware of performance and limitation to 10.000 lines.
static void FindFile(Args _args) { #File
FileName fullFileName(FileName _path, FileName _fileName) { FileName pathName; FileName fileName; FileName fileExtension; ; [pathName,fileName,fileExtension] = fileNameSplit(_fileName);
return _path + '\\' + fileName + fileExtension; }
void findFiles(FileName _path, FileName _fileName, boolean _inclSubDir = true, FileName _prefix = fullFileName(_path,_fileName)) { FileName fileName; int hdl; ;
setprefix(_prefix);
if (WinAPI::folderExists(_path)) { [hdl,fileName] = WinApi::findFirstFile(fullFileName(_path,_fileName));
while (fileName) { if (WinAPI::fileExists(fullFileName(_path,fileName))) info(fileName);
fileName = WinApi::findNextFile(hdl); }
WinApi::findClose(hdl);
if (_inclSubDir) { [hdl, fileName] = WinAPI::findFirstFile(_path+'\\'+#AllFiles);
while (fileName) { if (strlwr(fileName) != strlwr(_fileName) && strlwr(fileName) != strlwr('.') && strlwr(fileName) != strlwr('..') && WinAPI::pathExists(fullFileName(_path,fileName))) findFiles(fullFileName(_path,fileName), _fileName, _inclSubDir, fileName);
fileName = WinApi::findNextFile(hdl); }
WinApi::findClose(hdl); } }
}
findFiles('c:\\Program Files','*.doc'); } December 10 Using query()As the name implies QueryRun is the executor of a query linked to it. To construct a query you want QueryRun to execute, you need build classes:
Today’s example will use the first four to demonstrate a query that sums the credit limit field in CustTable grouped by Country and Currency. Additionally a count field indicates how many records are represented in the sum. Ranges are implemented for AccountNum and Country, but the user is allowed to add additional range criteria in the dialog. To demonstrate the status() property I have locked it, so the user can not change it.
QueryRun QueryRun executes the query. If needed the familiar query dialog can be opened before the query is run. This is done using the prompt() method. SysQueryRun extends QueryRun and has a total of 7 prompt*() methods, with different default behavior. For example promptAllowAddRange() would allow the user to add new where-conditions.
Query A query represents the select statement. This is where all the strings come together.
QueryBuildDataSource Using QueryBuildDataSources you add all the tables you want joined (just one in this example). This is also where you define how the resultset is to be sorted. The orderMode() method lets you define
QueryBuildRange Ranges represent where conditions. Multiple ranges are connected with AND conditions. Unfortunately there is no easy way to change that.
QueryBuildFieldList Represents the selected fields of the query. By default all fields are selected. In regular queries you probably wouldn’t use them that often, but when grouping this is the way to define which fields are calculated. SelectionField is an enum with the following values
The result of Test_Query on my test system looks something like this: CA CAD 1120,00 (3 records) CA USD 500,00 (1 records) DE EUR 0,00 (1 records) DK CAD 5000,00 (1 records) DK EUR 300,00 (6 records) DK GBP 560,00 (2 records) ES EUR 0,00 (1 records) IE EUR 0,00 (1 records) NL EUR 20,00 (2 records) NO EUR 3000,00 (1 records)
static void Test_Query(Args _args) { CustTable custTable; Query query = new Query(); QueryRun qr = new queryRun(query); QueryBuildDataSource qbds = qr.query().addDataSource(tableNum(CustTable)); QueryBuildRange qbrAccN = qbds.addRange(fieldNum(CustTable,AccountNum)); QueryBuildRange qbrCountry = qbds.addRange(fieldNum(CustTable,Country)); QueryBuildFieldList qbfl = qbds.fields(); ;
qbrAccN.value('4000..4050'); qbrAccN.status(RangeStatus::Locked); qbrCountry.value('CA..NO');
qbfl.addField(fieldNum(CustTable,CreditMax),SelectionField::Sum); qbfl.addField(fieldnum(CustTable,RecId),SelectionField::Count);
qbds.addSortField(fieldnum(CustTable,Country)); qbds.addSortField(fieldNum(CustTable,Currency)); qbds.orderMode(OrderMode::GroupBy);
if (qr.prompt()) {
while (qr.next()) { custTable = qr.get(tableNum(CustTable));
print strfmt("%1 %2 %3 (%4 records)",custTable.Country,custTable.Currency, num2str(custTable.CreditMax,10,2,0,0),custTable.RecId); }
}
pause; } December 06 Using lists & listIterators - 8 queens exampleI am not using lists a lot, because I find it too restrictive not to be able to move back in the list. Also, I find it annoying that end() moves beyond the last element, not to the last element. Nevertheless, there are some uses for lists and I want to give an example how it can be used.
The example is a puzzle: How do you place eight queens on a chessboard, so they can not beat each other? The algorithm below is a brute force approach to the problem: Find a place for the next queen, place it. If it was the 8th queen, save solution. If the queen could not be placed, undo last move, try next possible field and so forth. The problem can be solved nicely using iteration or recursion. I like recursion.
I am using two lists. The first one is for the current solution. All placed queens are placed in this list. It basically is a simple stack. All elements are added to the start of the list. To undo a move, the first element is deleted. The second list is a list of lists, a list of all solutions that worked out. To access the contents you need an iterator similar to sets and maps. You can see those in the last block, when solutions are output via the infolog.
static void eightQueens(Args _args) { int board[64]; int found; str solution; list listAllSolution = new list(types::Class); list listOneSolution = new list(types::String); listIterator li_OneSolution = new listIterator(listOneSolution); listIterator li_AllSolution = new listIterator(listAllSolution);
int board(int _row, int _col, int _upd = 0) { if (_row < 1 || _row > 8 || _col < 1 || _col > 8) return -1; else board[(_row-1)*8+_col] += _upd;
return board[(_row-1)*8+_col]; }
void updateBoard(int _row, int _col, int _upd) { int i; ; for (i=1;i<=8;i++) { board(i,_col,_upd); board(_row,i,_upd); board(i,_col-_row+i,_upd); board(i,_col+_row-i,_upd); } }
void placeQueen(int _row = 1, int _col = 1) { int i; ;
while (_col <= 8) { while (_col <= 8 && board(_row, _col)) _col++;
if (_col <= 8) { updateBoard(_row,_col,1); listOneSolution.addStart(strfmt("%1%2 ",num2char(64+_row),int2str(_col)));
if (_row == 8) { listAllSolution.addEnd(List::merge(listOneSolution,new list(Types::String))); } else placeQueen(_row+1,1);
updateBoard(_row,_col,-1); li_OneSolution.begin(); li_OneSolution.delete(); }
_col++; } }
;
placeQueen();
li_AllSolution.begin(); while (li_AllSolution.more()) { found++; solution = ""; li_OneSolution = new listIterator(li_AllSolution.value()); li_OneSolution.begin(); while (li_OneSolution.more()) { solution = li_OneSolution.value() + " " + solution; li_OneSolution.next(); } info(strfmt("%1: %2",found,solution)); li_AllSolution.next(); }
} December 04 RecId & TablebrowserA table with recId columns (other than its own recId) will not show these in the table browser. Take reqTransCov for example. It has two additional recId columns, linking it to reqTrans: ReceiptRecId and IssueRecId. You will not see those in the table browser. That is because adding them to a grid will always fail. Axapta will pretend it did add them, but in fact it did not. Try it out. Most of the time it will make sense not to display them as they are internal information and not of interest to the user. However, it might sometimes be helpful to see them, if just for debugging purpose. You can use the display-method workaround to do so:
This display method is a (datasource) method to display reqTransCov.receiptRecid:
display recid ReceiptRecid(common _record) { return _record.(fieldNum(ReqTransCov, ReceiptRecId)); }
The more general approach would of course be to implement this capability into \Classes\SysTableBrowser and Forms\SysTableBrowser. Maybe I’ll pick that up in a later post… December 02 Preview Steen's x++ and MorphX bookSteen Andreasen’s book is expected to be released in January. He has uploaded a free preview demo chapter online here. The chapter deals with what most developers dread – reports. Check it out, lots of code samples, illustrations and in depth explanations. Nice. November 28 Interesting fact about set(Types::Record)Sets can store data of type record. By definition sets only store unique data, right? So, if you use this construct you should be aware that recId is not taken into account when determining whether a record is already in the set or not. Now, in some tables it can be perfectly normal to have records whose only difference is the recId. If you need all instances of these records in your memory storage (set) for further processing, you are better of using a different construct like a recordSortedList, recordInsertList or maybe a map. Take inventTrans for example: run the job below on your test system. If you do get a match of set elements and inventTrans records try this: Add a sales order line, any item, qty = 2. Go to the inventory transaction, functions/split and make two transactions out of one. Now run the job again.
The result in my test company: 214 records in inventTrans 213 records in record set
static void SetDuplicates(Args _args) { inventTrans inventTrans; set recordSet = new set(types::Record); int recordCnt = 0; ;
while select inventTrans { recordSet.add(inventTrans); recordCnt++; }
print strfmt("%1 records in inventTrans",recordCnt); print strfmt("%1 records in record set ",recordSet.elements()); pause; } November 27 Axapta & precisionBrandon reported his frustration about un-Real precision yesterday. And he has a point of course. However, I think the real issue here is not how many digits there are after the decimal sign, it is how many decimals there are overall. Axapta handles a precision of up to 16 digits. This can become an issue when dealing with very large numbers and high precision at the same time (inventory closing for example). The job below illustrates the point. It job takes two numbers a and b, adds them up and prints the results. For each inner loop another digit is appended to the end. The first outer iteration takes a starting value of 9, the next 99 and the final iteration starts at 999. The point is that the calculation will “bum out” at step 15 for starting value 9, at step 14 for 99 and at 13. for 999. In other words it fails as soon as the total number of digits reaches 16.
static void realPrecision(Args _args) { int i,j; real a,b;
for (i=1;i<=3;i++) { a = exp10(i) - 1; b = exp10(i) - 1; for (j=1;j<=16;j++) { a += (9 / exp10(j)); b += (9 / exp10(j)); print strfmt("(%1) a = %2", num2str(j,2,0,0,0),num2str(a,25,16,1,1)); print strfmt("(%1) b = %2", num2str(j,2,0,0,0),num2str(b,25,16,1,1)); print strfmt("(%1) a + b = %2", num2str(j,2,0,0,0),num2str(a+b,25,16,1,1));
if (j mod 4 == 0) pause; } }
}
November 26 Manipulating bitsAxapta and x++ implements all the operators you need to manipulate data on a bit level. Not that you’d need it a lot these days, but it can be helpful to implement a set of flags for example. The standard application also makes occasional use of it, too (see InventDimFixedClass).
The bit operators are << shift left (multiply with 2) >> shift right (divide by 2) & binary and | binary or ^ binary xor ~ binary inversion
Take a look at the job for a few examples on how they are used.
static void Bits(Args _args) { int mask;
int setBit(int _bitMask,int _bit) { return _bitMask | (1 << _bit); }
int killBit(int _bitMask,int _bit) { return _bitMask & ~(1 << _bit); }
boolean bit(int _bitMask, int _bit) { return setBit(0,_bit) & _bitMask; }
int flipBit(int _bitMask, int _bit) { return bit(_bitMask,_bit) ? killBit(_bitMask, _bit) : setBit(_bitMask, _bit); }
int invertMask(int _bitMask) { return ~_bitmask; }
int xorMask(int _bitMask1, int _bitMask2) { return _bitMask1 ^ _bitMask2; }
str bitString(int _bitMask) { str bS; int i;
for (i=31;i>=0;i--) bS += bit(_bitMask,i) ? "1" : "0";
return bS; } ;
mask = setBit(mask,4); print bitString(mask);
mask = flipBit(mask,7); print bitString(mask);
mask = killBit(mask,7); print bitString(mask);
mask = invertMask(mask); print bitString(mask);
mask = xorMask(mask,255); print bitString(mask);
pause; } November 24 #InventDimJoinIf you’ve developed anything in the supply chain area, you’ve most probably come across InventDimJoin. InvenDimJoin is a macro and it’s mostly used to narrow down search results for inventory transactions (inventTrans) or inventory postings (inventTransPost), but can be used on any table having a inventDimId field.
The macro accepts four to five parameters · InventDimId · InventDim · InventDimCriteria · InventDimParm · Index hint (optional)
You must use it as a join with a select-statement (hence the name) and it returns no contents from inventDim. It is not an exist join, but doesn’t return any useful fields, so it probably should be an exist-join. Anyway, let’s take the parameters one by one:
InventDimId This is the unique key to the inventory dimension table. This is where you supply the inventDimId field from the table you are joining.
InventDim Any InventDim table buffer. This is the table buffer used in the joined select. The contents of the buffer has no influence on the select result set and resulting records will only have the tableId field filled (but that’s a constant anyway and already filled by just defining the buffer, so really you get nothing).
InventDimCriteria This is also a inventDim buffer, but the contents of this one matters. Using this buffer, you define which dimensions you are looking for exactly (warehouse ‘Main’, location ‘IN-1’, batch ‘241105’ etc.).
InventDimParm InventDimParm is a temporary table. A record basically consists of nothing but a number of flags to indicate which inventory dimensions are important and which ones can be disregarded. There is a *Flag field for every inventory dimension field. InventDimParm has a variety of methods to initialize these flags. An example would be initPhysicalInvent(). It clears all flags (=No) and sets only those flags to yes whose corresponding inventory dimension is a physical dimension (for the dimension group id you pass along as a parameter). Consequently you provide InventDimJoin with a InventDimParm buffer. For all flags with value ‘No’ the contents of the corresponding inventDimParm field does not matter.
Index hint This is an optional parameter to help you optimize performance.
Here's an example:
static void InventDimJoinTest(Args _args) { SalesLine salesLine; InventDim inventDim; InventDim inventDimCriteria; InventDimParm inventDimParm; ;
InventDimCriteria.InventLocationId = 'Main'; InventDimCriteria.wMSLocationId = 'IN-1'; InventDimCriteria.configId = 'Red';
inventDimParm.clear(); inventDimParm.InventLocationIdFlag = NoYes::Yes; inventDimParm.wmsLocationIdFlag = NoYes::No; inventDimParm.ConfigIdFlag = NoYes::Yes;
while select salesLine #InventDimJoin(salesLine.inventDimId, InventDim, inventDimCriteria, InventDimParm, dimIdx) { print strfmt("%1 %2 %3 %4 %5",salesLine.InventDimId, salesLine.SalesId, salesLine.ItemId, inventDim.InventLocationId, salesLine.inventDim().InventLocationId); }
pause; }
The job finds all salesLines for the ‘Main’ warehouse and configuration ‘Red’. The location doesn’t matter, since it’s inventDimParm flag is turned off (additional dimensions like batch, serial etc.wouldn’t make a difference either, clear() takes care of that). Note that %4 prints the inventDim.inventLocationId. I just put that in there to make the point that it will always be blank. dimIdx is the optional parameter. It’s an index defined on the InventDim table. You will notice in the output the result is sorted by inventDimId.
November 23 Executing external x++ coderunbuf() executes x++ code passed to it. The code must be defined as a function and can return a value. Parameters passed to runbuf() will be forwarded, but default parameters won’t work. To show how it works, I am going to use this function to execute code read from an external file. Not very useful and you probably wouldn’t want to allow it, it’s just to show that it can be done (easily).
static void ExecuteCodeFromFile(Args _args) { #File AsciiIo asciiIo = new AsciiIo("c:\\temp\\findCustomer.xpp",#io_read); XppCompiler xppCompiler = new XppCompiler(); Source source; str line; CustTable custTable; ;
if (asciiIo) { asciiIo.inFieldDelimiter(#delimiterEnter); [line] = asciiIo.read();
while (asciiIo.status() == IO_Status::Ok) { source += #delimiterEnter; source += line; [line] = asciiIo.read(); }
if (!xppCompiler.compile(source)) error (xppCompiler.errorText());
custTable = runbuf(source,'4000'); print CustTable.Name; } else print "Could not open file";
pause; }
The external file c:\temp\findCustomer.xpp:
CustTable findCustomer(CustAccount _accountNum) { return CustTable::find(_accountNum); }
First the file c:\temp\findCustomer.xpp is read into source. Source is then compiled and if that goes okay it is executed. As you can see ‘4000’ is passed as a parameter simply by adding it to the runbuf() call. You can also see runbuf returns the function’s return value.
I had trouble getting code compiled that I had written using notepad. As it turns out, the compiler does not accept the tab character. So if you are going to try this out, watch out for that. Productivity tip: classes EditorScripts & xppSourceDid you ever notice that little service icon in the Axapta source editor? When you open it, it reveals some tools like commenting a source-block or inserting a template. That can for example be a while() loop or an if() block. It is actually surprisingly easy to hook your own little scripts up to this menu.
All you need is a method in Classes\EditorScripts. comments_insertHeader() is one of the standard methods, but you can add your own methods. Use the underscore to define the menu path. Having defined a method you need to add some task. The example I will show here is adding a block to iterate a map. I will add the template to \Classes\xppSource and call it from my EditorScripts method. And that's it. You are ready to use your new tool.
This is the template we will build:
miMap = new mapIterator(map); miMap.begin(); while (miMap.more()) { miMap.next() }
..and this is how we do it:
\Classes\EditorScripts void template_flow_iterateMap(Editor editor) { xppSource xppSource = new xppSource(editor.columnNo()); ; editor.insertLines(xppSource.iterateMap()); }
\Classes\xppSource Source iterateMap(Source _mapName = 'map') { str miMapName = "mi"+StrUpr(substr(_mapName,1,1))+substr(_mapName,2,strlen(_mapName)); ;
source += strfmt("%1 = new mapIterator(%2);",miMapName,_mapName); source += '\n'; source += this.indent(); source += strfmt("%1.begin();",miMapName); source += '\n'; source += this.indent(); this.while(strfmt("%1.more()",miMapName),strfmt("%1.next()",miMapName));
return source; }
Now obvisouly there’s some room for improvement here. We could search the code for defined maps / mapIterator and use those etc. This is just to show how it works in general.
November 21 Useful methods for sets / record2set exampleI find myself working with sets and maps a lot. They are a fast and easy way to buffer information without going back to the database all the time. The set object has a few nice methods to compare sets.
Union Pass two sets to this method. The method returns a new set that includes all items from either set.
Difference Pass two sets to this method. The return set includes all items from the first set that are not found in the second set.
Intersection Pass two sets to this method. The method return set includes all items found in both sets.
The following example illustrates the three methods. record2Set creates a set out of any table record (for simplicity arrays are excluded here). The format is fieldname#value The method is applied to two customer records. Check out the results you are getting:
static void JobSetExamples(Args _args) { custTable custTable_1; custTable custTable_2; Set setCustTable_1; Set setCustTable_2; Set compare;
set record2set(common _record) { DictTable dictTable; DictField dictField; FieldId fieldId; Set recordSet; ;
dictTable = new DictTable(_record.tableId); recordSet = new Set(Types::String);
for (fieldId = dictTable.fieldNext(0);fieldId;fieldId = dictTable.fieldNext(fieldId)) { dictField = dictTable.fieldObject(fieldId); if (!dictField.isSystem() && dictField.arraySize() == 1) recordSet.add(strfmt("%1#%2",dictField.name(),_record.(fieldId))); }
return recordSet; } ;
custTable_1 = CustTable::find('4000'); custTable_2 = CustTable::find('4000'); custTable_1.AccountNum = "1234"; custTable_1.Name = "Test"; custTable_2.CreditMax = 333.33;
setCustTable_1 = record2set(custTable_1); setCustTable_2 = record2set(custTable_2);
compare = Set::difference(setCustTable_1,setCustTable_2); print compare.toString();
compare = Set::difference(setCustTable_2,setCustTable_1); print compare.toString();
compare = Set::intersection(setCustTable_1,setCustTable_2); print compare.toString();
compare = Set::union(setCustTable_1,setCustTable_2); print compare.toString();
pause; } November 20 Storing objects in a containerAxapta containers have a severe limitation: They don’t accept objects. Or do they? Well, not directly, but there is a way to do it: pack the contents into a container. Containers can store containers, so that will work.
Let’s take an easy example first:
static void Container1(Args _args) { container con, conSet; Set set = new Set(Types::String); int intValue = 42; real realValue = 1.61803; ;
set.add("John"); set.add("Paul"); set.add("George"); set.add("Ringo"); print set.toString();
con = [set.pack(),intValue,realValue];
// do some processing, call methods using the container etc.
[conSet,intValue,realValue] = con; set = Set::create(conSet); set.add("Yoko Ono");
print set.toString(); pause; }
Now this was easy, because the set is a basic data type string and sets have a pack method. What if it was a real object? Well, we would have to implement the SysPackable interface. The interface only two methods: pack() and unpack(). For the next example I am going to create a new class TestPackable. I am going to get the class wizard to do most of the work for me.
1. Go to Tools / Development Tools / Wizards / Class Wizard. 2. Choose a name for the class, for example TestPackable, select class template “Normal”, no inheritance. Click Next. 3. From the list of interfaces select SysPackable. Click Next. 4. Select “Create all interface methods”, “Create all abstract methods”. Click Next. 5. Click Finish.
That’s it. Now all we have to do is adding some functionality.
void new(int _intValue, real _realValue, Set _set) { intValue = _intValue; realValue = _realValue; set = _set; }
str toString() { return strfmt("%1 %2 %3",intValue,realValue,set ? set.toString() : ""); }
public boolean unpack(container _packedClass) { container setContainer; int version = runbase::getVersion(_packedClass);
switch (version) { case #CurrentVersion: [version,#CurrentList] = _packedClass; set = Set::create(setContainer); return true; default : return false; }
return false; }
public container pack() { container setContainer = set.pack(); ;
return [#CurrentVersion,#CurrentList]; }
public class TestPackable implements SysPackable { #define.CurrentVersion(1) #localmacro.CurrentList intValue, realValue, setContainer #endmacro
int intValue; real realValue; Set set; }
I am using the macro #CurrentList to define which instance variables to put in the container (the foundation was already generated by the wizard). I have also added a simple toString() method, so we can check the results are as expected. The following job is an example of how pack/unpack can be used:
static void Container2(Args _args) { container con; Set set1 = new Set(Types::String); Set set2 = new Set(Types::String); int intValue = 42; real realValue = 1.61803; TestPackable testPackable1, testPackable2; ;
set1.add("John"); set1.add("Paul"); set1.add("George"); set1.add("Ringo"); set2.add("Bjorn"); set2.add("Benny"); set2.add("Frida"); set2.add("Agnetha");
testPackable1 = new TestPackable(intValue,realValue,set1); print testPackable1.toString();
testPackable2 = new TestPackable(intValue,realValue,set2); print testPackable2.toString();
con = [testPackable1.pack(),testPackable2.pack()];
// do some processing, call methods using the container etc.
testPackable1 = new TestPackable(0,0,null); testPackable2 = new TestPackable(0,0,null);
testPackable1.unpack(conpeek(con,1)); testPackable2.unpack(conpeek(con,2));
print testPackable1.toString(); print testPackable2.toString();
pause; }
The job creates two instances of TestPackable, puts them both into a container, retrieves them again and displays the contents for verification. SysPackable is implemented by some standard classes as well, Classes\Dialog is one of them. November 19 cross-references & findOne of the cool things in Axapta is that you can read and modify the actual source code (provided you have the source code license, of course). When working with a standard object you often need to know where and how it is used. This is where two essential tools come in: cross-references and the find-dialog.
Cross-References Cross references answer questions like "Where is this object used?" and "What objects is it using?". You access it by right clicking any object in the AOT, Add-Ins/Cross-Reference. The key is to keep the references updated. The initial references are built during the installation (installation check list / update cross-reference). It is up to you to keep it updated when you later change or add objects to the AOT. You can do that periodically by right clicking the top AOT node, Add-Ins / Cross-reference / Update or you can have the system update the reference whenever you compile some code. To do that go to Menu Tools / Options / Button Compiler and activate the Cross-reference checkbox.
Find Dialog The find dialog is more powerful than you might think. The search text is actually a regular expression. The syntax for regular expressions in Axapta can be found under System Documentation / Functions / match. It is not quite as powerful as Unix users might be used to, but it’ll do.
The find dialog allows you to search not only methods, but “All nodes”. Properties for example can be found easily, if you know they are stored as text as Property blank(s)#Value.
Find all temp. tables: Go to Data Dictionary\Tables find “All nodes” containing text
Temporary *#Yes
Ever wondered why searching for static method calls or enum types didn’t return any results? Colons are treated as special characters in regular expressions, so to find them you have to escape them with a backslash
InventDirection\:\:Receipt
finds all occurrences of InventDirection::Receipt in the selected scope.
The dialog even let’s you define your own filter code. Your code is actually compiled to a method called FilterMethod(). The method has four parameters:
You can define any filter you want, your source simply needs to return a boolean to indicate found / not found. For example to find all insert() methods that do not call super(): Go to Data Dictionary / Tables find methods named insert, then copy this into the source field:
TextBuffer tb = new textbuffer(); tb.setText(_treeNodeSource); return !tb.find("super()");
The method is also useful to match or-conditions.
TextBuffer tb = new textbuffer(); tb.setText(_treeNodeSource); return tb.find("insert()") || tb.find("delete()");
Execute this find on the classes node to return all methods that call insert() or delete(). November 18 Microsoft Shuffles the Deck Chairs at MBSMicrosoft Shuffles the Deck Chairs at MBS. Mary Jo Foley at Microsoft Watch reports, Microsoft Business Solutions SVP Doug Burgum will move into a new chairman role next spring, where he will be charged with more evangelism and long-range thinking about Microsoft's applications business. Microsoft is hoping to have a new MBS senior vice president in place by Spring 2006. November 17 Recursion in AxaptaAxapta can handle recursive method calls and you can see the standard application making use of it in some places, for example in \Classes\ReqTransFormExplosion\treeBuildNode. But just deep can you go before it bugs out?
Let’s take a classic example of recursive programming: Towers of Hanoi.
The Tower of Hanoi puzzle was invented by the French mathematician Edouard Lucas in 1883. We are given three pegs and a tower of n disks, initially stacked in increasing size on one of three pegs. The objective is to transfer the entire tower to one of the other pegs, moving only one disk at a time and never a larger one onto a smaller.
The code to solve this puzzle is minimal, very intuitive – and recursive.
static void TowersOfHanoi(Args _args) { void move(int _n,str _from, str _to, str _transfer) { if (_n > 1) { move(_n-1,_from,_transfer,_to); move(1,_from,_to,_transfer); move(_n-1,_transfer,_to,_from); } else print strfmt("%1 -> %2",_from, _to); } ;
move(3,"Peg #1","Peg #3","Peg #2"); pause; }
The parameters instruct the function to move 3 discs from peg #1 to peg #3 and use peg #2 as a buffer. The solution is to move n-1 discs to the buffer, the last (biggest) disc to the destination and then move the rest from the buffer to the destination. How do you move the rest from the buffer to the destination? Well, you move n-1 discs… you get the point.
Back to original question: How far can you go before it crashes? The answer is 400. Try it out. Change _n from 3 to 400 and run the job. No problem. It will take a long time to finish, so unless you really need to know how to move 400 discs, I suggest you interrupt with Ctrl+Break :-). Now change _n to 401. It crashes almost instantly. Each call to move adds one level of recursion (if _n > 1), so that’s 399 + 1 for the initial call = 400.
Btw: Don’t use info() for the output. You would run into a whole other limitation with the infolog. November 16 Deleting all objects from a custom layerAn entire layer can safely be deleted by removing these two files:
ax<layer>.aod aod stands for “application object data”. This file holds all objects for the layer it is named after. For example, axusr.aod for the usr-layer, axcus.aod for the cus-layer etc.
axapd.aoi aoi stands for “application object index”. It tells Axapta where to find each individual object. The file will be rebuild the next time you log on to Axapta.
Note: Deleting these will not delete any labels. Labels are stored in files using this naming convention:
ax<layer><language>.ald Application label data
ax<layer><language>.alc application label comments
ax<layer><language>.ali application label index
If you delete entire layers frequently and are prefer to automate repetitive tasks, you can easily create a batch file to do the job for you.
move /Y "F:\Program Files\Axapta30_SP3\Application\Appl\Standard\axusr.aod" "F:\Program Files\Axapta30_SP3\Application\Appl\Standard\axusr.aod_bak" move /Y "F:\Program Files\Axapta30_SP3\Application\Appl\Standard\axapd.aoi" "F:\Program Files\Axapta30_SP3\Application\Appl\Standard\axapd.aoi_bak"
This example has the added benefit of moving the file to *_bak instead of deleting it outright. If for some reason I need the old version back I can restore it by simply moving them back to their original names.
It goes without saying that no user should be logged into Axapta when you delete or move these files. November 15 Working with multiple configurations? Try -regconfigWhen working with multiple configurations, using the configuration utility to select the current config before logging on can get tiresome. You might use different configurations if you have more than one Axapta installation or each configuration has a different default company, for example.
There’s an easy way around it: For each of your configurations create a shortcut to the appropriate ax32.exe, and then change the shortcut properties (target) to include the -regconfig parameter.
"F:\Program Files\Axapta30_SP3\Client\Bin\ax32.exe" "-regconfig=Axapta 3.0 SP3 Fat Client"
That's what the target for one of my SP3 installations looks like. Now all you have to do to get into the right installation is double-clicking the corresponding shortcut. No more AxConfig :-).
November 14 SQLSYSTEMVARIABLESWhen restoring an Axapta database from a different server, you might have experienced the following error message:
[Microsoft][ODBC Database Server Driver][SQL Server] Invalid object name ‘SQLSYSTEMVARIABLES’.
The problem is your database user (default bmssa) can not access the SQLSYSTEMVARIABLES table. If the original and new SQL server use the same user id, the following query analyzer command should fix it:
exec sp_change_users_login 'update_one','bmssa','bmssa'
If that doesn’t help or user ids are different, try changing the database and object owners.
exec sp_changedbowner 'bmssa'
changes the owner of the database. I have used this script to change the owner of tables.
DECLARE @oldOwner sysname, @newOwner sysname, @sql varchar(1000)
SELECT @oldOwner = 'AX30' , @newOwner = 'bmssa' , @sql = ' IF EXISTS (SELECT NULL FROM INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA)+''.''+QUOTENAME(TABLE_NAME) = ''?'' AND TABLE_SCHEMA = ''' + @oldOwner + ''' ) EXECUTE sp_changeobjectowner ''?'', ''' + @newOwner + ''''
EXECUTE sp_MSforeachtable @sql
Hi Axapta Fans!Welcome to my blog! I will post links, code snippets, tips and tricks and Axapta news, ... whatever I feel worth sharing with the AX community. Please feel free to add your comments and ideas. Thanks! Check out the linked websites and blogs. There's a lot of great information out there. |
|||
|
|