Professional Documents
Culture Documents
by David Horowitz Did you know that you can add and remove controls from a UserForm (also known as custom dialog boxes) at run-time? This means that you can change your UserForms based on certain conditions that exist during the operation of your VBA projectyoure not limited to the design of the UserForm that you created in the VBE designer! To illustrate the technique, well create a Word VBA Template which will prompt the user for the number of cars they own. (It allows for up to 20hopefully that will be enough for most of us!) It will then display the correct number of textboxes to allow the user to enter the make of each car. When the user is done, the list of cars will be added to the document. The key thing here we want to illustrate is the part where the dialog box will actually change to display the correct number of textboxes (with accompanying labels). The primary method that accomplishes this task is Form.Controls.Add. (You can look it up in VBA Help.) Its syntax is like this: Set ctl = Me.Controls.Add(ControlClass, Name, Visible) For our purposes, ControlClass can have one of the following values: "Forms.CheckBox.1" "Forms.ComboBox.1" "Forms.CommandButton.1" "Forms.Frame.1" "Forms.Image.1" "Forms.Label.1" "Forms.ListBox.1" "Forms.MultiPage.1" "Forms.OptionButton.1" "Forms.ScrollBar.1" "Forms.SpinButton.1" "Forms.TabStrip.1" "Forms.TextBox.1" "Forms.ToggleButton.1" These are text strings, so you must enclose them in quotes. You can optionally specify the name of the new control using the Name parameter, for example, "TextBox7, "txtFirstName, "lblPrompt, or "chkChicago, whatevers appropriate for your use. Finally, you can choose to make the new control invisible by specifying False for the Visible parameter. The default is for Visible to be True. So, for example, lets say you would like to add a new label to your form. The form is named frmMyForm, 1
and the new label should have the name "lblPrompt. The method call would look like: Dim myLabel as Label Set myLabel = frmMyForm.Controls.Add _ ("Forms.Label.1", "lblPrompt") Dont get confused about myLabel and lblPrompt. myLabel is an Object variable in VBA which is now set to refer to the control on the form by the name of "lblPrompt. Once you add a control to a form, you will certainly need to set some properties on the control, at least things like Top, Left, Width and Height. You can do this right after creation if you want, using, in our example, myLabel: With myLabel .Left = 10 .Top = 10 .Width = 30 .Caption = "Enter your name:" End With Weve let the Height property default to whatever VBA sets for it initially. Now later on in your code, if you want to refer to the newly added control, but you no longer have the myLabel reference, you can either use: frmMyForm.Controls("lblPrompt) or frmMyForm!lblPrompt You cannot use the other syntax to refer to a control: frmMyform.lblPrompt when the control has been created dynamically using Controls.Add. The example which accompanies this article, Dynamic UserForms Demo.dot, is a Word template which illustrates the techniques weve just describes in greater detail and in actual usage. You can download a copy of this template demo by clicking: HERE. When you open this template, the Document_New method will call ShowCarsDialog, which will call the frmCars.Show method. frmCars has a label (lblNumCars) and textbox (txtNumCars), which prompt you to enter the number of cars you have. When you click on the Show Cars button (cmdShowCars), you will see a number of labels and textboxes equal to the number of cars you entered, allowing you to enter a make for each car. Then, when you click the Done button (cmdDone), all the car makes you entered will be inserted into the 2
document and the dialog form (frmCars) will be unloaded. If you want to change the number of cars again before you press Done, you can do that and when you click on Show Cars, the number of car labels and textboxes will change again to accommodate. So you can Add and Remove cars from the list before you click Done. When you look at the code, you will see that each car make has a label and a textbox called lblCarN and txtCarN, where N is the number of the car. Each label is added to the form using this line of code: Set theLabel = Me.Controls.Add _ ("Forms.Label.1", "lblCar" & CurrentNumberOfCars) Each textbox is added to the form using this line of code: Set theTextBox = Me.Controls.Add _ ("Forms.TextBox.1", "txtCar" & CurrentNumberOfCars) After creating each control, we set a number of properties on it. We set each labels caption using this line of code: theLabel.Caption = "Car #" & CurrentNumberOfCars & ":" You can see we even set the Accelerator property on each label to the number of the car using this line of code: theLabel.Accelerator = CurrentNumberOfCars If you tell the dialog box to remove some cars, the following lines of code are used: Me.Controls.Remove "lblCar" & CurrentNumberOfCars Me.Controls.Remove "txtCar" & CurrentNumberOfCars In this article, weve learned how to use Form.Controls.Add to add controls to a forms Controls collection at run-time. We then set properties on the control to make it look and function the way we want. We also learned how to dynamically remove a control from a forms Controls collection using Form.Controls.Remove. Now you can experiment with adding and removing controls to your forms whenever you need them. You will find many uses for this technique once youve learned how to do it.
Argument
formname controltype
Description
A string expression identifying the name of the open form or report on which you want to create the control. One of the following intrinsic constants identifying the type of control you want to create. To view these constants and paste them into your code from the Object Browser, click Object Browser on the Visual Basic toolbar, then click Access in the Project/Library box, and click AcControlType in the Classes box.
Constant
acBoundObjectFrame acCheckBox acComboBox acCommandButton acCustomControl acImage acLabel acLine acListBox acObjectFrame acOptionButton acOptionGroup acPage acPageBreak
Control
Bound object frame Check box Combo box Command button ActiveX control Image Label Line List box Unbound object frame Option button Option group Page Page break
acRectangle acSubform acTabCtl acTextBox acToggleButton section One of the following intrinsic constants identifying the section that will contain the new control. To view these constants and paste them into your code from the Object Browser, click Object Browser on the Visual Basic toolbar, then click Access in the Project/Library box, and click AcSection in the Classes box.
Constant
acDetail acHeader acFooter acPageHeader acPageFooter acGroupLevel1Header
Section
(Default) Detail section Form or report header Form or report footer Page header Page footer Group-level 1 header (reports only)
acGroupLevel1Footer
acGroupLevel2Header
acGroupLevel2Footer
If a report has additional group levels, the header/footer pairs are numbered consecutively, beginning with 9. parent A string expression identifying the name of the parent control of an attached control. For controls that have no parent control, use a zerolength string for this argument, or omit it. columnnam e The name of the field to which the control will be bound, if it is to be a data-bound control.
If you are creating a control that won't be bound to a field, use a zerolength string for this argument. left, top width, height Numeric expressions indicating the coordinates for the upper-left corner of the control in twips. Numeric expressions indicating the width and height of the control in twips.
Remarks
You can use the CreateControl and CreateReportControl methods in a custom wizard to create controls on a form or report. Both methods return a Control object. You can use the CreateControl and CreateReportControl methods only in form Design view or report Design view, respectively. You use the parent argument to identify the relationship between a main control and a subordinate control. For example, if a text box has an attached label, the text box is the main (or parent) control and the label is the subordinate (or child) control. When you create the label control, set its parent argument to a string identifying the name of the parent control. When you create the text box, set its parent argument to a zero-length string. You also set the parent argument when you create check boxes, option buttons, or toggle buttons. An option group is the parent control of any check boxes, option buttons, or toggle buttons that it contains. The only controls that can have a parent control are a label, check box, option button, or toggle button. All of these controls can also be created independently, without a parent control. Set the columnname argument according to the type of control you are creating and whether or not it will be bound to a field in a table. The controls that may be bound to a field include the text box, list box, combo box, option group, and bound object frame. Additionally, the toggle button, option button, and check box controls may be bound to a field if they are not contained in an option group. If you specify the name of a field for the columnname argument, you create a control that is bound to that field. All of the control's properties are then automatically set to the settings of any corresponding field properties. For example, the value of the control's ValidationRule property will be the same as the value of that property for the field. Note If your wizard creates controls on a new or existing form or report, it must first open the form or report in Design view. To remove a control from a form or report, use the DeleteControl and DeleteReportControl statements.
Example
The following example first creates a new form based on an Orders table. It then uses the CreateControl method to create a text box control and an attached label control on the form. Sub NewControls()
frm As Form ctlLabel As Control, ctlText As Control intDataX As Integer, intDataY As Integer intLabelX As Integer, intLabelY As Integer
' Create new form with Orders table as its record source. Set frm = CreateForm frm.RecordSource = "Orders" ' Set positioning values for new controls. intLabelX = 100 intLabelY = 100 intDataX = 1000 intDataY = 100 ' Create unbound default-size text box in detail section. Set ctlText = CreateControl(frm.Name, acTextBox, , "", "", _ intDataX, intDataY) ' Create child label control for text box. Set ctlLabel = CreateControl(frm.Name, acLabel, , _ ctlText.Name, "NewLabel", intLabelX, intLabelY) ' Restore form. DoCmd.Restore End Sub
-----------------------------------------------------------------------------------------------------------------------------
AcControlType
AcControlType can be one of these AcControlType constants. acBoundObjectFrame acCheckBox acComboBox acCommandButton acCustomControl acImage
acLabel acLine acListBox acObjectFrame acOptionButton acOptionGroup acPage acPageBreak acRectangle acSubform acTabCtl acTextBox acToggleButton
AcSection
AcSection can be one of these AcSection constants. acDetaildefault acFooter acGroupLevel1Footer acGroupLevel1Header acGroupLevel2Footer acGroupLevel2Header acHeader acPageFooter acPageHeader Parent Optional Variant. A string expression identifying the name of the parent control of an attached control. For controls that have no parent control, use a zero-length string for this argument, or omit it. ColumnName Optional Variant. The name of the field to which the control will be bound, if it is to be a data-bound control. Left, Top Optional Variant. Numeric expressions indicating the coordinates for the upper-left corner of the control in twips. Width, Height Optional Variant. Numeric expressions indicating the width and height of the control in twips
----------------------------------------------------------------------------------------------------------------------------8
Sometimes you need to create a complex UserForm with many components. I had to create a form with 3 labels, 1 text box and a check box for 43 different records (215 separate components) and so developed this technique of programatically creating the form. Obviously one can manually create a complex UserForm but the problem of individually naming each component and writing the event handler codes is a real drag. The references that need to be loaded are: Visual Basic For Applications Microsoft Excel Object Library MS Forms 2.0 Object Library The code below is based on John Walkenbach's original procedure.
CODE
Sub MakeUserForm() Dim TempForm As Object Dim NewButton As MSForms.commandbutton Dim NewLabel As MSForms.Label Dim NewTextBox As MSForms.TextBox Dim NewOptionButton As MSForms.OptionButton Dim NewCheckBox As MSForms.CheckBox Dim X As Integer Dim Line As Integer Dim MyScript(4) As String 'This is to stop screen flashing while creating form Application.VBE.MainWindow.Visible = False Set TempForm = ThisWorkbook.VBProject.VBComponents.Add(3) 'Create the User Form With TempForm .Properties("Caption") = "My User Form" .Properties("Width") = 450 .Properties("Height") = 300 End With 'Create 10 Labels For X = 0 To 9 Set NewLabel = TempForm.designer.Controls.Add("Forms.label.1") With NewLabel .Name = "FieldLabel" & X + 1 .Caption = "My Label " & X + 1 .Top = 20 + (12 * X) .Left = 6 .Width = 90 .Height = 12 .Font.Size = 7 .Font.Name = "Tahoma" .BackColor = &H80FFFF End With Next 'Create 10 Text Boxes For X = 0 To 9 Set NewTextBox = TempForm.designer.Controls.Add("Forms.textbox.1") With NewTextBox
.Name = "MyTextBox" & X + 1 .Top = 20 + (12 * X) .Left = 100 .Width = 150 .Height = 12 .Font.Size = 7 .Font.Name = "Tahoma" .BorderStyle = fmBorderStyleSingle .SpecialEffect = fmSpecialEffectFlat End With Next 'Create 10 Check Boxes For X = 0 To 9 Set NewCheckBox = TempForm.designer.Controls.Add("Forms.checkbox.1") With NewCheckBox .Name = "MyCheck" & X + 1 .Caption = "" .Top = 20 + (12 * X) .Left = 260 .Width = 12 .Height = 12 .Font.Size = 7 .Font.Name = "Tahoma" .BackColor = &HFF00& End With Next 'Create 10 Labels -> result of Check Box For X = 0 To 9 Set NewLabel = TempForm.designer.Controls.Add("Forms.label.1") With NewLabel .Name = "Result_Text" & X + 1 .Caption = "" .Top = 20 + (12 * X) .Left = 280 .Width = 150 .Height = 12 .Font.Size = 7 .Font.Name = "Tahoma" .BackColor = &H80FFFF End With Next 'Create Event Handler Code For Each Check Box '(True -> Upper Case of Text Box Value;False -> Lower Case of Text Box Value) For X = 0 To 9 With TempForm.codemodule Line = .countoflines MyScript(0) = "Sub MyCheck" & X + 1 & "_Click()" MyScript(1) = "If .MyCheck" & X + 1 & " = true then" MyScript(2) = ".result_Text" & X + 1 & ".caption = ucase(.mytextbox" & X + 1 & ".value)" MyScript(3) = ".result_Text" & X + 1 & ".caption = lcase(.mytextbox" & X + 1 & ".value)" .insertlines Line + 3, MyScript(0) .insertlines Line + 2, "With Me" .insertlines Line + 3, MyScript(1) .insertlines Line + 4, MyScript(2) .insertlines Line + 5, "Else"
10
Next
+ + + +
6, 7, 8, 9,
'Show the form VBA.UserForms.Add(TempForm.Name).Show 'Delete the form (Optional) 'ThisWorkbook.VBProject.VBComponents.Remove TempForm End Sub I tend not to delete the form but build up a series of UserForms (I refer to them as TestForms) - one generated each time the code is run. In this way I can check the results of any changes I made and use the latest TestForm to manually move components, get their new positions and modify the code accordingly. When satisfied I can rename the final TestForm to an appropriate name, add any other components such as command buttons and other event handler codes, and then delete all the remailing TestForms. This code can also be used to create a user form on the fly but command buttons and their event handlers must be added in the code.
Function
Description
Current date and time. Example: 7/5/00 3:16:38 PM returned by Now Current date only. Example: 7/5/00 returned by Date Current time only. Example: 3:12:38 PM returned by Time Number of seconds since midnight. Example: 3:16:38 PM returned by Timer See other examples below this table. Time part of argument. Example: 3:16:38 PM returned by TimeValue(Now) Date part of argument (excellent for ordering by date) Example: SELECT * from tblPeople ORDER BY DateValue(Review) Date part of three arguments: year, month, day Example: HAVING InvoiceDate <= DateSerial(Year(Now), Month(Now)-1, Day(Now)) DateSerial handles January correctly in the above example Returns a portion of the date. Year example: 2000 returned by DatePart('yyyy', Date) Month example: 10 returned by DatePart('m', #10/11/2001#) Week of year example: 41 returned by DatePart('ww', #10/11/2001#) Day of Week example: Monday returned by DatePart('dddd', #6/3/2002#) Quarter example: 4 returned by DatePart('q', #10/11/2001#) Returns the year portion of the date argument. Also see DatePart() above. Returns the month portion of the date argument. Also see DatePart() above. Returns the day portion of the date argument. Also see DatePart() above. Used to format month names. July returned by MonthName(Month(Date)) Used to format day names. Wednesday returned by WeekdayName(Weekday(Date)) Current date only; used in Excel, not available in Access Returns the difference in dates. Days example: -656 returned by DateDiff("d", #10/11/2001#, #12/25/1999#) Months example: 1 returned by DateDiff("m", #8/10/2000#, #9/14/2000#) Days example: 0 returned by DateDiff("m", date1, date2) 0 is returned above only if the two dates have same month and year
TimeValue()
DateValue()
DateSerial()
DatePart()
DateDiff()
12
Function
Description
DateAdd()
Add and subtract dates. 10/11/2002 returned by DateAdd("yyyy", 1, #10/11/2001#)) Today's date + 30 days returned by DateAdd("d", 30, Date) The date 45 days ago returned by DateAdd("d", -45, Date) To find Monday of a week: DateAdd("d",-WeekDay(Date) +2,Date) Very useful for formatting dates. Examples: Wed, 5-July-2000 returned by Format(Date,"ddd, d-mmmmyyyy") 5-Jul-00 returned by Format(Date,"d-mmm-yy")
Format()
Sub QueryTimer (strQueryName As String) Dim sngStart As Single, sngEnd As Single Dim sngElapsed As Single
' Get start time. ' Run query. ' Get end time.
13
MsgBox ("The query " & strQueryName & " took " & sngElapsed _ & " seconds to run.") End Sub
You can use any of these functions to retrieve a portion of a date value. For example, the following fragment displays the current year value: MsgBox "The current year is " & Year(Now) and the following fragment displays the month and day: MsgBox "Month: " & Month(Now) & " Day: " & Day(Now) The following fragment checks the current time and allows you to take an action at 1:12 If Hour(Time) = 13 And Minute(Time) = 12 Then
p.m.:
14
' You know it's 1:12 PM End If Don't try sending the Date function to functions that return time portions of a date/time value. Because the return value from the Date function doesn't include any time information (its fractional portion is 0), the Hour, Minute, and Second functions will all return 0. The same warning applies to the Day, Month, and Year functions: don't send them the Time function, because the return value from that function doesn't include any date information.
15
For example, the following two lines of code are equivalent: Debug.Print Day(Date) Debug.Print DatePart("d", Date) But these two lines have no equivalent alternatives: ' Return the ordinal position of the current day within the year. Debug.Print DatePart("y", Date) ' Return the quarter (1, 2, 3, or 4) containing today's date. Debug.Print DatePart("q", Date) DatePart allows you to optionally specify the first day of the week (just as you can do with the WeekDay function) in its third parameter. It also allows you to optionally specify the first week of the year in its fourth parameter. (Some countries treat the week in which January 1st falls as the first week of the year, as does the United States. Other countries treat the first four-day week as the first week, and still others wait for the first full week in the year and call that the first week.)
VBA supplies two functions, DateAdd and DateDiff, that allow you to add and subtract date and time intervals. Of course, as mentioned above, if you're just working with days, you don't need these functionsyou can just add and subtract the date values themselves. The following sections describe each of these important functions in detail.
Table 2.5: Possible Interval Settings for DateAdd Setting yyyy q m y d w ww h n s Description Year Quarter Month Day of year Day Weekday Week Hour Minute Second
For example, to find the date one year from the current date, you could use an expression like this: DateAdd("yyyy", 1, Date) rather than add 365 days to the current date (a common, although incorrect, solution). What about calculating the time two hours from now? That's easy, too: DateAdd("h", 2, Now)
17
DateAdd will never return an invalid date, but if you try to add a value that would cause the return date to be before 1/1/100 or after 12/31/9999, VBA triggers a run-time error. Watch out! The abbreviation for adding minutes to a date/time value is n, not m, as you might guess. (VBA uses m for months.) Many VBA developers have used m inadvertently and not noticed until the program was in use.
Subtracting Dates
If you need to find the number of intervals between two dates (where the interval can be any item from Table 2.5), use the DateDiff function. Table 2.6 lists the parameters for this function. Table 2.6: Parameters for the DateDiff Function Parameter Interval Date1, Date2 FirstDayOfWeek FirstWeekOfYear Required? Yes Yes No No Datatype String Date Integer constant Integer constant Description Interval of time used to calculate the difference between Date1 and Date2 The two dates used in the calculation The first day of the week. If not specified, Sunday is assumed The first week of the year. If not specified, the first week is assumed to be the week in which January 1 occurs
For example, to calculate the number of hours that occurred between two date variables, dtmValue1 and dtmValue2, you could write an expression like this: DateDiff("h", dtmValue1, dtmValue2) DateDiff's return value can be confusing. In general, it performs no rounding at all, but the meaning of the difference varies for different interval types. For example, DateDiff("h", #10:00#, #12:59:59#) returns 2 because only two full hours have elapsed between the two times. When working with months or years, DateDiff returns the number of month or year borders that have been crossed between the dates. For example, you might expect the following expression to return 0 (no full months have been traversed), yet the function returns 1 because a single month border has been crossed: DateDiff("m", #11/15/97#, #12/1/97#) The same goes for the following expression, which returns 1 even though only a single day has transpired: DateDiff("yyyy", #12/31/97#, #1/1/98#)
18
When working with weeks, DateDiff becomes, well, strange. VBA treats the w (weekday) and ww (week) intervals differently, but both return (in some sense) the number of weeks between the two dates. If you use w for the interval, VBA counts the number of the day on which Date1 falls until it hits Date2. It counts Date2 but not Date1. (This explanation requires visual aids, so consult Figure 2.1 for an example to work with.) For example, DateDiff("w", #11/5/97#, #11/18/97#) returns 1 because there's only one Wednesday following 11/5/97 before stopping at 11/18. On the other hand, DateDiff("w", #11/5/97#, #11/19/97#) returns 2 because there are two Wednesdays (11/12 and 11/19) in the range. Using ww for the range, DateDiff counts calendar weeks. (That is, every time it hits a Sunday, it bumps the count.) Therefore, the previous two examples both return 2, using the ww interval; in both cases, there are two Sundays between the two dates. Just as with the w interval, VBA counts the end date if it falls on a Sunday, but it never includes the starting date, even if it is a Sunday. Given that caveat, DateDiff should return the same answer for either the w or ww interval if Date1 is a Sunday.
Figure 2.1: A visual aid for DateDiff calculations If you use date literal values (like #5/1/97#), VBA uses the exact date in its calculations. If, on the other hand, you use a string that contains only the month and date (like 5/1), VBA inserts the current year when it runs the code. This allows you to write code that works no matter what the year. Of course, this makes it difficult to compare dates from two different years because there's no way to indicate any year except the current one. But if you need to perform a calculation comparing dates within the current year, this technique can save you time.
19
names as well. If the value you send it includes a time portion, DateValue just removes that information from the output value. For example, all of the following expressions return the same value (assuming the variable intDate contains the value 30): DateValue("12 30 97") DateValue("December 30 1997") DateValue("December " & intDate & " 1997") DateValue("12/30/97 5:00 PM") DateValue("30/12/97") The final example returns December 30 no matter where you are, of course, only because the date is unambiguous. Try that with a date like 12/1/97, and you'll get the date as defined in your international settings (December 1 in the United States, January 12 in most of the rest of the world). The TimeValue function works similarly to the DateValue function. You can send it a string containing any valid expression, and it returns a time value. If you send TimeValue a string containing date information, it disregards that information as it creates the output value. For example, all of the following return the same time value: TimeValue("5:15 PM") TimeValue("17:15") TimeValue("12/30/97 5:15 PM") The CDate function coerces any value it can get its hands on into a date/time value, if it can. Unlike the TimeValue and DateValue functions, it returns a full date/time value, with all the information it was sent intact. In addition, it can convert numeric values into dates. For example, all of the following examples return the same value. (The last example is redundant, of course, but it works.) CDate("12/30/97 5:15 PM") CDate(35794.71875) CDate(#12/30/97 5:15 PM#) Most often, you'll use CDate to convert text into a full date/time value, and you'll use DateValue and TimeValue to convert text into a date or a time value only.
20
As you'll see, this is exactly the technique the dhFirstDayInMonth function, discussed later in this chapter, uses. By creating a new date that takes the year portion of the current date, the month portion of the current date, and a day value of 1, the function returns a new date that corresponds to the first day in the current month. The TimeSerial function works just the same way. You pass it hour, minutes, and seconds values, and it creates the appropriate time value for you. You'll use both functions together to build a full date/time value if you've got six values containing the year, month, day, hour, minutes, and seconds. That is, you might find yourself with an expression like this: DateSerial(intYear, intMonth, intDay) + _ TimeSerial(intHour, intMinutes, intSeconds) Because a date/time value is simply the sum of a whole number representing days and a fraction representing time, you can use both functions together to create a full date/time value. One useful feature of VBA's built-in date functions is that they never return an invalid date. For example, asking for DateSerial(1997, 2, 35), which certainly describes a date that doesn't exist, politely returns 3/7/97. We'll actually use this feature to our benefit, as you'll see in the section Is This a Leap Year? later in this chapter.
21
Short Date format Long Time Medium Time Short Time Displays a time (no date portion) using your system's Long Time format; includes hours, minutes, seconds Displays time (no date portion) in 12-hour format using hours and minutes and the AM/PM designator Displays a time (no date portion) using the 24-hour format; for example, 17:45 Yes Yes Yes
To test out these formats, we took a field trip to a fictional country. The region's time settings for Windows are displayed in Figure 2.2, and their date settings are shown in Figure 2.3. The screen in Figure 2.4 shows some tests, using the Format function, with the various date and time formats.
22
Figure 2.3: Regional settings for dates in the same fictitious environment
Figure 2.4: Test of regional date formats in the Microsoft Access Debug window If you're feeling creative, or hampered by the limitations of the named time and date formats, you can create your own formats using the options shown in Table 2.8. If you build a string containing combinations of these characters, you can format a date/time value any way you like. Figure 2.5 demonstrates a few of the formats you can create yourself, using the characters listed in Table 2.8.
23
Figure 2.5: Use the Format function with user-defined formats for complete control. Table 2.8: User-Defined Time/Date Formats for the Format Function Character (:) Description hours, minutes, and seconds when time values are formatted (/) Date separator. Separates Yes the day, month, and year when date values are formatted c Displays the date as ddddd Yes and displays the time as ttttt, in that order d Displays the day as a number without a leading 0 (131) dd Displays the day as a number with a leading 0 (0131) ddd dddd ddddd Displays the day as an abbreviation (SunSat) Displays the day as a full name (SundaySaturday) Displays the date as a complete date (including day, month, and year) dddddd Displays a date as a Yes Same as the named Long Yes Same as the named Short Date format Yes Yes No No Use Regional Settings? Comments In some locales, this character may have been translated and may not be a colon (:). Output value is determined by local settings In some locales, this character may have been translated and may not be a slash (/). Output value is determined by local settings Same as the named General Date format
24
complete date (including day, month, and year) w Displays the day of the week as a number (1 for Sunday through 7 for Saturday) ww m Displays the week of the year as a number (154) Displays the month as a number without a leading 0 (112) mm Displays the month as a number with a leading 0 (0112) mmm mmmm Displays the month as an abbreviation (JanDec) Displays the month as a full month name (January December) q y yy yyyy h Displays the quarter of the No year as a number (14) Displays the day of the year as a number (1366) Displays the year as a two- No digit number (0099) Displays the full year (100 No 9999) Displays the hour as a number without leading zeros (023) hh Displays the hour as a number with leading zeros (0023) n Displays the minute as a number without leading zeros (059) nn Displays the minute as a number with leading zeros No No No No No Yes Yes No No No No
Date format Output depends on the setting of the FirstDayOfWeek parameter Output depends on the FirstWeekOfYear parameter If m follows h or hh, displays minutes instead If mm follows h or hh, displays minutes instead
25
(0059) s Displays the second as a number without leading zeros (059) ss Displays the second as a number with leading zeros (0059) ttttt Displays a time as a complete time (including hour, minute, and second) AM/PM Uses the 12-hour clock No Use AM for times before noon and PM for times between noon and 11:59
p.m.
No
No
Yes
am/pm
No
Use am for times before noon and pm for times between noon and 11:59
p.m.
A/P
No
Use a for times before noon and p for times between noon and 11:59
p.m.
a/p
No
Use A for times before noon and P for times between noon and 11:59
p.m.
AMPM
Uses the 12-hour clock and Yes displays the AM/PM string literal as defined by your system
If you want to include literal text in your format string, you have two choices. You can do either of the following: Precede each character with a backslash (\).
The first method becomes quite tedious and difficult to read if you have more than a few characters. The second method requires you to embed a quote inside a quoted string, and that takes some doing on its own. For example, if you want to display a date/time value like this:
26
May 22, 1997 at 12:01 AM you have two choices. With the first method, you could use a format string including \ characters: Format(#5/22/97 12:01 AM#, "mmm dd, yyyy \a\t h:mm AM/PM") Using the second method, you must embed quotes enclosing the word at into the format string. To do that, you must use two quotes where you want one in the output. VBA sees the two embedded quotes as a single literal quote character and does the right thing: Format(#5/22/97 12:01 AM#, "mmm dd, yyyy ""at"" h:mm AM/PM") Either way, the output is identical. The Turn of the Century Approacheth How does VBA handle the year 2000 issue? Actually, quite gracefully. Normally, users are accustomed to entering two-digit year values, and this, of course, is what has caused the great, late 20th century computer controversy. VBA interprets two-digit years in a somewhat rational manner: if you enter a date value with a two-digit year between 1/1/00 and 12/31/29, VBA interprets that as a date in the 21st century. If you enter a date with a two-digit year between 1/1/30 and 12/31/99, VBA interprets that as being a date in the 20th century. Although it would be nice if the interpretation had a user-configurable component, this fixed solution is much better than nothing at all. The following list summarizes how VBA treats date values entered with a two-digit year value: Date range 1/1/00 through 12/31/29: Treated as 1/1/2000 through 12/31/2029 Date range 1/1/30 through 12/31/99: Treated as 1/1/1930 through 12/31/1999
27