![]() |
|
Published 1998-11-01 Printer-friendly version
I use Quicken for my personal accounting, and I've always liked the hot keys for quickly changing the dates. I figured that this would be a great situation for a global template, utilizing some type of support library or class. The Quicken hotkeys are as follows:
T - Today
Plus - Tomorrow (current value plus one)
Minus - Yesterday (current value minus one)
M (as in Month) - The beginning of the current value's month. If the current value is already the first day of the month, then it goes to the beginning of the previous month.
H (as in montH) - The end of the current value's month. If the current value is already the last day of the month, then it goes to the end of the next month.
Y (as in Year) - The beginning of the current value's year. If the current value is already the first day of the year, then it goes to the beginning of the previous year.
R (as in yeaR) - The end of the current value's year. If the current value is already the last day of the year, then it goes to the end of the next year.
To achieve this feat, we have to alert each of these keys for all date controls (ENTRY or SPIN) throughout the application. Once the alerts have been added, we have to watch for the alerted keys being pressed, and to respond accordingly.
We'll write a class to handle the drudgery, reducing the generated source required in each procedure to approximately two lines (Init and TakeEvent).
I decided to use a Class rather than a library, because of the ABC template's built-in support for utilizing ABC compliant classes. If I wanted to use a library, I would be stuck with determining the best way to include the necessary code in the various MAP sections. As everyone likes to point out: Why reinvent the wheel?
The declaration of the class is wonderfully simple:
!ABCIncludeFile
OMIT('_EndOfInclude_', _MhAbQuickenDatePresent_)
_MhAbQuickenDatePresent_ EQUATE(1)
MH::QuickenDateClass CLASS, TYPE, |
MODULE('MHABQDTE.CLW'),
LINK('MHABQDTE.CLW',|
_ABCLinkMode_),|
DLL(_ABCDllMode_)
Init PROCEDURE
TakeEvent PROCEDURE,BYTE,PROC
END!CLASS
_EndOfInclude_
As usual, an ABC-compliant INC file must start with !ABCIncludeFile. This instructs the class header checker to include our class in its scan. The next couple of lines, along with the last line, are to ensure that the header is included only once within a given scope.
The interesting thing to note here is that our class encapsulates no data. As I mentioned earlier, I could have made this into a library rather than a class; the lack of class data makes this possible. Alternatively, if each usage of the functions requires persistent data attached to that usage, then the need for an OOP-based implementation would have been more obvious. I might want to add some member data later, though, so it's probably best to use the OOP approach from the start.
As mentioned earlier, there are only two methods: Init and TakeEvent.
Support Procedures
To support our class' efforts, I've created various local procedures in the class module. I could have included these as "PRIVATE" method in my class, but I decided to do it this way instead. The main reason is that the support procedures don't need to access class data. (Considering there isn't any class data, this kind of makes sense <grin>.) The start of our module looks like this:
MEMBER
INCLUDE('MHAbQDte.INC')
INCLUDE('EQUATES.CLW')
INCLUDE('KEYCODES.CLW')
INCLUDE('ABERROR.INC')
MAP
IsDateEntryControl(LONG Control),BYTE
ChangeDate(LONG Control, |
LONG D),BYTE
ChangeDate(LONG Control, |
BYTE M, BYTE D, |
SHORT Y),BYTE
END!
The module needs its include file, along with the basic Clarion keycodes and equates. It also uses some of the error equates (e.g. Level:Benign), so those are also included. (The class doesn't, however, make use of the error class itself.)
The support procedures include IsDateEntryControl, which checks to see if a given control is a candidate for Quicken hotkeys, and the two ChangeDate procedures, which change the value of the date in the control.
If you are wondering why there are two ChangeDate procedures (and how this is even possible), this is an example of "overloading". Note that each procedure has a different parameter list, so that the compiler can determine which procedure we are calling. I have two of them because sometimes I want to change the date and I know the exact value, and sometimes I known the individual MM/DD/YYYY elements. To save myself the trouble of always using the DATE function, I decided to create an extra version of ChangeDate to deal with it for me. (Call me lazy.)
The IsDateEntryControl function looks like this:
IsDateEntryControl FUNCTION(LONG Control)
CODE
IF Control
CASE Control{PROP:Type}
OF CREATE:Entry OROF CREATE:Spin
IF UPPER(SUB(Control{PROP:Text}, 1, 1)) = 'D'
IF ~Control{PROP:ReadOnly}
RETURN True
END!IF
END!IF
END!CASE
END!IF
RETURN False
As you can see, it must be an ENTRY or SPIN control with an @D picture, and it must not have the READONLY attribute. If all of these conditions are true, then the function returns True. Otherwise, it drops through to the bottom and returns False.
The ChangeDate functions look like this:
ChangeDate FUNCTION(LONG Control, LONG D)
CODE
CHANGE(Control, D)
RETURN Level:Notify
ChangeDate FUNCTION(LONG Control, |
BYTE M, BYTE D, |
SHORT Y)
CODE
RETURN ChangeDate(Control, DATE(M, D, Y))
Notice that the first one is responsible for changing the value of the date and displaying the result. It does both of these with one CHANGE() command. At this point the function always returns Level:Notify, to indicate that something has happened. In the future I may decide to change this behavior, so I've coded it this way for now. The second version of ChangeDate simply uses the DATE function to pass a value to the other ChangeDate.
Class Methods
The Init method is straightforward:
MH::QuickenDateClass.Init PROCEDURE
C LONG,AUTO
CODE
LOOP C = FIRSTFIELD() TO LASTFIELD()
IF IsDateEntryControl(C)
C{PROP:Alrt,255} = PlusKey
C{PROP:Alrt,255} = MinusKey
C{PROP:Alrt,255} = TKey
C{PROP:Alrt,255} = MKey
C{PROP:Alrt,255} = HKey
C{PROP:Alrt,255} = YKey
C{PROP:Alrt,255} = RKey
END!IF
END!LOOP
It simply loops through all of the controls on the window, checking if each of them is an applicable date entry control. When it finds one, it alerts all of the necessary keys. PROP:Alrt is an array, and notice that each PROP:Alrt assignment uses element number 255. If you don't know how many elements are already present, you can use a high number to drop the new alert key on top of any existing ones.
The TakeEvent method is a little stranger:
MH::QuickenDateClass.TakeEvent FUNCTION
C LONG,AUTO
D LONG,AUTO
M BYTE,AUTO
Y SHORT,AUTO
R BYTE(Level:Benign)
CODE
IF EVENT() = EVENT:AlertKey AND FIELD()
C = FIELD()
IF IsDateEntryControl(C)
D = CONTENTS(C)
CASE KEYCODE()
OF PlusKey
R = ChangeDate(C, D+1)
OF MinusKey
R = ChangeDate(C, D-1)
OF TKey
R = ChangeDate(C, TODAY())
OF MKey
R = ChangeDate(C, |
MONTH(D) + |
CHOOSE(DAY(D)=1,11,12), |
1, |
YEAR(D)-1)
OF HKey
R = ChangeDate(C, |
DATE(MONTH(D) + |
CHOOSE(DAY(D+1)=1, 2, 1), |
1, |
YEAR(D)) - 1)
OF YKey
R = ChangeDate(C, 1, 1, |
YEAR(D) - |
CHOOSE(DAY(D)=1 |
AND MONTH(D)=1, 1, 0))
OF RKey
R = ChangeDate(C, 12, 31, |
YEAR(D) + CHOOSE(DAY(D)=31 |
AND MONTH(D)=12, 1, 0))
END!CASE
END!IF
END!IF
RETURN R
First it checks that EVENT:AlertKey has occurred, and that the alert key belongs to a FIELD() (rather than the Window). If the field is an appropriate date entry control, then it proceeds to determine the required action, if any.
If it's PlusKey, MinusKey, or TKey, then the action is quite simple: change the date to the new value. Handling YKey and RKey is similar, because the first day of the year is always 1/1 and the last is always 12/31. The only difference is that we're using the alternate form of ChangeDate. The calculations for the beginning and end of the month, however, are much harder. Here are the tricks that I used:
The Template
We're going to use a global extension template, so that we can add to once to the application to get complete coverage. Remember from my prior articles that a global template can place code into the embeds of all procedures. Here's the first line of the template:
#EXTENSION(mhQuickenDate,'Quicken Date Entry'),APPLICATION
The APPLICATION attribute indicates that the extension can only be added to the global extensions (contrasted with the PROCEDURE attribute). There are no prompts to enter, as the actions of the template are consistent and well defined. Next we have to place our code at the various embed points. First we ensure that the ABC include files have been scanned:
#ATSTART #CALL(%ReadABCFiles(ABC)) #ENDAT
If the window for the current procedure contains at least one date entry control, then the MH::QuickenDateClass class header file must be included in the module:
#AT(%GatherSymbols),WHERE(%HasQuickenDateControl())
#CALL(%AddModuleIncludeFile(ABC),
'MH::QuickenDateClass')
#ENDAT
If there is a date entry control, then the class is added to the procedure's local data section:
#AT(%LocalDataClasses), WHERE(%HasQuickenDateControl())
mhQuickenDate MH::QuickenDateClass
#ENDAT
After the window is opened, the controls are initialized with the PROP:Alrt keys:
#AT(%WindowManagerMethodCodeSection, 'Init', '(),BYTE'),|
WHERE(%HasQuickenDateControl()),
PRIORITY(8050)
mhQuickenDate.Init
#ENDAT
Finally, the events are monitored for handling Quicken date hotkeys.
#AT(%WindowManagerMethodCodeSection, 'TakeEvent',|
'(),BYTE'), |
WHERE(%HasQuickenDateControl()), |
PRIORITY(1300)
ReturnValue = mhQuickenDate.TakeEvent()
IF ReturnValue THEN RETURN ReturnValue.
#ENDAT
Most of these embeds apply only WHERE(%HasQuickenDateControl()). This template group looks like this:
#GROUP(%HasQuickenDateControl),AUTO
#EQUATE(%RetVal, %False)
#FOR(%Control)
#CASE(%ControlType)
#OF('ENTRY')
#OROF('SPIN')
#IF(UPPER(SUB(EXTRACT(%ControlStatement,|
%ControlType,1),2,1)) =
'D')
#SET(%RetVal, %True)
#BREAK
#ENDIF
#ENDCASE
#ENDFOR
#RETURN(%RetVal)
The AUTO attribute indicates that the group will be declaring one or more new symbols, but those symbols are no longer needed when the group has finished. In this case, we are creating a %RetVal and initializing it to %False. Note the similarity between this #GROUP and IsDateEntryControl in the Class module. They both perform the same function.
Conclusion
Well there you have it. With the addition of a single global extension, your entire application has really cool Quicken-style date entry hot fields. Not only that, but now you should be even more comfortable with classes, templates, and alert key processing, and date manipulation. For the complete source code to this and many other useful and educational templates and classes, go to my web site at www.BoxsoftDevelopment.com and download the free MHAB*.EXE.
|
Posted on Saturday, June 09, 2001 by Carl Barnes Another article on Quicken Style Dates! But can the author cook?
|
To add a comment to this article you must log in.
Copyright © 1999-2009 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $169
(includes all back issues since '99)
Renewals from $119
Two years: $269
Renewals from $219