How find an object using Progress 4GL? - progress-4gl

I need to find an object in a progress session... I don't know how to do... only with a sequential search, but it's very expensive(time consuming) if the number of objects is relative big.
Is there another way to do this?
define variable myObject As character no-undo.
define variable loop as Progress.Lang.Object no-undo.
assign myObject = "1234".
loop = Session:First-object.
do While valid-object(loop) :
if (loop:tostring() = myObject) then Do:
MESSAGE "Found!!!"
VIEW-AS ALERT-BOX INFO BUTTONS OK.
leave.
end.
loop = loop:Next-sibling.
end.
Thank you.

Related

Generic Procedure to generate report from browse in progress 4gl

Procedure should handle any table linked to browse means it should be generic.
please help.
/* below code is sample to Show the data in message box ,
but only first data it is showing right now.*/
DEFINE INPUT PARAMETER hRecord AS WIDGET-HANDLE.
DEFINE INPUT PARAMETER hQuery AS WIDGET-HANDLE .
DEF VAR hFld AS HANDLE NO-UNDO.
DEFINE VARIABLE iCOunt AS INTEGER INITIAL 0.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE j AS integer INITIAL 1.
MESSAGE hRecord:NUM-COL VIEW-AS ALERT-BOX.
DO WHILE TRUE:
hQuery:GET-NEXT().
iCount = iCount + 1.
DO i = 1 TO hRecord:NUM-COL:
hfld = hRecord:GET-BROWSE-COL(i).
MESSAGE hfld:SCREEN-VALUE.
END.
j = j + 1.
END.
MESSAGE iCount VIEW-AS ALERT-BOX.
END PROCEDURE.
You can get a buffer field like so:
hfld = hRecord:GET-BUFFER-FIELD(i).
and then get the field's value:
DISPLAY hfld:BUFFER-VALUE.
See the docs for an explanation of what these do.

4GL ABL Openedge loop through handle?

here is my current code
def var hbTT as handle.
for each Cust:
hbTT:buffer-create().
assign
hbTT::Name = Cust.Name
hbTT::address = Cust.Address.
end.
now what I want to do is to loop through hbtt. How can I do that?
I tried
for each hbTT:
/* Do something */
end.
the error I get is
unknown or ambiguous table hbTT. (725)
thank you
You won't be able to do a loop that way, as for each requires a static name.
Instead, try this:
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
create query hQuery.
hQuery:set-buffers(hbtt).
hquery:query-prepare('for each tt'). /* <-- Where tt is the original buffer name */
hquery:query-open().
hquery:get-first().
do while not hquery:query-off-end:
disp hbtt::name hbtt::address .
hquery:get-next().
end.

How to execute procedure from List with parameters in Progress 4gl?

I have some list like this
DEFINE VARIABLE procedurelist AS CHARACTER EXTENT 5
INITIAL [ "1.p", "2.p", "3.p", "4.p", "5.p"].
but this all procedures with input-output parameters and i want to execute this procedure, How can i do this? I have no idea how to do this.
The base of your solution is the RUN VALUE statement.
The manual states.
VALUE( extern-expression ) An expression that returns the name of the (local or remote) external procedure you want to run....
This basically means that you can input a string with the value of a program (or procedure) into your RUN statement.
If all input-output parameters are exactly the same you can do like this:
DEFINE VARIABLE procedurelist AS CHARACTER EXTENT 5 INITIAL [ "1.p", "2.p", "3.p", "4.p", "5.p"].
DEFINE VARIABLE iExtent AS INTEGER NO-UNDO.
DEFINE VARIABLE cVariable AS CHARACTER NO-UNDO.
DO iExtent = 1 TO EXTENT(procedurelist):
RUN VALUE(procedurelist[iExtent]) (INPUT-OUTPUT cVariable).
END.
If the parameters are different it gets trickier (but not impossible). The CREATE CALL and the Call Object can help you there. In this case you would need some kind of way to keep track of the different parameters as well.
Here's a basic example taken directly from the online help:
DEFINE VARIABLE hCall AS HANDLE NO-UNDO.
CREATE CALL hCall.
/* Invoke hello.p non-persistently */
hCall:CALL-NAME = "hello.p".
/* Sets CALL-TYPE to the default */
hCall:CALL-TYPE = PROCEDURE-CALL-TYPE.
hCall:NUM-PARAMETERS = 1.
hCall:SET-PARAMETER(1, "CHARACTER", "INPUT", "HELLO WORLD").
hCall:INVOKE.
/* Clean up */
DELETE OBJECT hCall.

Transaction lock not releasing on the last record

I have an include file with 2 functions. One of the functions works perfectly fine so I won't include it in this. I will include the function that is causing the issue.
The ss_update function is the one causing me issue and not releasing the lock as I assumed it would. I finally got to work this way adding the find current screenstate no-lock. statement. I wondering if someone can explain this to me and if there is a better way to handle this situation.
FUNCTION ss_update RETURNS INTEGER
( INPUT iUserName AS CHAR,
INPUT iScreenName AS CHAR,
INPUT iWidgetName AS CHAR,
INPUT iWidgetValue AS CHAR ):
DEFINE VARIABLE retStatus AS INTEGER NO-UNDO.
FIND ScreenState EXCLUSIVE-LOCK WHERE ScreenState.userName = iUserName AND
ScreenState.screenName = iScreenName AND
ScreenState.widgetName = iWidgetName NO-ERROR.
IF AVAIL ScreenState THEN
DO:
IF ScreenState.widgetValue <> iWidgetValue THEN
DO:
ASSIGN
ScreenState.widgetValue = iWidgetValue.
END.
retStatus = 1.
END.
IF NOT AVAIL ScreenState THEN
DO:
CREATE ScreenState.
ASSIGN
ScreenState.screenStateId = NEXT-VALUE(seq-ScreenStateId)
ScreenState.userName = iUserName
ScreenState.screenName = iScreenName
ScreenState.widgetName = iWidgetName
ScreenState.widgetValue = iWidgetValue.
retStatus = 2.
END.
/* This was added to release the lock. */
FIND CURRENT screenstate NO-LOCK.
RETURN retStatus.
END FUNCTION.
I have code that will call the update function several times in a row. Like this...
ss_update(USERID(LDBNAME(1)), "FindComp2.w", "t-ActiveOnly", t-ActiveOnly:SCREEN-VALUE).
ss_update(USERID(LDBNAME(1)), "FindComp2.w", "t-BadAdd", t-BadAdd:SCREEN-VALUE).
ss_update(USERID(LDBNAME(1)), "FindComp2.w", "LastCompany", company.companyId).
ss_update(USERID(LDBNAME(1)), "FindComp2.w", "rs-Filter", rs-Filter:SCREEN-VALUE).
ss_update(USERID(LDBNAME(1)), "FindComp2.w", "cb-Salesman", cb-Salesman:SCREEN-VALUE).
ss_update(USERID(LDBNAME(1)), "FindComp2.w", "cb-Search", cb-Search:SCREEN-VALUE).
ss_update(USERID(LDBNAME(1)), "FindComp2.w", "scr-Search", TRIM(scr-Search:SCREEN-VALUE)).
The problem I was having was that progress wasn't releasing the lock from the last called ss_update function. I had to add find current screenstate no-lock to downgrade the lock. This just seems ugly and not properly coded and was wondering why this happened and what is the proper way to handle this such issue.
Your record buffers are weakly scoped and there is likely a reference to ScreenState somewhere in the program that includes this function.
The function is likely "borrowing" the record from the main block.
To fix it there are several possibilities. It is quick and dirty but one of the things that I like to do is to add:
define buffer ScreenState for ScreenState.
at the top of the function definition. This may look a little odd but what it does is to force all references to ScreenState to be local to the function. It stops the accidental "borrowing" of scope.
The ultimate solution is to strong scope the records and declare explicit transactions. That code would look like this:
FUNCTION ss_update RETURNS INTEGER
( INPUT iUserName AS CHAR,
INPUT iScreenName AS CHAR,
INPUT iWidgetName AS CHAR,
INPUT iWidgetValue AS CHAR ):
define buffer ScreenState for ScreenState. /* prevent accidents from happening... */
define buffer updScreenState for ScreenState. /* used for updates */
DEFINE VARIABLE retStatus AS INTEGER NO-UNDO.
do for updScreenState transaction:
FIND updScreenState EXCLUSIVE-LOCK WHERE
updScreenState.userName = iUserName AND
updScreenState.screenName = iScreenName AND
updScreenState.widgetName = iWidgetName NO-ERROR.
IF available updScreenState THEN
DO:
IF updScreenState.widgetValue <> iWidgetValue THEN
DO:
ASSIGN
updScreenState.widgetValue = iWidgetValue.
END.
retStatus = 1.
END.
IF NOT available updScreenState THEN
DO:
CREATE updScreenState.
ASSIGN
updScreenState.screenStateId = NEXT-VALUE(seq-ScreenStateId)
updScreenState.userName = iUserName
updScreenState.screenName = iScreenName
updScreenState.widgetName = iWidgetName
updScreenState.widgetValue = iWidgetValue.
retStatus = 2.
END.
end.
RETURN retStatus.
END FUNCTION.
The code above defines both ScreenState and updScreenState -- strictly speaking plain old ScreenState does nothing since there are no references to it. But if someone comes along later (or if I somehow missed one) it will prevent accidental references from having side-effects.
Using updScreenState makes it clear and obvious that the buffer is for update purposes.
The explicit TRANSACTION keyword clearly defines where you expect a transaction to start -- if the compiler objects to that then it is telling you that your code is trying to do something that you didn't expect.
The DO FOR block is what "strong scopes" the updScreenState buffer. The compiler will object if there are stray free references to updScreenState lying around outside that block.

Bracketed key value - limit OR foreach ... in OR?

Continuing my quest to convert .NET to Progress, I faced another challenge yesterday.
Our company bought time ago a .NET DLL to manage Excel document without the need to install Microsoft Excel. There is several functions that return a series of cells depending of the need.
The returned value is a class that implement IEnumerator interface in .NET.
The problem is that I cannot find a way to iterate trough the cells without getting the error:
System.ArgumentException: Row or column index is invalid or out of required range
Is there a way to in Progress to validate if X is inside of the extent range?
OR
Is there a way to iterate trough the array without knowing the upper limit of the array?
Thank you!
Sebastien
--- temporary solution ---
/* declaration */
DEFINE VARIABLE oCell AS CLASS GemBox.Spreadsheet.ExcelCell NO-UNDO.
DEFINE VARIABLE oRange AS CLASS GemBox.Spreadsheet.CellRange NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
/* load excel file */
...
/* retrieve a series of cells */
ASSIGN oRange = oWorksheet:Cells:GetSubrangeAbsolute(1,1, 2,2).
/* first cell */
ASSIGN i = 0.
ASSIGN oCell = ?.
ASSIGN oCell = oRange:Item[i] NO-ERROR.
/* validate cell is in the range */
DO WHILE NOT oCell EQ ?:
MESSAGE oCell:Value VIEW-AS ALERT-BOX.
/* next cell */
ASSIGN i = i + 1.
ASSIGN oCell = ?.
ASSIGN oCell = oRange:Item[i] NO-ERROR.
END.
I don't have access nor I can test this solution, but if it implements correctly the interface some solution like this one should work:
/* declaration */
DEFINE VARIABLE oCell AS CLASS GemBox.Spreadsheet.ExcelCell NO-UNDO.
DEFINE VARIABLE oRange AS CLASS GemBox.Spreadsheet.CellRange NO-UNDO.
DEFINE VARIABLE oEnumerator AS CLASS System.Collections.IEnumerator NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
/* load excel file */
...
/* retrieve a series of cells */
ASSIGN oRange = oWorksheet:Cells:GetSubrangeAbsolute(1,1, 2,2).
oEnumerator = oRange:getEnumerator().
DO WHILE oEnumerator:MoveNext():
oCell = CAST(oEnumerator:current,"GemBox.Spreadsheet.ExcelCell").
END.
If it doesn't work exactly like this, at least it should point you in the correct direction to use it.
From the web page I'd infer that the # of cols =
oRange:LastColumnIndex - oRange:FirstColumnIndex
and the # of rows is
oRange:LastRowIndex - oRange:FirstRowIndex
I'd think using
oCell = oRange:Item[Int32, Int32]
to get the item at the row, col position would work better instead of using a single element array element.