【问题标题】:Excel 365 VBA for hours and minutes formatExcel 365 VBA 小时和分钟格式
【发布时间】:2021-03-02 23:03:24
【问题描述】:

我正在处理一个简单的 Excel 文件,其中包含一些工作表,我在每个工作表中都报告了工作小时数和分钟数。我想将其显示为 313:32,即 313 小时 32 分钟,为此我使用了自定义格式 [h]:mm

为了方便很少使用Excel的工作人员,我想创建一些vba代码,以便他们不仅可以插入分钟,还可以插入经典格式[h]:mm,这样他们还可以插入小时值和分钟。 我报告了一些我想要的示例数据。 我插入的内容 -> 我想要在单元格内打印的内容

  • 1 -> 0:01
  • 2 -> 0:02
  • 3 -> 0:03
  • 65 -> 1:05
  • 23:33 -> 23:33
  • 24:00 -> 24:00
  • 24:01 -> 24:01

然后我在[h]:mm 中格式化了每个可以包含时间值的单元格,然后我编写了这段代码

Public Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
On Error GoTo bm_Safe_Exit
    With Sh
        If IsNumeric(Target) = True And Target.NumberFormat = "[h]:mm" Then

            If Int(Target.Value) / Target.Value = 1 Then
                Debug.Print "Integer -> " & Target.Value
                Application.EnableEvents = False
                Target.Value = Target.Value / 1440
                Application.EnableEvents = True
                Exit Sub
            End If

            Debug.Print "Other value -> " & Target.Value
        End If
    End With
bm_Safe_Exit:
    Application.EnableEvents = True
End Sub

代码运行良好,但是当我输入 24:00 及其倍数 48:00、72:00 时它会出错... 这是因为单元格的格式为 [h]:mm,所以 24:00 在 vba 代码执行之前变为 1!

我试图更正代码,有趣的事实是,当我更正 24:00 时,所以 24:00 仍然是 24:00 而不是 00:24,问题切换到 1 变成了 24:00 而不是 00 :01

我的第一个想法是在单元格格式之前“强制”执行 vba 代码,但我不知道是否可能。 我知道这似乎是一个愚蠢的问题,但我真的不知道这是否可能以及如何解决它。

任何想法将不胜感激

【问题讨论】:

  • 也许是look this
  • 感谢 Dorian 的回复,我不是很专业,但通常我会插入像 h:m 这样的数据,而不是像 3.5 或 3,5 这样的几分钟或几小时,并且“假装”有 3小时30分钟。所以再次感谢这一点,在某种程度上可能有用,但我认为现在不行。但我不是那么专家,也许,实际上是一个很好的起点。
  • 这样他们也可以只插入分钟 但是稍后作为示例,您使用像 23:33, 24:00, 24:01 这样的值,这不仅仅是分钟,这些输入是小时和分钟。按照您自己的规则,这些值应输入为1413, 1440, 1441,您的代码会将这些输入转换为23:33, 24:00, 24:01。所以你打破了自己的输入规则。更改方法或修改代码以首先检查插入的值是否只是分钟或小时和分钟。老实说,我认为更简单的方法是强制工人输入一个 integer 值,只需几分钟。
  • 我不确定在同一个单元格中允许 2 种不同类型的单元是否是正确的方法。这就像允许在相同的小区英里和公里,或磅和公斤。这可能会导致问题。考虑只允许以hh:mm 格式工作,因此 1 分钟将输入为00:01。实际上,我认为在 1 个单一的时间单位内工作比在 2 个不同的时间单位内工作更容易。
  • 总是更喜欢修复输入而不是稍后清理。指导用户使用一种格式并通过数据验证检查强制执行。

标签: excel vba excel-365


【解决方案1】:

要求:时间以小时和分钟为单位,分钟是最低的度量(即:无论时间量以小时为单位,部分小时以分钟为单位,即@ 987654333@或13.0638888888888889应显示为313:32) 应该允许用户以两种不同的方式输入时间:

  1. 仅输入分钟:输入的值应为整数(无小数)。
  2. 要输入小时和分钟:输入的值应由代表小时和分钟的两个整数组成,用冒号分隔:

输入的 Excel 处理值:

Excel 直观地处理单元格中输入的值的Data typeNumber.Format。 当单元格NumberFormat 为常规时,Excel 将输入的值转换为与输入的数据相关的数据类型(字符串、双精度、货币、日期等),它还会根据“格式”更改NumberFormat输入值(见下表)。

当单元格NumberFormat 不是“常规”时,Excel 会将输入的值转换为与单元格格式对应的数据类型,NumberFormat 不变(见下表)。

因此,无法知道用户输入的值的格式,除非可以在 Excel 应用其处理方法之前截取输入的值。

虽然输入的值在 Excel 处理之前无法被截取,但我们可以使用Range.Validation property 为用户输入的值设置验证标准。

解决方案:此提议的解决方案使用:

建议使用自定义的style 来识别和格式化输入单元格,实际上 OP 是使用NumberFormat 来识别输入单元格,但是似乎也可能存在带有公式或对象的单元格(即汇总表、PivotTables 等)需要相同的 NumberFormat。通过仅对输入单元格使用自定义样式,可以轻松地将非输入单元格从流程中排除。

Style object (Excel) 允许同时为单个或多个单元格设置NumberFormatFontAlignmentBordersInteriorProtection。下面的过程添加了一个名为TimeInput 的自定义样式。 Style 的名称被定义为公共常量,因为它将在整个工作簿中使用。

将此添加到标准模块中

Public Const pk_StyTmInp As String = "TimeInput"

Private Sub Wbk_Styles_Add_TimeInput()
    
    With ActiveWorkbook.Styles.Add(pk_StyTmInp)
        
        .IncludeNumber = True
        .IncludeFont = True
        .IncludeAlignment = True
        .IncludeBorder = True
        .IncludePatterns = True
        .IncludeProtection = True
    
        .NumberFormat = "[h]:mm"
        .Font.Color = XlRgbColor.rgbBlue
        .HorizontalAlignment = xlGeneral
        .Borders.LineStyle = xlNone
        .Interior.Color = XlRgbColor.rgbPowderBlue
        .Locked = False
        .FormulaHidden = False
    
    End With

End Sub

新样式将显示在主页选项卡中,只需选择输入范围并应用样式。

我们将使用Validation object (Excel) 告诉用户时间值的标准,并强制他们以Text 输入值。 以下过程设置输入范围的样式并为每个单元格添加验证:

Private Sub InputRange_Set_Properties(Rng As Range)

Const kFml As String = "=ISTEXT(#CLL)"
Const kTtl As String = "Time as ['M] or ['H:M]"
Const kMsg As String = "Enter time preceded by a apostrophe [']" & vbLf & _
                            "enter M minutes as 'M" & vbLf & _
                            "or H hours and M minutes as 'H:M"  'Change as required
Dim sFml As String
    
    Application.EnableEvents = False
    
    With Rng

        .Style = pk_StyTmInp
        sFml = Replace(kFml, "#CLL", .Cells(1).Address(0, 0))

        With .Validation
            .Delete
            .Add Type:=xlValidateCustom, _
                AlertStyle:=xlValidAlertStop, _
                Operator:=xlBetween, Formula1:=sFml
            .IgnoreBlank = True
            .InCellDropdown = False

            .InputTitle = kTtl
            .InputMessage = kMsg
            .ShowInput = True

            .ErrorTitle = kTtl
            .ErrorMessage = kMsg
            .ShowError = True

    End With: End With

    Application.EnableEvents = True

End Sub

过程可以这样调用

Private Sub InputRange_Set_Properties_TEST()
Dim Rng As Range
    Set Rng = ThisWorkbook.Sheets("TEST").Range("D3:D31")
    Call InputRange_Set_Properties(Rng)
    End Sub

现在我们已经使用适当的样式和验证设置了输入范围,让我们编写将处理时间输入的Workbook Event

将这些程序复制到ThisWorkbook 模块中:

  • Workbook_SheetChange - 工作簿事件
  • InputTime_ƒAsDate - 支持函数
  • InputTime_ƒAsMinutes - 支持函数

Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)

Const kMsg As String = "[ #INP ] is not a valid entry."
Dim blValid As Boolean
Dim vInput As Variant, dOutput As Date
Dim iTime As Integer
    
    Application.EnableEvents = False
    
    With Target

        Rem Validate Input Cell
        If .Cells.Count > 1 Then GoTo EXIT_Pcdr         'Target has multiple cells
        If .Style <> pk_StyTmInp Then GoTo EXIT_Pcdr    'Target Style is not TimeInput
        If .Value = vbNullString Then GoTo EXIT_Pcdr    'Target is empty
        
        Rem Validate & Process Input Value
        vInput = .Value                         'Set Input Value
        Select Case True
        Case Application.IsNumber(vInput):      GoTo EXIT_Pcdr      'NO ACTION NEEDED - Cell value is not a text thus is not an user input
        Case InStr(vInput, ":") > 0:            blValid = InputTime_ƒAsDate(dOutput, vInput)        'Validate & Format as Date
        Case Else:                              blValid = InputTime_ƒAsMinutes(dOutput, vInput)     'Validate & Format as Minutes
        End Select

        Rem Enter Output
        If blValid Then
            Rem Validation was OK
            .Value = dOutput
            
        Else
            Rem Validation failed
            MsgBox Replace(kMsg, "#INP", vInput), vbInformation, "Input Time"
            .Value = vbNullString
            GoTo EXIT_Pcdr
        
        End If

    End With

EXIT_Pcdr:
    Application.EnableEvents = True

End Sub

Private Function InputTime_ƒAsDate(dOutput As Date, vInput As Variant) As Boolean

Dim vTime As Variant, dTime As Date
    
    Rem Output Initialize
    dOutput = 0
              
    Rem Validate & Process Input Value as Date
    vTime = Split(vInput, ":")
    Select Case UBound(vTime)
    
    Case 1
        
        On Error Resume Next
        dTime = TimeSerial(CInt(vTime(0)), CInt(vTime(1)), 0)   'Convert Input to Date
        On Error GoTo 0
        If dTime = 0 Then Exit Function                         'Input is Invalid
        dOutput = dTime                                         'Input is Ok
        
    Case Else:      Exit Function                               'Input is Invalid
    End Select

    InputTime_ƒAsDate = True
    
End Function

Private Function InputTime_ƒAsMinutes(dOutput As Date, vInput As Variant) As Boolean

Dim iTime As Integer, dTime As Date
    
    Rem Output Initialize
    dOutput = 0
                
    Rem Validate & Process Input Value as Integer
    On Error Resume Next
    iTime = vInput
    On Error GoTo 0
    Select Case iTime = vInput
    
    Case True
        On Error Resume Next
        dTime = TimeSerial(0, vInput, 0)    'Convert Input to Date
        On Error GoTo 0
        If dTime = 0 Then Exit Function     'Input is Invalid
        dOutput = dTime                     'Input is Ok
        
    Case Else:      Exit Function           'Input is Invalid
    End Select

    InputTime_ƒAsMinutes = True
    
End Function

下表显示了输入的各种类型值的输出。

【讨论】:

  • 这里的工作很棒。正如我在 cmets 中所说,我认为在同一个单元格中允许 2 种不同的措施是一种糟糕的方法,并且您的代码长度证明了这一点(没有简单的方法可以做到)但是您在这里进行了史诗般的研究和全面的开发,并发布了记录在案的答案。赞成。
  • 嗨@FoxfireAndBurnsAndBurns,谢谢你的好话。我认为允许不同类型的措施是没有问题的,只要有明确、独特、简洁和一致的方式来识别它们(规则),并有明确的说明 i> 和 feedback 给用户,以便他们知道对他们的期望,为什么有些条目没有显示他们的期望,而其他条目根本无效。如果不能做到这一点,那么就只剩下一步到位的路径了。然而,即便如此,规则、指示和反馈仍然必不可少。请记住,没有傻瓜证明系统。
  • 只有敢于尝试荒谬的人才能实现不可能!谢谢EEM!很好的解释。现在我将更改格式并调整自定义TimeInput 的信息和格式。我有个问题。我认为可以是可选的,但我不知道如何正确设置InputRange_Set_Properties 我的意思是,应该在我打开文档时开始,对吧?你的主要想法是什么?我认为最好的方法是创建一个命名范围并设置它。 J
  • 创建Named Range 的想法很好,但是请记住,创建自定义样式、命名范围和应用范围验证是一次性的过程,不需要打开文档时重复。就像您现在将 [h]:mm 格式分配给范围时所做的一样。告诉我进展如何。
  • 感谢 EEM 指点我。您是否认为可以默认将InputRange_Set_Properties 设置为所有以TimeInput 为样式的单元格?这样,每次我必须设置或更改某些内容时,总是会更新
【解决方案2】:

最简单的方法似乎是使用单元格文本(即单元格的显示方式)而不是实际的单元格值。如果它看起来像一个时间(例如"[h]:mm""hh:mm""hh:mm:ss"),则使用它来相应地添加每个时间部分的值(以避免 24:00 问题)。否则,如果是数字,则假定为分钟。

以下方法也适用于 GeneralTextTime 等格式(除非时间以天部分开头,但它可以需要进一步开发以处理该问题)。

Public Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
    On Error GoTo bm_Safe_Exit
    
    Dim part As String, parts() As String, total As Single
    
    Application.EnableEvents = False
    
    If Not IsEmpty(Target) And Target.NumberFormat = "[h]:mm" Then
        'prefer how the Target looks over its underlying value
        If InStr(Target.Text, ":") Then
            'split by ":" then add the parts to give the decimal value
            parts = Split(Target.Text, ":")
            total = 0
            
            'hours
            If IsNumeric(parts(0)) Then
                total = CInt(parts(0)) / 24
            End If
            
            'minutes
            If 0 < UBound(parts) Then
                If IsNumeric(parts(1)) Then
                    total = total + CInt(parts(1)) / 1440
                End If
            End If
        ElseIf IsNumeric(Target.Value) Then
            'if it doesn't look like a time format but is numeric, count as minutes
            total = Target.Value / 1440
        End If
        
        Target.Value = total
    End If
    
bm_Safe_Exit:
    Application.EnableEvents = True
End Sub

【讨论】:

  • 嗨@sbgib,谢谢你的帮助,我会试试代码,我会更新你的。可以作为起点,不知道能不能只设置一个范围,可能需要设置更多范围。明天我会试试你的代码。使用此代码的一个问题是可以对其他单元格进行求和吗?我有其他单元格(在最终范围之外)总计小时和分钟。
  • 友情提示:您使用的是Worksheet_Change() 事件的参数集,而不是Workbook_SheetChange() 事件的参数集:-) @sbgib
  • @sbgib 我试图复制粘贴代码,但在测试过程中出现错误,例如“过程声明与具有相同名称的事件或过程的描述不匹配”
  • @T.M.感谢您指出这一点,我现在已经更正了。
  • @JormanFranzini 对此感到抱歉!如果您现在再试一次,它应该可以工作。可以使用此代码求和,结果值将是 "[h]:mm" 格式的数字,正如您所期望的那样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多