|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
On one of our more recent projects, we were required to display a PDF document by double-clicking in an edit table. No real issue, we can use just use SHELLEXEC as documented here right? Wrong. The problem with a SHELLEXEC, is that by default, it launches a browser that has an address bar containing the full URL to the document in question, allowing the user to modify the URL and potentially access information they were not privy to. (The users don't have browse rights so there isn't a problem of them browsing to other information, but knowing the fully formed URL they could take intelligent guesses at other potential document names). So, we considered looking at turning the address bar off, but then remembered the new WebView control. A control that allows you to embed the Microsoft WebView2 Edge Browser directly into an OpenInsight form and to interact with it. This looked like it was going to be the easiest hack ever. Create a form with just two controls, the WebView control and a close button. Have it accept as a create parameter the document to display, and all is good, yes? So we added a quickevent to the form that simply set the URI property of the WebView control, to the value passed in the createParam for the form. It ran. It did nothing. Puzzled we modified the CREATE quickevent to also update the screen caption with the file name we were trying to display. (This ability to stack quickevents rocks!). The caption changed to the correct value but still nada. Thinking laterally we invoked the system monitor and used the SP command to set the URI of the control to the value we had passed in. It displayed. Rinse and repeat. Chatting to the developers, it seems the issue here is that the WebView control takes a little time to instantiate itself, so it it might not be finished before the CREATE event runs. So we have to wait for the WebView control to be finished and THEN set the URI. Sounds like a case for looping, querying status and waiting, yes? No. The WebView control is asynchronous. So all we have to do is wait for an event that tells us that the WebView control is ready, and THEN set the URI. All this took was two quickevents. One on the CREATE event, setting a synthetic property to contain the document name and one on the WEBVIEWCREATED event for the WebView control (which is fired when the WebView is fully instantiated) which retrieves the synthetic property and uses it to set the URI property. Occasionally RTI slip out little tools and utilities that they've found useful. If you're very lucky they'll even ship source. One such gem in OI 10 is the OIWIN Date/Time picker - RTI_DATETIME_POPUP to its friends. If ever you've wanted to offer your user a way of selecting date or time from a calendar/time control, and you've not been too happy with the datetimepicker OCX and the old POPUP_MONTH dialog seems a little dated, then this is the one for you. It really is stunningly simple to use. Just add a quickevent to call a stored procedure and you're sorted. For illustrative purposes, here's a sample window - The cuebanners reflect the conversion on the edit line. The quickevent for the options on each editline is defined thusly - Once you've chosen the Target you can just accept the defaults other than DEFPROP which should be replaced with INVALUE. We only need one options button because we're taking advantage of a newly exposed property - in the property panel under the Behavior section, we're setting ALLOWFOCUS to false and then we can just send an OPTIONS event to @FOCUS. So how does the routine behave? See the following screen shots - Now there are a couple of caveats that I need to mention. The first (as you can see) is that DT and DTS provide the same dialog. The second (due to a bug that I leave you to find) is that DTS always returns 1967 as the year. But as you are encouraged to save under a different name and compile your own version it is easily fixed! If you don't want to then it is all fixed with the latest beta. The examples given below show what displays when you let the system determine default values. Examining the source reveals that, should you wish to, you may have much more granular control over the behaviour of the control. In this endeavour RTI_POPUP_DATETIME_EQUATES are your friend, but to save you having to look at this, we'll document their use here. If you use DIALOG_BOX to launch the RTI_POPUP_DATETIME window, you can pass in an @Fm delimited create parameter having the following values - <1> Mode, D, DT, DTS, MT, MTS We can override date ranges, set default values and even alter dialog positioning by passing in the correct values. One of the biggest differences between OI TCL and AREV TCL is that the latter made use of the VOC file to allow the user to customise commands and even to add their own commands. This does not seem to be the case in OI, rather a cursory examination of the object code for RTI_IDE_TCL suggests that the command list is hard coded. With this in mind it might be useful to document exactly what commands are supported, and where unclear, what they actually do. For the purposes of this article it will be assumed that the reader is familiar with the syntax of AREV TCL commands. When optional variables are nominated they will normally be in the order TABLE ROW(S) OPTIONS. So in alphabetical order, the commands are as follows -
Recently we were stumped by a compilation error when using pipes to create a multi line statement, this sort of thing, if value then on inlist("ALPHA,BRAVO,CHARLIE", value, ",") goSub | doAlpha, | doBravo, | doCharlie end the compiler just kept complaining about an unmatched then/end else. We thought that the only way to get to the bottom of this was to look at what the precompiler produced to see where the error was. Struggling we tried to recall the syntax of the pragma to save the precompiled output. Fortunately a more knowledgeable team member pointed out that using the pipe stripped spaces by design, to allow aesthetic alignments of strings. What this meant was that the compiler was not compiling goSub doAlpha, but rather, goSubdoAlpha. The solution, repeating pipes. Using two pipes prevents the spaces being stripped and if value then on inlist("ALPHA,BRAVO,CHARLIE", value, ",") goSub || doAlpha, | doBravo, | doCharlie end compiled just fine. Anyway it's been 16 years since our article on using pragmas and it's still as relevant as ever, so why not check it out and refresh your memory! We recently came across a situation that we have literally never encountered in our decades of working with Linear Hash. Basically a FIXLH operation was interrupted mid process and hi-jinks ensued. The resultant mess took a while to sort out but our findings may prove useful if you ever find yourself in a similar situation. This specific issue was encountered on an AREV 3.12 system but the logic for FIXLH remains roughly the same so the information remains applicable. Basically, the FIXLH operation was interrupted about 10% of the way through processing (the NTVDM abended). A quick check of the table header revealed that it believed itself to be a clean empty table with no rows. Yet listing the table produced a set of row ids after an initial pause while nothing was apparently happening. What had happened was that the table header had been reset to that of an empty table, and the first 10% or so of the primary frames had been blanked. This caused us to investigate the processing undertaken by FIXLH and we can confirm that it is as follows: Create and/or clear the temporary dumpfix table grab frame zero group write rows in group to temporary dumpfix table set frame zero header to that of an empty table loop grab next group write rows in group to temporary dumpfix table wipe primary frame to null until no groups left repeat copy rows in temporary table back to source table So if the operation is interrupted, some of the data will be in the source table and some will be in the temporary dump fix table. As an additional caveat, note that if the rows in the temporary dump fix table are not stored off elsewhere, they will be lost next time FIXLH is run. Since the advent of the UD we've never had to actually fix a GFE for quite some time, so this was an eye-opening operation! Backups are your friend... Recently at a client site, we encountered an error that was a little more baffling than normal. Under very specific circumstances, RLIST would bail with no results. Eventually, we had a duplicatable test sentence which was basically 1. PROCMON and other useful tools are available as part of the Sysinternals suite of development tools, which no programmer should be without.
Those of you who've been in the industry for a while will remember the smugness with which we treated the Y2K issues the rest of the world were experiencing. It didn't affect us, we stored our dates in internal numerical format. I'm sure we all have favourite anecdotes. Mine was being called into the Press Office of a major Government department to certificate their AREV tracking system. I sat down and gave the system a cursory glance. "Ermmm - it doesn't seem to use dates?". "No, it doesn't". "Well, here's your certificate"... turns out they couldn't get the budget for system tweaks but they could for Y2K compliance. I spent the rest of the day implementing their desired changes. Anyway, this week wiped the smile off of our collective faces (if you'd ignored the advice in KB 42 thirty or so years ago - which to be honest you could be forgiven for) with the advent of our very own variant - we'll call it the 20100 bug, although it's not a bug, it's an unfortunate feature. Users began reporting that date searches were failing for values after the 10th of January 2023. At first we couldn't see an obvious reason. We built a database containing date values going back to the previous century with five rows per date and indexed it. We wrote a test program and ran it RUN ZZ_POP 10/01/2023 RUN ZZ_POP 11/01/2023 RUN ZZ_POP 20099 What we've fallen foul of is discussed in the KB reference earlier. Basically BTREE.EXTRACT takes what it is given and tries to ICONV it to use for the look up. If it can't ICONV it (20099 isn't a viable date) it uses the value passed. If it CAN ICONV it, is uses the ICONVed value. And guess what? 20100 ICONVs to the 2nd of January 2000 as you can see above. Of course, this won't be the first time this has happened in the wild - for example looking for the 7th of December 1998 using 11299 would have returned the 1st of December 1999 and so on. Note that the same issue will be experienced when using internal date formats with RLIST statements. The solution? When calling BTREE.EXTRACT use EXTERNAL values as it will try and ICONV the data before using it. AddendumWe've been asked to share the code we used to locate btree.extracts in client systems to enable a manual inspection to determine possible failure points. This is a rough and dirty hack which met our requirements. Feel free to tailor to your own requirements. 0001 Subroutine zz_find_Btree_Extract( void )
0002 /* 0003 Author AMcA 0004 Date Jan 2022 0005 Purpose Quick hack to help identify system usage of btree.extract 0006 Provided as is with no warranty 0007 */ 0008 0009 Gosub initialise 0010 Gosub process 0011 0012 Return 0013 0014 initialise: 0015 0016 filesToCheck = "SYSPROCS,SYSREPOSEVENTS,SYSTABLES" 0017 columnsToCheck = ",,8" 0018 0019 loopCtr = Dcount(filesToCheck, ",") 0020 resultString = "" 0021 0022 Return 0023 0024 process: 0025 0026 For loopPtr = 1 To loopCtr 0027 file = Field( filesToCheck, ",", loopPtr ) 0028 column = Field( columnsToCheck, ",", loopPtr ) 0029 0030 resultString := file : \0D0A\ 0031 0032 if file = "SYSTABLES" Then 0033 starting = "%" 0034 Gosub processSysTables 0035 End Else 0036 starting = "@" 0037 Gosub processRest 0038 End 0039 0040 resultString := \0D0A0D0A\ 0041 0042 Next 0043 0044 Oswrite resultString On "ZZ_BE.TXT" 0045 Call Set_Property("CLIPBOARD", "TEXT", resultString ) 0046 0047 Return 0048 0049 processRest: 0050 Open file To v Then 0051 Gosub processV 0052 End Else 0053 Call FSMsg() 0054 End 0055 Return 0056 0057 processV: 0058 0059 Select v 0060 eof = 0 0061 Loop 0062 Readnext id Else eof = 1 0063 Until eof Do 0064 If id[1, 1] = starting else 0065 Read row From v, id Then 0066 ptr = 1 0067 If column Then 0068 saveRow = row 0069 row = row< column > 0070 Convert @Vm To @Fm In row 0071 end 0072 lenRow = Len(row) 0073 lineNo = 0 0074 If lenRow > 0 then 0075 Loop 0076 nextline = row[ptr, @Fm] 0077 ptr = Col2() + 1 0078 lineno += 1 0079 there = IndexC( nextLine, "btree.extract(", 1) 0080 If there Then 0081 variable = Trim( nextLine[ there + 14, ","] ) 0082 If variable[1, 1] = "'" Or variable[1, 1] = '"' Then 0083 // passing literals - check the line 0084 resultString := file : \09\ : id : \09\ : lineNo : \09\ : nextLine : \0D0A\ 0085 End else 0086 // ok we're now going to work back through the code until we find = for our var 0087 tempLineNo = lineNo 0088 Loop 0089 tempLineNo -= 1 0090 line = row< tempLineNo > 0091 If Trimf( line )[1, 1] = "*" Then 0092 line = "" 0093 End 0094 Convert " " To "" In line 0095 line = " " : line 0096 Until IndexC( line, " " : variable : "=", 1) 0097 Until tempLineNo = 0 0098 Repeat 0099 If tempLineNo = 0 then 0100 resultString := file : \09\ : id : \09\ : lineNo : \09\ : "Not found " : row< LineNo > : \0D0A\ 0101 End else 0102 resultString := file: \09\ : id : \09\ : lineNo : \09\ : row< tempLineNo > : \0D0A\ 0103 end 0104 end 0105 End 0106 While ptr < lenRow 0107 0108 Repeat 0109 End 0110 End Else 0111 end 0112 Call send_info( file : " " : id ) 0113 end 0114 repeat 0115 return 0116 0117 processSystables: 0118 0119 Open file To v Then 0120 Select v 0121 saveV = v 0122 eof = 0 0123 dictCtr = 0 0124 0125 Loop 0126 Readnext id Else eof = 1 0127 Until eof Do 0128 If ID[1, 5] = "DICT." Then 0129 Open id To v Then 0130 0131 file = id 0132 0133 Call push.Select( v1, v2, v3, v4 ) 0134 Gosub processV 0135 Call pop.Select( v1, v2, v3, v4 ) 0136 eof = 0 0137 0138 End Else 0139 * Call FSMsg() 0140 End 0141 v = saveV 0142 end 0143 Repeat 0144 End Else 0145 Call FSMsg() 0146 End 0147 0148 return |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||