By Captain C | Thursday 29 July 2010 19:52 | 0 Comments
One issue that recently reared it's head again at Sprezz Towers Support Central was the problem of using the Basic+ Rnd() function to generate a random number sequence - apparently the same series of numbers was being produced every time.

On first investigation it appeared that the customer was not using the InitRnd() function to seed the Rnd() generator before asking for the random numbers. This is a documented requirement - Calling InitRnd() is an absolute necessity before using the Rnd() function.

Even so, using InitRnd() as specified in the online help failed to produce sufficiently random results so we decided to take a closer look at the process to see what the problem was and how we could improve it.

Under the hood the Basic+ InitRnd() and Rnd() functions are little more than thin wrappers around the srand() and rand() functions in the Windows C-runtime library. The rand() function actually produces a pseudorandom sequence of numbers, so it is not truly random at all - instead the srand() function is used to seed the starting point for the pseudorandom sequence so it begins in a different place after each initialisation. Also, if srand() is called again with the same seed the same sequence of numbers is generated by rand()!

Going back to the problem, the OpenInsight online help gives an example of using InitRnd() with a seed based on the current time and date (which is what our customer tried next):

0001  /*
0002     The present time and date set the random number
0003     generator with a unique value.
0004  */
0005
0006     InitRnd Time(): Date()

Unfortunately for this example using the date results in a seed value that doesn't show much variance in it's low order value (it only increments by one per day), and this appears to have the effect of generating a identical Rnd() sequence between each initialisation (especially if called multiple times on the same day which is something that should be avoided). We then thought about swapping the date and time around so that the low order value would hopefully be more varied, but here we find ourselves in danger of generating predictable sequences if we started our application at the same time every day (like at 9:15am for example).

What we needed was some way to fulfil the following criteria:

• Ensure that InitRnd() is only called once per session to avoid the same seed being used more than once.
• Provide InitRnd with a varied seed value that is less predictable and has a greater "spread" than a simple date/time value.

We quickly put together the following function to do this that gave much better results:

0001  subroutine zz_InitRnd( bReset )
0002  /*
0003     Author  : Mr C, Sprezzatura Ltd
0004     Date    : 29 July 2010
0005     Purpose : Simple function to call initRnd with with a more varied
0006               seed value, and to also ensure we only call it once per
0007               session.
0008
0009     Parameters
0010     ==========
0011
0012     bReset  : If TRUE\$ then call initRnd regardless of whether or
0013               not it's been called before.
0014
0015  */
0016     declare function getTickCount, getEngineWindow
0017     \$insert logical
0018
0019     if assigned( bReset ) else bReset = ""
0020
0021     common /%%_ZZ_INITRND_%%/ zzInitRndSeed@
0022
0023     * // Check to see if we've already been called this session.
0024     if zzInitRndSeed@ then
0025        * // We've called it already - are we asking for a reset?
0026        if bReset else
0027           * // Nope!
0028           return
0029        end
0030     end
0031
0032     * // Start off with the tickcount (ms) and the
0033     * // OE hwnd ...
0034     seed = getTickCount() + getEngineWindow()
0035
0036     * // Now adjust this slightly to help avoid the time
0037     * // factor
0038     if mod( seed, 2 ) then
0039        b = seed[-1,1]
0040        if b then
0041           seed = int( seed / b )
0042         end else
0043           seed = int( seed / 3 )
0044        end
0045     end
0046
0047     * // Right - run it through some more mods to keep it
0048     * // within range of a short int if it's not already...
0049     loop
0050     while ( seed > 0x7FFF )
0051        if mod( seed, 2 ) then
0052           seed -= 0x7FFF
0053        end else
0054           b = seed[-1,1]
0055           if b then
0056              seed = int( seed / b )
0057           end else
0058              seed = int( seed / 3 )
0059           end
0060        end
0061     repeat
0062
0063     initRnd seed
0064
0065     * // Flag that we've already called this...
0066     zzInitRndSeed@ = seed
0067
0068  return

(Note that we have a global variable in place to ensure that initRnd is only called once per session, unless we explicitly override this)

You can download a text version of zz_InitRnd here.

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Labels: ,

By Captain C | Monday 12 July 2010 11:16 | 0 Comments
Following on from our recent post on how to remove the row-selection from an EditTable we came across a related issue last week that we think you might like to know about.

Basically we designed a form with several EditTables, all row-select enabled, but during testing it became difficult to know where the actual input focus was as so many controls had a selection highlighted. We could have used the technique in the aforementioned blog post to clear the row-selection during the LOSTFOCUS event, but in this case we still needed to see which row was selected even though the focus was on another control.

The solution we used was to change the highlight color of the EditTable during the LOSTFOCUS event, toning it down to a lighter shade than normal. We then reset it during the GOTFOCUS event. This is actually quite easy to achieve with the COLOR_BY_POS message, the only real challenge is to devise an algorithm to fade the highlight color.

As usual we've saved you the trouble - here's small function that you can use as a starting point for your own application should you wish to use a similar technique:

0002  /*
0003     Author   : Darth C, Sprezzatura Actual
0004     Date     : 12 Jul 2010
0005     Purpose  : Function to adjust the row selection color of an edit table
0006
0007     Parameters
0008     ==========
0009
0010       edtID    -> Fully qualified name of the edit table
0011
0012       bFade    -> If TRUE then fade the selection color, otherwise
0013                   reset it to normal
0014
0015     Requirements
0016     ============
0017
0018  */
0019     declare function rgb
0020     \$insert winAPI_EditTable_Equates
0021     \$insert winAPI_SysColor_Equates
0022     \$insert logical
0023
0024     if assigned( edtID ) else edtID = ""
0025     if assigned( bFade ) else bFade = FALSE\$
0026
0027     if len( edtID ) then
0028        if ( bFade ) then
0030        end else
0031           goSub resetSelectionColor
0032        end
0033     end
0034
0035  return
0036
0037  ///////////////////////////////////////////////////////////////////////////////
0038  ///////////////////////////////////////////////////////////////////////////////
0039
0041
0042     origColor =        winAPI_GetSysColor( SYSCOLOR_HIGHLIGHT\$ )
0043
0044     origColor = fmt( oconv( origColor, "MB" ), "R(0)#32" )
0045     bleach    = 185 ; * // this is how much white we want to add...(0..255)
0046
0047     selColor =        iconv( origColor[25,8], "MB" )
0048     selColor := @fm : iconv( origColor[17,8], "MB" )
0049     selColor := @fm : iconv( origColor[9,8], "MB" )
0050
0051     * // Now add the bleach to each component
0052     for x = 1 to 3
0053        pct = ( 1 - ( selColor<x>/255 ) )
0054        selColor<x> = selColor<x> + int( pct * bleach )
0055     next
0056
0057     selColor = rgb( selColor<1>, selColor<2>, selColor<3> )
0058     txtColor = winAPI_GetSysColor( SYSCOLOR_WINDOWTEXT\$ )
0059     if txtColor else
0060        * // 0 (BLACK) means "default color" in COLOR_BY_POS processing!!!
0061        txtColor += 1
0062     end
0063
0064     dtcs =       DT_DEFAULTCOLOR\$                                           |
0065          : @fm : DT_DEFAULTCOLOR\$                                           |
0066          : @fm : selColor                                                   |
0067          : @fm : txtColor
0068
0069     call send_Message( edtID, "COLOR_BY_POS", 0, 0, dtcs )
0070
0071  return
0072
0073  ///////////////////////////////////////////////////////////////////////////////
0074
0075  resetSelectionColor:
0076
0077     dtcs =       DT_DEFAULTCOLOR\$                                           |
0078          : @fm : DT_DEFAULTCOLOR\$                                           |
0079          : @fm : DT_DEFAULTCOLOR\$                                           |
0080          : @fm : DT_DEFAULTCOLOR\$
0081
0082     call send_Message( edtID, "COLOR_BY_POS", 0, 0, dtcs )
0083
0084  return
0085
0086  ///////////////////////////////////////////////////////////////////////////////
0087  ///////////////////////////////////////////////////////////////////////////////

In your EditTable LOSTFOCUS event you call this:

call edt_FadeSelection( @window : ".TABLE_1", TRUE\$ )

And to reset the colors in your EditTable GOTFOCUS event you call this:

call edt_FadeSelection( @window : ".TABLE_1", FALSE\$ )

(You'll notice that the function uses two "WinAPI" \$insert records, both of which can be found the WinAPI Library that we recently posted.)

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

By Captain C | 11:14 | 0 Comments
There's a quick way to select or deselect all rows in a multi-select edit table, and that's via the DTM_SELALLROWS message. It takes a single wParam argument which is TRUE\$ to select all rows, or FALSE\$ to deselect them.

Here's an example:

\$insert logical
equ DTM_SELALLROWS\$ to 1085 ; * // ( WM_USER + 61 )

hwndEdt = get_Property( @window : ".TABLE_1", "HANDLE" )

* // Select all rows....
call sendMessage( hwndEdt, DTM_SELALLROWS\$, TRUE\$, 0 )

* // Deselect all rows....
call sendMessage( hwndEdt, DTM_SELALLROWS\$, FALSE\$, 0 )

Previous Posts
Archives
BlogRoll

Subscribe to
Posts [Atom]

 Making Databases Happen UK +44 (0) 20 8912 1010 USA +1 267 238 3814