[英]How can I create a calendar input in VBA Excel?
Problem Statement问题陈述
In VBA, three main kinds of date time controls can be used provided certain ocxs have been registered using administrator rights .在 VBA 中,如果某些 ocx 已使用管理员权限注册,则可以使用三种主要类型的日期时间控件。 These are VB6 controls and are not native to VBA environment.
这些是 VB6 控件并且不是 VBA 环境的原生控件。 To install the Montview Control and Datetime Picker , we need to set a reference to Microsoft MonthView Control 6.0 (SP4) which can only be accessed by elevated registration of mscomct2.ocx .
要安装Montview Control和Datetime Picker ,我们需要设置对Microsoft MonthView Control 6.0 (SP4)的引用,它只能通过mscomct2.ocx的提升注册访问。 Similarly for mscal.ocx and mscomctl.ocx .
mscal.ocx和mscomctl.ocx 也是如此。 Having said that, the deprecated mscal.ocx may or may not work on Windows 10.
话虽如此,已弃用的 mscal.ocx可能会或可能不会在 Windows 10 上运行。
Depending on your Windows and Office versions (32 bit or 64 bit), it can be really painful to register these ocxs.根据您的 Windows 和 Office 版本(32 位或 64 位),注册这些 ocx 可能非常痛苦。
The Monthview Control , Datetime Picker and the deprecated Calendar control look like below. Monthview Control 、 Datetime Picker和已弃用的 Calendar 控件如下所示。
So what problem can I face if I include these in my applicaiton?那么,如果我将这些包含在我的应用程序中,我会面临什么问题?
If you include them in your project and distribute them to your friends, neighbours, clients etc the application may or may not work depending on whether they have those ocx installed.如果您将它们包含在您的项目中并将它们分发给您的朋友、邻居、客户等,则该应用程序可能会或可能不会工作,具体取决于他们是否安装了这些 ocx。
And hence it is highly advisable NOT to use them in your project因此,强烈建议不要在您的项目中使用它们
What alternative(s) do I have?我有什么选择?
This calendar, using Userform and Worksheet , was suggested earlier and is incredibly basic.这个使用 Userform 和 Worksheet 的日历是之前建议的,并且非常基础。
When I saw the Windows 10 calendar which popped up when I clicked on the date and time from the system tray, I could not help but wonder if we can replicate that in VBA.当我看到当我点击系统托盘中的日期和时间时弹出的 Windows 10 日历时,我不禁想知道我们是否可以在 VBA 中复制它。
This post is about how to create a calendar widget which is not dependant on any ocx or 32bit/64bit and can be freely distributed with your project.这篇文章是关于如何创建一个日历小部件,它不依赖于任何 ocx 或 32 位/64 位,并且可以随您的项目自由分发。
This is what the calendar looks like in Windows 10:这是日历在 Windows 10 中的样子:
and this is how you interact with it:这就是你与它互动的方式:
The sample file (added at the end of the post) has a Userform, Module and a Class Module.示例文件(在文章末尾添加)具有用户窗体、模块和类模块。 To incorporate this into your project, simply export the Userform, Module and the Class Module from the sample file and import it into your project.
要将其合并到您的项目中,只需从示例文件中导出用户窗体、模块和类模块并将其导入到您的项目中。
Class Module Code类模块代码
In the Class Module (Let's call it CalendarClass
) paste this code在类模块(我们称之为
CalendarClass
)中粘贴此代码
Public WithEvents CommandButtonEvents As MSForms.CommandButton
'~~> Unload the form when the user presses Escape
Private Sub CommandButtonEvents_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If Not f Is Nothing Then If KeyAscii = 27 Then Unload f
End Sub
'~~> This section delas with showing/displaying controls
'~~> and updating different labels
Private Sub CommandButtonEvents_Click()
f.Label6.Caption = CommandButtonEvents.Tag
If Left(CommandButtonEvents.Name, 1) = "Y" Then
If Len(Trim(CommandButtonEvents.Caption)) <> 0 Then
CurYear = Val(CommandButtonEvents.Caption)
With f
.HideAllControls
.ShowMonthControls
.Label4.Caption = CurYear
.Label5.Caption = 2
.CommandButton1.Visible = False
.CommandButton2.Visible = False
End With
End If
ElseIf Left(CommandButtonEvents.Name, 1) = "M" Then
Select Case UCase(CommandButtonEvents.Caption)
Case "JAN": CurMonth = 1
Case "FEB": CurMonth = 2
Case "MAR": CurMonth = 3
Case "APR": CurMonth = 4
Case "MAY": CurMonth = 5
Case "JUN": CurMonth = 6
Case "JUL": CurMonth = 7
Case "AUG": CurMonth = 8
Case "SEP": CurMonth = 9
Case "OCT": CurMonth = 10
Case "NOV": CurMonth = 11
Case "DEC": CurMonth = 12
End Select
f.HideAllControls
f.ShowSpecificMonth
End If
End Sub
Module Code模块代码
In the Module (Let's call it CalendarModule
) paste this code在模块中(我们称之为
CalendarModule
)粘贴此代码
Option Explicit
Public Const GWL_STYLE = -16
Public Const WS_CAPTION = &HC00000
#If VBA7 Then
#If Win64 Then
Public Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias _
"GetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
Public Declare PtrSafe Function SetWindowLongPtr Lib "user32" Alias _
"SetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr) As LongPtr
#Else
Public Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias _
"GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
Private Declare Function SetWindowLongPtr Lib "user32" Alias _
"SetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, _
ByVal dwNewLong As LongPtr) As LongPtr
#End If
Public Declare PtrSafe Function DrawMenuBar Lib "user32" _
(ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function SetTimer Lib "user32" _
(ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, _
ByVal uElapse As LongPtr, ByVal lpTimerFunc As LongPtr) As LongPtr
Public Declare PtrSafe Function KillTimer Lib "user32" _
(ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As LongPtr
Public TimerID As LongPtr
Dim lngWindow As LongPtr, lFrmHdl As LongPtr
#Else
Public Declare Function GetWindowLong _
Lib "user32" Alias "GetWindowLongA" ( _
ByVal hwnd As Long, ByVal nIndex As Long) As Long
Public Declare Function SetWindowLong _
Lib "user32" Alias "SetWindowLongA" ( _
ByVal hwnd As Long, ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
Public Declare Function DrawMenuBar _
Lib "user32" (ByVal hwnd As Long) As Long
Public Declare Function FindWindowA _
Lib "user32" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Public Declare Function SetTimer Lib "user32" ( _
ByVal hwnd As Long, ByVal nIDEvent As Long, _
ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
Public TimerID As Long
Dim lngWindow As Long, lFrmHdl As Long
#End If
Public TimerSeconds As Single, tim As Boolean
Public CurMonth As Integer, CurYear As Integer
Public frmYr As Integer, ToYr As Integer
Public f As frmCalendar
Enum CalendarThemes
Venom = 0
MartianRed = 1
ArcticBlue = 2
Greyscale = 3
End Enum
Sub Launch()
Set f = frmCalendar
With f
.Caltheme = Greyscale
.LongDateFormat = "dddd dd. mmmm yyyy" '"dddd mmmm dd, yyyy" etc
.ShortDateFormat = "dd/mm/yyyy" '"mm/dd/yyyy" or "d/m/y" etc
.Show
End With
End Sub
'~~> Hide the title bar of the userform
Sub HideTitleBar(frm As Object)
#If VBA7 Then
Dim lngWindow As LongPtr, lFrmHdl As LongPtr
lFrmHdl = FindWindow(vbNullString, frm.Caption)
lngWindow = GetWindowLongPtr(lFrmHdl, GWL_STYLE)
lngWindow = lngWindow And (Not WS_CAPTION)
Call SetWindowLongPtr(lFrmHdl, GWL_STYLE, lngWindow)
Call DrawMenuBar(lFrmHdl)
#Else
Dim lngWindow As Long, lFrmHdl As Long
lFrmHdl = FindWindow(vbNullString, frm.Caption)
lngWindow = GetWindowLong(lFrmHdl, GWL_STYLE)
lngWindow = lngWindow And (Not WS_CAPTION)
Call SetWindowLong(lFrmHdl, GWL_STYLE, lngWindow)
Call DrawMenuBar(lFrmHdl)
#End If
End Sub
'~~> Start Timer
Sub StartTimer()
'~~ Set the timer for 1 second
TimerSeconds = 1
TimerID = SetTimer(0&, 0&, TimerSeconds * 1000&, AddressOf TimerProc)
End Sub
'~~> End Timer
Sub EndTimer()
On Error Resume Next
KillTimer 0&, TimerID
End Sub
'~~> Update Time
#If VBA7 And Win64 Then ' 64 bit Excel under 64-bit windows ' Use LongLong and LongPtr
Public Sub TimerProc(ByVal hwnd As LongPtr, ByVal uMsg As LongLong, _
ByVal nIDEvent As LongPtr, ByVal dwTimer As LongLong)
frmCalendar.Label1.Caption = Split(Format(Time, "h:mm:ss AM/PM"))(0)
frmCalendar.Label2.Caption = Split(Format(Time, "h:mm:ss AM/PM"))(1)
End Sub
#ElseIf VBA7 Then ' 64 bit Excel in all environments
Public Sub TimerProc(ByVal hwnd As LongPtr, ByVal uMsg As Long, _
ByVal nIDEvent As LongPtr, ByVal dwTimer As Long)
frmCalendar.Label1.Caption = Split(Format(Time, "h:mm:ss AM/PM"))(0)
frmCalendar.Label2.Caption = Split(Format(Time, "h:mm:ss AM/PM"))(1)
End Sub
#Else ' 32 bit Excel
Public Sub TimerProc(ByVal hwnd As Long, ByVal uMsg As Long, _
ByVal nIDEvent As Long, ByVal dwTimer As Long)
frmCalendar.Label1.Caption = Split(Format(Time, "h:mm:ss AM/PM"))(0)
frmCalendar.Label2.Caption = Split(Format(Time, "h:mm:ss AM/PM"))(1)
End Sub
#End If
'~~> Improvement suggested by T.M (https://stackoverflow.com/users/6460297/t-m)
'(1) Get weekday name
Function wday(ByVal wd&, ByVal lang As String) As String
' Purpose: get weekday in "DDD" format
wday = Application.Text(DateSerial(6, 1, wd), cPattern(lang) & "ddd") ' the first day in year 1906 starts with a Sunday
End Function
'~~> Improvement suggested by T.M (https://stackoverflow.com/users/6460297/t-m)
'(2) Get month name
Function mon(ByVal mo&, ByVal lang As String) As String
' Example call: mon(12, "1031") or mon(12, "de")
mon = Application.Text(DateSerial(6, mo, 1), cPattern(lang) & "mmm")
End Function
'~~> Improvement suggested by T.M (https://stackoverflow.com/users/6460297/t-m)
'(3) International patterns
Function cPattern(ByVal ctry As String) As String
' Purpose: return country code pattern for above functions mon() and wday()
' Codes: see https://msdn.microsoft.com/en-us/library/dd318693(VS.85).aspx
ctry = LCase(Trim(ctry))
Select Case ctry
Case "1033", "en-us": cPattern = "[$-409]" ' English (US)
Case "1031", "de": cPattern = "[$-C07]" ' German
Case "1034", "es": cPattern = "[$-C0A]" ' Spanish
Case "1036", "fr": cPattern = "[$-80C]" ' French
Case "1040", "it": cPattern = "[$-410]" ' Italian
' more ...
End Select
End Function
Userform Code用户表单代码
The Userform (Let's call it frmCalendar
) code is too big to be posted here. frmCalendar
(我们称之为frmCalendar
)代码太大,无法在此处发布。 Please refer to the sample file.请参考示例文件。
Screenshot截屏
Themes主题
Highlights强调
Sample File示例文件
Acknowlegements @Pᴇʜ, @chrisneilsen and @TM for suggesting improvements.感谢@Pᴇʜ、@chrisneilsen 和@TM 提出改进建议。
What's New :最新消息:
Bugs reported by @RobinAipperspach and @Jose fixed @RobinAipperspach 和 @Jose 报告的错误已修复
This is my first post here.这是我在这里的第一篇文章。 I felt compelled to share as the loss of the calendar in Excel was a huge deal and this calendar SiddhartRout created is incredible.
我觉得有必要分享一下,因为 Excel 中的日历丢失是一件大事,而 SiddhartRout 创建的这个日历令人难以置信。 So, MANY thanks to @SiddhartRout for putting together this really amazing calendar.
所以,非常感谢@SiddhartRout 整理了这个非常棒的日历。 I made changes to the cosmetics but most of the underlying meat of it is still all Siddhart's work with some minor changes to meet my use case.
我对化妆品进行了更改,但它的大部分基础内容仍然是 Siddhart 的工作,只进行了一些小的更改以满足我的用例。
Cosmetic changes :外观变化:
Code Changes代码更改
Screenshots of each theme:每个主题的截图:
Download link for code:代码下载链接:
Get international day & month names获取国际日期和月份名称
This answer is intended to be helpful to Sid's approach regarding internationalization ;
这个答案旨在帮助 Sid 关于国际化的方法; so it doesn't repeat the other code parts which I consider to be clear enough building a UserForm.
所以它不会重复我认为足够清晰的构建用户窗体的其他代码部分。 If wanted, I can delete it after incorporation in Vers.
如果需要,我可以在合并到 Vers 后将其删除。 4.0.
4.0.
Just in addition to Sid's valid solution I demonstrate a simplified code to get international weekday and month names - cf Dynamically display weekday names in native Excel language除了 Sid 的有效解决方案之外,我还演示了一个简化的代码来获取国际工作日和月份名称 - 参见Dynamically display weekday names in native Excel language
Modified ChangeLanguage
procedure in form's module frmCalendar
修改了表单模块
frmCalendar
ChangeLanguage
程序
Sub ChangeLanguage(ByVal LCID As Long)
Dim i&
'~~> Week Day Name
For i = 1 To 7
Me.Controls("WD" & i).Caption = Left(wday(i, LCID), 2)
Next i
'~~> Month Name
For i = 1 To 12
Me.Controls("M" & i).Caption = Left(mon(i, LCID), 3)
Next i
End Sub
Called Functions in CalendarModule
CalendarModule
调用函数
These three functions could replace the LanguageTranslations()
function.这三个函数可以替代
LanguageTranslations()
函数。 Advantage: short code, less memory, easier maintenance, correct names优点:代码短、内存少、维护方便、名称正确
'(1) Get weekday name
Function wday(ByVal wd&, ByVal lang As String) As String
' Purpose: get weekday in "DDD" format
wday = Application.Text(DateSerial(6, 1, wd), cPattern(lang) & "ddd") ' the first day in year 1906 starts with a Sunday
End Function
'(2) Get month name
Function mon(ByVal mo&, ByVal lang As String) As String
' Example call: mon(12, "1031") or mon(12, "de")
mon = Application.Text(DateSerial(6, mo, 1), cPattern(lang) & "mmm")
End Function
'(3) International patterns
Function cPattern(ByVal ctry As String) As String
' Purpose: return country code pattern for above functions mon() and wday()
' Codes: see https://msdn.microsoft.com/en-us/library/dd318693(VS.85).aspx
ctry = lcase(trim(ctry))
Select Case ctry
Case "1033", "en-us"
cPattern = "[$-409]" ' English (US)
Case "1031", "de"
cPattern = "[$-C07]" ' German
Case "1034", "es"
cPattern = "[$-C0A]" ' Spanish
Case "1036", "fr"
cPattern = "[$-80C]" ' French
Case "1040", "it"
cPattern = "[$-410]" ' Italian
' more ...
End Select
End Function
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.