1. Trang chủ
  2. » Công Nghệ Thông Tin

1001 Things You Wanted To Know About Visual FoxPro phần 3 pot

33 427 1
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 33
Dung lượng 312,73 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

This code in the text box's GotFocus method allows the number to be entered correctly: WITH This *** Save the input mask .cOldInputMask = .InputMask *** Remove separators from input m

Trang 1

.Value = EVAL( cAlias + '.' + cField )

ENDIF

ELSE

*** Otherwise, save the current work area

*** before switching to the specified table

lnSelect = SELECT()

SELECT ( cAlias )

*** And locate the specified record

LOCATE FOR UPPER( ALLTRIM( EVAL (.cField ) ) ) = UPPER( lcSoFar )

At this point we have either found the desired record in cAlias or we are at the end of the

file All that remains to be done is to reset the highlighted portion of the text box correctly andrefresh the controls in the parent container (if this was specified by setting lRefreshParent =.T.):

*** If we need to refresh the parent container do it here

*** If we have refreshed the controls in the parent container,

*** there are timing issues to overcome

*** Even though SelStart and SelLength have the correct values,

*** the search box does not appear highlighted correctly without this delay

=INKEY( 1, 'H' )

ENDWITH

Notice the INKEY() command here, and take some time to read the comment above if youhaven't already This problem is not specific to our incremental search text box and timingissues like this are not uncommon in Visual FoxPro (We have also run into it when displayingmulti-select list boxes in which the previous selections are highlighted In that case, using

INKEY() in the form's refresh allows the list box to be highlighted correctly.) It is interesting tonote that the INKEY() command is not required in the code above when lRefreshParent = F.

This lends support to the assumption that this is nothing more than a timing issue The shortpause allows Visual FoxPro to catch up

Numeric text box (Example: CH04.VCX::txtNum and txtNumeric)

Visual FoxPro has inherited some serious shortcomings with respect to entering numeric datafrom its FoxPro ancestors It's not too bad when the entire field is selected, and the number isnot formatted with separators However, problems begin to occur when the insertion point is

Trang 2

not at the beginning of the displayed value Sometimes the user is trying to type the number 10,but all he can type is 1 and, with confirm set off, the value of the text box becomes 1 and thecursor moves on to the next field We have also seen the opposite problem The user wants toenter 3 but after typing 3 and exiting the control, the number 30 is displayed instead of theintended 3 So what can a Visual FoxPro developer do to help?

There are a few workarounds to this problem You could create a numeric text box toselect the entire field and remove any separators used to format the number This code in the

text box's GotFocus method allows the number to be entered correctly:

WITH This

*** Save the input mask

cOldInputMask = InputMask

*** Remove separators from input mask

InputMask = STRTRAN( cOldInputMask, ',', '' )

*** Perform Visual FoxPro native GotFocus()

TextBox::GotFocus()

*** Select the entire field

SelStart = 0

SelLength = LEN( cOldInputMask )

*** Don't let base class behavior reset SelStart/SelLength

NODEFAULT

ENDWITH

Since we need to change the text box's inputMask to accomplish this, we add a custom property called cOldInputMask to hold the original inputMask assigned to the control We will need this property in the text box's LostFocus method in order to restore the formatting like so:

This.InputMask = This.cOldInputMask

Of course, we already have a text box class that correctly selects the entire field where you

tab into it or mouse-click on it Our base class text box does this when SelectOnEntry = T So all we have to do is base our numeric text box on our base class text box, set SelectOnEntry to true, and put this code in its GotFocus method:

WITH This

*** Save the original input mask

cOldInputMask = InputMask

*** Remove separators from input mask

InputMask = STRTRAN( cOldInputMask, ',', '' )

*** Perform the parent class behavior

DODEFAULT()

ENDWITH

The numeric text box described above may be sufficient for you It's easy to create, doesn'tcontain a lot of code and works around the problems involved in entering numeric data

correctly But wouldn't it be nicer to have a numeric text box that does calculator style entry

from right to left? We have seen several examples of such text boxes and, in our opinion, theyall suffer from the same shortcoming Either the cursor can be seen flashing to the left ascharacters appear from the right or there is no cursor at all Both of these solutions tend tomake things confusing for the user So we set out to create the ultimate Visual FoxPro numeric

Trang 3

text box And we very quickly discovered why none currently exists It was HARD! So we

hope you find this useful as it is the result of entirely too many hours and too much blood,sweat, and tears Not only does it do calculator style entry, the cursor is also positioned on thecorrect character When the value in the text box is not selected, you can even delete or insertindividual digits in the middle of the number displayed in the text box

The numeric text box is a simple control to use Just drop it on a form, page or container

and set its ControlSource property That's all! You don't even need to set its InputMask unless

you want the control to be unbound because it is capable of formatting itself when bound Theway most numeric text boxes work is by changing the value into a character string,

manipulating the string and the InputMask and then re-converting the string to a numeric value.

However, our numeric text box is actually an unbound control (even though you can set it up

as if it were bound) and works because its value actually is a character string and is

manipulated as such It uses custom code to update its ControlSource with the numeric

equivalent of the character string which is its value

This example is designed to work either unbound or bound to a field in a table, cursor orview If you need to bind to a form property, the code will need a little modification to account

for it An example of how to do this can be found in the UpdateControlSource method of the spnTime class described later in this chapter.

The following, eight custom properties were added to our custom numeric text box Theyare all used internally by the control and you do not need to do anything with them explicitly

Table 4.2 Custom properties of the numeric text box

Property Description

CcontrolSource Saves the controlSource if this is a bound control before it is unbound in the Init

method Cfield Field name portion of ControlSource if it is bound

CinputMask Stores original inputMask when it is specified, otherwise stores the inputMask

constructed by the control ColdConfirm Original setting of SET( 'CONFIRM' ) saved in GotFocus so it can be restored in

LostFocus.

ColdBell Original setting of SET('BELL') saved in GotFocus so it can be restored in

LostFocus Cpoint Character returned by SET( 'POINT' )

Cseparator Character returned by SET( 'SEPARATOR' )

Ctable Table name portion of ControlSource if it is bound

LchangingFocus Flag set to suppress KEYBOARD '{END}' which is used to position the cursor at

the rightmost position in the text box If we do this when the control is losing focus,

it messes up the tab order NmaxVal Maximum value allowed in the control

The SetUp method, called by the TextBox's Init method, saves the content of the

ControlSource property to the custom cControlSource property before unbinding the control from its ControlSource It also determines, and sets up, the InputMask for the control Even

though this code is executed only once when the text box is instantiated, we have put it in acustom method to avoid coding explicitly in events whenever possible Notice that we use

Trang 4

SET( 'POINT' ) and SET( 'SEPARATOR' ) to specify the characters used as the

decimal point and separator instead of hard-coding a specific character This allows the control

to be used just as easily in Europe as it is in the United States without the necessity of

modifying code:

LOCAL laFields[1], lnElement, lnRow, lcIntegerPart, lcDecimalPart, lcMsg

WITH This

*** Save the decimal point and separator characters so we can use this

*** class in either the USA or Europe

cPoint = SET( 'POINT' )

cSeparator = SET( 'SEPARATOR' )

*** Save the controlSource

IF EMPTY( cControlSource )

.cControlSource = ControlSource

ENDIF

Next we parse the table name and field name out of the controlSource It may seem

redundant to store these two properties since they can easily be obtained by executing thissection of code However, because there are various sections of code that refer to one or theother, it's much faster to save them as localized properties when the text box is instantiated

You may wonder then why we have bothered to have a cControlSource property when we

could just as easily have referred to This.cTable + '.' + This.cField We believe this is moreself-documenting and makes the code more readable This is just as important as performanceconsiderations Be nice to the developer who inherits your work You never know when you

may wind up working for her! This code from the text box's Setup method makes its purpose

very clear:

IF ! EMPTY( cControlSource )

*** If This is a bound control, save table and field bound to

*** Parse out the name of the table if ControlSource is prefixed by an alias

IF AT( '.', cControlSource ) > 0

cTable = LEFT( cControlSource, AT( '.', cControlSource ) - 1 )

cField = SUBSTR( cControlSource, AT( '.', cControlSource ) + 1 ) ELSE

The setup routine also saves any specified InputMask to the cInputMask property If this is

a bound control, you do not need to specify an InputMask, although you can do so if you wish.

This section of the code will do it for you by getting the structure of the underlying field It

also sets the control's nMaxVal property, required during data entry to ensure the user cannot

enter a number that is too large, causing a numeric overflow error:

*** Find out how the field should be formatted if no InputMask specified

IF EMPTY(.InputMask)

AFIELDS(laFields, cTable)

lnElement = ASCAN(laFields, UPPER(.cField))

Trang 5

IF lnElement > 0

*** If the field is of integer or currency type

*** and no InputMask is specified, set it up for

*** the largest value the field will accommodate

CASE laFields[ lnRow, 2 ] = 'N'

lcIntegerPart = REPLICATE('9', laFields[lnRow, 3] – ;

laFields[lnRow, 4] - 1)

lcDecimalPart = REPLICATE('9', laFields[lnRow, 4])

cInputMask = lcIntegerPart + '.' + lcDecimalPart

nMaxVal = VAL( cInputMask )

OTHERWISE

lcMsg = IIF( INLIST( laFields[ lnRow, 2 ], 'B', 'F' ), ;

'You must specify an input mask for double and float data types', ; 'Invalid data type for this control' ) + ': ' + This.Name

MESSAGEBOX( lcMsg, 16, 'Developer Error!' )

RETURN F.

ENDCASE

ENDIF

ELSE

cInputMask = STRTRAN( InputMask, ',', '' )

nMaxVal = VAL( cInputMask )

ENDIF

ELSE

.cInputMask = STRTRAN( InputMask, ',', '' )

.nMaxVal = VAL( cInputMask )

ENDIF

Now that we have saved the Control Source to our internal cControlSource property, we can safely unbind the control We also set the lChangingFocus flag to true This ensures our

numeric text box will keep the focus if it's the first object in the tab order when SET(

'CONFIRM' ) = 'OFF' This is essential because our text box positions the cursor by using a

KEYBOARD '{END}'. This would immediately set focus to the second object in the tab orderwhen the form is instantiated because we cannot force a SET CONFIRM OFF until our text boxactually has focus:

Trang 6

cControlSource evaluates to a numeric value, the first thing we must do is convert this value to

a string We then format the string nicely with separators and position the cursor at the end ofthe string:

WITH This

*** cControlSource is numeric, so convert it to string

IF ! EMPTY ( cControlSource )

IF ! EMPTY ( EVAL( cControlSource ) )

Value = ALLTRIM( PADL ( EVAL( cControlSource ), 32 ) )

The AddSeparators method is used to display the formatted value of the text box The first

step is to calculate the length of the integer and decimal portions of the current string:

LOCAL lcInputMask, lnPointPos, lnIntLen, lnDecLen, lnCnt

*** Reset the InputMask with separators for the current value of the text box lcInputMask = ''

WITH This

*** Find the length of the integer portion of the number

lnPointPos = AT( cPoint, ALLTRIM( Value ) )

*** Find the length of the decimal portion of the number

IF AT( cPoint, cInputMask ) > 0

lnDecLen = LEN( SUBSTR( cInputMask, AT( cPoint, cInputMask ) + 1 ) ) ELSE

lnDecLen = 0

ENDIF

Once we have calculated these lengths, we can reconstruct the inputMask, inserting

commas where appropriate The easy way is to count characters beginning with the rightmostcharacter of the integer portion of the string We can then insert a comma after the format

character if the current character is in the thousands position (lnCnt = 4), the millions position (lnCnt = 7) and so on However, if the text box contains a negative value, this could possibly

result in "-,123,456" being displayed as the formatted value We check for this possibility afterthe commas are inserted:

Trang 7

*** Insert the separator at the appropriate interval

*** Make sure that negative numbers are formatted correctly

IF LEFT( ALLTRIM( Value ), 1 ) = '-'

In order for the user to enter data, the control must receive focus This requires that a

number of things be done in the GotFocus method The first is to make sure that SET ( 'CONFIRM' ) = 'ON' and that the bell is silenced, otherwise we will have problems when we

KEYBOARD '{END}' to position the cursor at the end of the field Next we have to strip the

separators out of the InputMask, and finally we want to execute the default SelectOnEntry behavior of our base class text box So the inherited 'Select on Entry' code in the GotFocus

method has to be modified to handle these additional requirements, as follows:

LOCAL lcInputMask, lnChar

*** Reset the InputMask for the current value of the text box

Trang 8

Like our incremental search text box, the numeric text box handles the keystroke in the

HandleKey method that is called from InteractiveChange after KeyPress has processed the keystroke The incremental search text box does not require any code in the KeyPress method

because all characters are potentially valid In the numeric text box, however, only a subset of

the keystrokes are valid We need to trap any illegal keystrokes in the control's KeyPress

method and when one is detected, issue a NODEFAULT to suppress the input We do this by

passing the current keystroke to the OK2Continue method If it's an invalid character, this method returns false to the KeyPress method, which issues the required NODEFAULT command:

LPARAMETERS tnKeyCode

LOCAL lcCheckVal, llretVal

llRetVal = T.

WITH This

Since the current character does not become a part of the text box's value until after the

InteractiveChange method has completed, we can prevent multiple decimal points by checking

for them here:

DO CASE

*** Make sure we only allow one decimal point in the entry

CASE CHR( tnKeyCode ) = cPoint && decimal point

IF AT( cPoint, Value ) > 0

The most complex task handled by the OK2Continue method is the check for numeric

overflow We do this by determining what the value will be if we allow the current keystroke

and compare this value to the one stored in the control's nMaxVal property:

*** Guard against numeric overflow!!!!

Trang 9

CASE SelStart = 0

lcCheckVal = CHR( tnKeyCode ) + ALLTRIM( Value )

CASE SelStart = LEN( ALLTRIM( Value ) )

lcCheckVal = ALLTRIM( Value ) + CHR( tnKeyCode )

*** Make sure that if the input mask specifies a

*** certain number of decimals, we don't allow more

*** than the number of decimal places specified

ENDIF && tnKeyCode > 47 AND tnKeyCode < 58

ENDIF && SelLength = 0

ENDIF && ! EMPTY( cInputMask )

Like our incremental search text box, a lot of work is done using a little bit of code in our

HandleKey method We can handle the positioning of the cursor and formatting of the value here because InteractiveChange will only fire after KeyPress has succeeded Therefore, handling the keystrokes here requires less code than handling them directly in KeyPress:

LOCAL lcInputMask, lnSelStart, lnEnd

*** Save the cursor's insertion point and length of the value typed in so far lnSelStart = This.SelStart

lnEnd = LEN( This.Value ) - 1

WITH This

*** Get rid of any trailing spaces so we can Right justify the value

Value = ALLTRIM(.Value)

*** We need special handling to remove the decimal point

Trang 10

If the character just entered was in the middle of the text box, we leave the cursor where itwas Otherwise we position it explicitly at the end of the value currently being entered:

Nearly there now! If this was originally a bound control, we must update the field

specified by the cControlSource property The Valid method is the appropriate place for this,

on spinner controls We feel there are actually two types of time entry that need to be

considered, and their differences require different controls

First there is the direct entry of an actual time Typically this will be used in a timerecording situation when the user needs to enter, for example, a start and a finish time for atask This is a pure data entry scenario and a text box is the best tool for the job, but there aresome issues that need to be addressed

Second there is the entry of time as an interval or setting Typically this type will be used

in a planning situation when the user needs to enter, for example, the estimated duration for atask In this case, a spinner is well suited to the task since users can easily adjust the value up

or down and can see the impact of their changes

Trang 11

A time entry text box (Example: CH04.VCX::txtTime)

The basic assumption here is that a time value will always be stored as a character string in the

form hh:mm We do not expect to handle seconds in this type of direct entry situation Actually

this is not unreasonable, since most time manipulation only requires a precision of hours andminutes This is easiest when the value is already in character form (If you truly need to enterseconds, it would be a simple matter to make this control into a subclass to handle them.) Also

we have decided to work on a 24-hour clock Again this simplifies the interface by removingthe necessity to add the familiar concept of an AM/PM designator

These decisions make the class' user interface simple to build because Visual FoxPro

provides us with both an InputMask and a Format property The former specifies how data

entered into the control should be interpreted, while the latter defines how it should be

displayed In our txtTime class (based on our txtbase class) these properties are defined as

We have some additional code in both the GotFocus and LostFocus methods to save and

restore the current setting of CONFIRM and to force it to ON while the time entry text box hasfocus While not absolutely necessary, we believe it's good practice when limiting entry lengths

to ensure that confirm is on to prevent users from inadvertently typing through the field

All of the remaining code in the class is in the Valid method of the text box and this is

where we need to address the issues alluded to above about how users will use this control Thekey issue is how to handle partial times For example, if a user enters the string: '011' do theyactually mean '01:10' (ten minutes past one in the morning) or '00:11' (eleven minutes pastmidnight)? How about an entry of '09'?

In fact there is no absolute way of knowing All we can do is define and implement somereasonable rulesfor this class as follows:

Table 4.3 Rules for entering a time value

User Enters Interpret as Result

1 A specific hour, no minutes 01:00

11 Hours only, no minutes 11:00

111 Hours and minutes, leading zero omitted 01:11

The code implementing these rules is quite straightforward:

Trang 12

LOCAL luHrs, luMins, lcTime, lnLen

*** Note: we have to assume that a user only omits leading or trailing

*** zeroes We cannot guess at the intended result otherwise!!!

*** Assume minutes are correct, hours leading zero was omitted

lcTime = PADL( lcTime, 4, '0' )

CASE lnLen = 2

*** Assume we have just got hours, no minutes

lcTime = PADR( lcTime, 4, '0' )

OTHERWISE

*** A single number must be an hour!

lcTime = "0" + lcTime + "00"

ENDCASE

*** Get the Hours and minutes components

luHrs = LEFT( lcTime, 2 )

luMins = RIGHT( lcTime, 2 )

*** Check that we have not gone over 23:59, or less than 00:00

IF ! BETWEEN( INT(VAL(luMins)), 0, 59) OR ! BETWEEN( INT(VAL(luHrs)), 0, 23) WAIT "Invalid Time Entered" WINDOW NOWAIT

A time entry composite class (Example: CH04.VCX::cntTime)

As noted in the introduction to this section, a spinner control is useful when you need to givethe user the ability to change times, as opposed to entering them directly However, onesignificant difference between using a spinner and a text box to enter time is that a spinnerrequires a numeric value This means if we still want to store our time value as a characterstring, we need to convert from character to numeric and back again For simplicity, this

control is set up to always display a time in hh:mm:ss format and expects that, if bound, it will

be bound to a Character (6) field (The purpose here is to show the basic techniques Modifyingthe control for other scenarios is left as an exercise for the reader.)

The next issue is how to get the time to be meaningful as a numeric value to display

properly Fortunately we can again make use of the Format and InputMask properties to

resolve this dilemma By setting the Spinner's InputMask = 99:99:99, and the Format = "RL"

we can display a six digit numeric value with leading zeroes (The 'L' option only works with

numeric values, so we could not use it in the preceding example.)

The final issue we need to address is how to determine which portion of our six-digitnumber will be changed when the spinner's up/down buttons are clicked The solution is tocreate a composite class that is based on a container with a spinner and a three-button optiongroup The Option group is used to determine which portion of the spinner gets incremented

Trang 13

(i.e hours, minutes or seconds) and the Spinner's UpClick and DownClick methods are coded

to act appropriately Here is the class in use:

Figure 4.2 Time Spinner Control

The time spinner's container

The container is a subclass of our standard cntBase class, with a single custom property (cControlSource) and one custom method (SetSpinValue) These handle the requirement to

convert between a character data source for our class and the numeric value required by the

spinner The cControlSource property is populated at design time with the name of the control source for the spinner Code has been added to the Refresh method of the container to call the SetSpinValue method to perform the conversion when the control is bound The Refresh code

The code in SetSpinValue is equally simple, it merely converts the control source's value to

a six-digit number padded with zeroes However, there is one gotcha here – notice the use of

the INT() function in this conversion We must ensure our numeric value is actually an integer

at all times Whether we are dealing with hours, minutes or seconds is based on the positions of

the digits in the numeric value and decimal places would interfere when using the PADx()

The time spinner's option group

This is the simplest part of the class It has no custom code whatsoever other than the change of

its name from the Visual FoxPro default to OptPick The native behavior of an option group is

to record, in the Value property of the group itself, the number of the option button selected.

Since we only need to know which button is selected, we don't need to do anything else

Trang 14

The time spinner's spinner

This is, unsurprisingly, where most of the work in the class is done Three properties have been

set – the InputMask and Format (to handle the display issues) and the Increment This has been

set to 0 to suppress the native behavior of the spinner since we need to handle the value change

in a more sophisticated manner

The GotFocus and LostFocus methods are used to turn the cursor off and back on again,

since this control is not intended for direct typing, eliminating the need to show the cursor

The Valid method handles the conversion of the spinner's numeric value back into a

character string and, if the control is bound, handles the REPLACE to update the control source.This code is also quite straightforward:

The tricky bits are handled in the UpClick and DownClick methods While this code may

look a little daunting at first glance, it's really quite straightforward and relies on interpreting

the position of the digits in the numeric value and handling them accordingly The UpClick

method checks the setting of the option group and increments the relevant portion of thenumeric value:

LOCAL lnPick, lnNewVal, lnHrs, lnMins, lnSecs

lnPick = This.Parent.optPick.Value

DO CASE

CASE lnPick = 1 && Hrs

*** Get the next Hours value

lnNewVal = This.Value + 10000

*** If 24 or more, reset to 0 by subtracting

This.Value = IIF( lnNewVal >= 240000, lnNewVal - 240000, lnNewVal )

CASE lnPick = 2 && Mins

*** Get the next value as a character string

lcNewVal = PADL(INT(This.Value) + 100, 6, '0' )

*** Extract hours as a value multiplied by 10000

lnHrs = VAL(LEFT(lcNewVal,2)) * 10000

*** Get the minutes as a character string

lnMins = SUBSTR( lcNewVal, 3, 2)

*** Check the value of this string, and either multiply up by 100 *** or, if above 59, roll it over to 00

lnMins = VAL(IIF( VAL(lnMins) > 59, "00", lnMins )) * 100

*** Extract the seconds portion

lnSecs = VAL(RIGHT(lcNewVal, 2 ))

*** Reconstruct the Numeric Value

This.Value = lnHrs + lnMins + lnSecs

CASE lnPick = 3 && Secs

*** Get the next value as a character string

lcNewVal = PADL(INT(This.Value) + 1, 6, '0' )

*** Extract hours as a value multiplied by 10000

lnHrs = VAL(LEFT(lcNewVal,2)) * 10000

*** Extract minutes as a value multiplied by 100

lnMins = VAL(SUBSTR( lcNewVal, 3, 2)) * 100

*** Get the seconds as a character string

Trang 15

lnSecs = RIGHT( lcNewVal, 2)

*** Check the value of this string,

*** If above 59, roll it over to 00

lnSecs = VAL(IIF( VAL(lnSecs) > 59, "00", lnSecs ))

*** Reconstruct the Numeric Value

This.Value = lnHrs + lnMins + lnSecs

ENDCASE

For hours the increment is 10000, for minutes it is 100 and for seconds it is just 1

The control is designed so that if the user tries to increment the hours portion above '23', itrolls over to '00' by simply subtracting the value 240000 from the spinner's new value Both theminutes and seconds are rolled over from '59' to '00', but in this case we need to actually stripout each component of the time to check the relevant portion before re-building the value byadding up the individual parts

A similar approach has been taken in the DownClick method, which decrements the

control's value In this case we need to store the current value and use it to maintain the settings

of the parts of the control that are not being affected Otherwise the principles are the same as

for the UpClick method:

LOCAL lnPick, lcNewVal, lnHrs, lnMins, lnSecs, lcOldVal

*** Get the Current value of the control as a string

lcOldVal = PADL(INT(This.Value), 6, '0' )

lnPick = This.Parent.optPick.Value

DO CASE

CASE lnPick = 1 && Hrs

*** Decrement the hours portion

lnHrs = VAL( LEFT( lcOldVal, 2 ) ) - 1

*** If it is in the desired range, use it, otherwise set to 0

lnHrs = IIF( BETWEEN(lnHrs, 0, 23), lnHrs, 23 ) * 10000

*** Extract the minutes

lnMins = VAL(SUBSTR( lcOldVal, 3, 2)) * 100

*** Extract the seconds

lnSecs = VAL(RIGHT( lcOldVal, 2))

CASE lnPick = 2 && Mins

*** Determine the new, decremented, value

lcNewVal = PADL(INT(This.Value) - 100, 6, '0' )

*** Retrieve the current Hours portion

lnHrs = VAL(LEFT(lcOldVal,2)) * 10000

*** Get the minutes portion from the new value

lnMins = VAL(SUBSTR( lcNewVal, 3, 2))

*** Check for validity with the range, set to 0 if invalid

lnMins = IIF( BETWEEN( lnMins, 0, 59), lnMins, 59 ) * 100

*** Retrieve the current Seconds portion

lnSecs = VAL(RIGHT(lcOldVal, 2 ))

CASE lnPick = 3 && Secs

*** Determine the new, decremented, value

lcNewVal = PADL(INT(This.Value) - 1, 6, '0' )

*** Retrieve the current Hours portion

lnHrs = VAL(LEFT(lcOldVal,2)) * 10000

*** Retrieve the current Minutes portion

lnMins = VAL(SUBSTR( lcOldVal, 3, 2)) * 100

*** Get the Seconds portion from the new value

lnSecs = VAL(RIGHT( lcNewVal, 2))

*** Check for validity with the range, set to 0 if invalid

lnSecs = IIF( BETWEEN(lnSecs, 0, 59), lnSecs, 59 )

Trang 16

*** Set the Value to the new, decremented, result

This.Value = lnHrs + lnMins + lnSecs

Conclusion

This time spinner is somewhat restricted in its ability to handle more than the simple

environment that we defined for it While clear to the end user, the mechanism for selectingwhich part of the time needs to be incremented or decremented is a bit cumbersome A moreelegant solution is offered in the final control in this section

The true time spinner (Example: CH04.VCX::spnTime)

This control looks and acts just like the time spinner you see when using the Date/Time PickerActiveX control that ships with Visual Studio However, it has several features that make itmore generally useful than the ActiveX control First, this control does not require that it bebound to a field of the DateTime data type As we have said, the best format for storing user

entered time is in a character field formatted to include the universal ':' time separator The

control can be configured to display and recognize either a 5-character 'hh:mm' format or a full 8-character 'hh:mm:ss' format and update either appropriately This is controlled by a single property, lShowSeconds Finally, because this is a native Visual FoxPro control it does not

suffer from the inherent problems that haunt ActiveX controls with respect to the multipleversions of specific windows DLLs, nor are there any problems associated with registering thecontrol

As with the container-based spinner, this control uses a cControlSource property for binding to an external data source and has associated methods (RefreshSpinner and

UpdateControlSource) to handle the issue of converting between character (source) and

numeric (internal) values and back Unlike the container-based spinner, this control can also

handle being bound to a form property, as well as to a data source The UpdateControlSource method, called from the spinner's Valid method, allows the time spinner, which is really an unbound control, to behave just like one that is bound when its cControlSource property is set:

LOCAL lcTable, lcField, lcValue, lcTemp

lcTable = LEFT( cControlSource, AT( '.', cControlSource ) - 1 )

lcField = SUBSTR( cControlSource, AT( '.', cControlSource ) + 1 ) ELSE

*** Assume the alias is the current selected alias if none is specified *** This is a little dangerous, but if it is a bad assumption, the

*** program will blow up very quickly in development mode giving

*** the developer a very clear indication of what is wrong once he checks *** out the problem in the debugger.

lcTable = ALIAS()

lcField = cControlSource

ENDIF

Ngày đăng: 05/08/2014, 10:20

TỪ KHÓA LIÊN QUAN