简体   繁体   English

如何在 VBA Excel 中创建日历输入?

[英]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 ControlDatetime Picker ,我们需要设置对Microsoft MonthView Control 6.0 (SP4)的引用,它只能通过mscomct2.ocx的提升注册访问。 Similarly for mscal.ocx and mscomctl.ocx . mscal.ocxmscomctl.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 ControlDatetime 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强调

  1. No need to register any dll/ocx.无需注册任何 dll/ocx。
  2. Easily distributable.易于分发。 It is FREE.这是免费的。
  3. No Administratior Rights required to use this.使用它不需要管理员权限。
  4. You can select a skin for the calendar widget.您可以为日历小部件选择皮肤。 One can choose from 4 themes Venom, MartianRed, ArticBlue and GreyScale.可以从毒液、火星红、ArticBlue 和 GreyScale 4 个主题中进行选择。
  5. Choose Language to see Month/Day name.选择语言以查看月/日名称。 Support for 4 languages.支持 4 种语言。
  6. Specify Long and Short date formats指定长日期和短日期格式

Sample File示例文件

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 :外观变化

  • Replaced ALL of the buttons with borderless labels so it looks a lot more like the Windows 10 calendar用无边框标签替换了所有按钮,使其看起来更像 Windows 10 日历
  • The border of the labels will appear/disappear on mouse enter/exit标签的边框将在鼠标进入/退出时出现/消失
  • I grayed out days that aren't for the current month.我将不属于当月的天数灰显。 The 'gray out' is a different color that matches better for each theme. “灰色”是一种不同的颜色,更适合每个主题。
  • Modified the theme colors to my liking.根据我的喜好修改了主题颜色。 Added a label to click for cycling through the themes.添加了一个标签以单击以循环浏览主题。
  • Changed the font to Calibri将字体更改为 Calibri
  • added color change on mouse entry to month/year and arrow controls将鼠标输入的颜色更改添加到月/年和箭头控件
  • Use this site for all of you color code needs --> RGB Color Codes使用本网站满足您所有的颜色代码需求 --> RGB 颜色代码

Code Changes代码更改

  • Optimized the Property Let Caltheme making it easier to setup and add theme colors or entirely new themes优化了属性让 Caltheme 更容易设置和添加主题颜色或全新的主题
  • I couldn't get the 'ESC to exit' to work reliably so i replaced it with an 'X'.我无法让“ESC 退出”可靠地工作,因此我将其替换为“X”。 It stopped crashing as much as well.它也停止了崩溃。
  • Removed the localization stuff as i'll never need it删除了本地化的东西,因为我永远不需要它
  • Changing from buttons to labels required modifying some object variables where needed throughout the project从按钮更改为标签需要在整个项目中根据需要修改一些对象变量
  • Added public variables used to store RGB values allowing use of theme colors throughout the project providing for more consistent and easier application of selected theme添加了用于存储 RGB 值的公共变量,允许在整个项目中使用主题颜色,从而更一致、更轻松地应用所选主题
  • User selected theme stored in the hidden sheet so it's persistent between runs用户选择的主题存储在隐藏的工作表中,因此它在运行之间保持不变
  • Removed the checkmark button & launch directly from a click on any day.删除了复选标记按钮并在任何一天单击即可直接启动。

Screenshots of each theme:每个主题的截图:

毒液 2 火星红2
北极蓝 2 灰度 2

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM