【问题标题】:Excel macro stops running by itself when I switch to another Excel file当我切换到另一个 Excel 文件时,Excel 宏会自行停止运行
【发布时间】:2019-10-09 22:06:34
【问题描述】:

我正在处理一个启用宏的 Excel 文件,我希望它像时间表一样工作。用户将按下一个按钮,当前时间将被标记在开始列中;如果用户再次按下它,当前时间将被标记在完成列中。如果用户启动了一个循环,并且 PC 在没有用户交互的情况下运行了 5 分钟,则循环将结束,时间将自动标记在完成单元格中。当循环开始时,会运行 5 分钟的计时器。如果在 5 分钟计时器结束之前没有 PC 活动,则会标记时间并结束循环。 该代码检测 PC 不活动,而不仅仅是在 Excel 中。

当我打开或处理另一个工作簿时,宏/计时器停止。即使我正在处理另一个 Excel 文件,我也需要宏继续运行。

我尝试了 DoEvents 解决方法,但没有成功。我读到在使用 Workbook.Open 时存在错误,但我没有在我的任何模块中使用 Workbook.Open。当我打开另一个 Excel 文件时,只需在我的桌面上找到该文件并双击它。

我一直在尝试声明一个全局变量来获取和存储工作表的名称(因为其他人会复制并使用它,所以很可能会更改工作表的名称)因为我感觉这与我识别活动单元格的代码有关。

表 1

Dim mRg As Range
Dim mStr As String

Private Sub Worksheet_Change(ByVal Target As Range)

Dim WorkRng As Range
Dim Rng As Range
Dim xOffsetColumn As Integer
Set WorkRng = Intersect(Application.ActiveSheet.Range("A:A"), Target)
xOffsetColumn = 5

If Not WorkRng Is Nothing Then
    Application.EnableEvents = False
    For Each Rng In WorkRng
        If Not VBA.IsEmpty(Rng.Value) Then
            Rng.Offset(0, xOffsetColumn).Value = Date
        Else
            Rng.Offset(0, xOffsetColumn).ClearContents
        End If
    Next
    Application.EnableEvents = True
End If

    'Locking the entire third column with a password
    Dim xRg As Range
    On Error Resume Next
    Set xRg = Intersect(Range("C:C"), Target)
    If xRg Is Nothing Then Exit Sub
    Target.Worksheet.Unprotect Password:="password"
    If xRg.Value <> mStr Then xRg.Locked = True
    Target.Worksheet.Protect Password:="password"

End Sub


Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)

If Not Intersect(Range("C:C"), Target) Is Nothing Then
    Set mRg = Target.Item(1)
    mStr = mRg.Value
End If
End Sub

这本工作簿

Private Sub Workbook_BeforeClose(Cancel As Boolean)

    Dim lA As Long
    'Last non-blank cell of column A (Start)

    Dim lB As Long
    'Last non-blank cell of column B (Finish)

        lA = Cells(Rows.Count, 1).End(xlUp).Row
        lB = Cells(Rows.Count, 2).End(xlUp).Row

    If lA <> lB Then
    'If there's an unfinished cycle, execute the following:
        Call Range_End_Method
        Call TimeStartStop
        ThisWorkbook.Save
        Application.DisplayAlerts = False
    End If

    If lA = lB Then
    'If there's no unfinished cycle, save the sheet and immediately close the sheet
        ThisWorkbook.Save
        Application.DisplayAlerts = False
    End If

End Sub

模块 1

Option Explicit

Sub Range_End_Method()
'Finds the last non-blank cell in a single row or column


Dim FirstBlankCell As Range
Set FirstBlankCell = Range("B" & Rows.Count).End(xlUp).Offset(1, 0)
FirstBlankCell.Activate


End Sub

模块2

Private Type LASTINPUTINFO
   cbSize As Long
   dwTime As Long
End Type

Private Declare PtrSafe Function GetLastInputInfo Lib "user32" (lii As LASTINPUTINFO) As Long
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long

'Getting number of seconds idle/user inactivity on PC, not just Excel
Private Function GetIdleSecs()
    Dim LastInput As LASTINPUTINFO
    With LastInput
        .cbSize = Len(LastInput)
        Call GetLastInputInfo(LastInput)
        GetIdleSecs = (GetTickCount() - .dwTime) / 1000
    End With
End Function

Public Sub checkIdle()

    Dim lA As Long
    'Last non-blank cell of column A (Start)

    Dim lB As Long
    'Last non-blank cell of column B (Finish)

    lA = Cells(Rows.Count, 1).End(xlUp).Row
    lB = Cells(Rows.Count, 2).End(xlUp).Row

    'If block for if the sheet is blank
    If lA = 4 Then
        lA = 6
    End If
    If lB = 4 Then
        lB = 6
    End If

    DoEvents
    'Number of seconds idle
    Dim idleSecs As Long: idleSecs = GetIdleSecs()

    If lA <> lB Then
    'If there's a running cycle, execute the following
        'For Debugging purposes; this shows up in the immediate Window which you can open by pressing Ctrl + G
        Debug.Print "Idle for"; idleSecs

        'If number of seconds idle is less than 5 minutes, the timer will continue counting and updating every second
        If idleSecs < 300 Then
            Application.OnTime Now + TimeValue("00:00:01"), "checkIdle", False
        End If

        'idleSecs is the number of seconds needed before the whole If Block is executed
        '300 seconds because 5 minutes x 60 seconds
        If idleSecs = 300 Then
            Call Module1.Range_End_Method
            Call Module3.TimeStartStop

            'Save the current workbook
            ThisWorkbook.Save

            'Message box: First parameter is the message body, third parameter is the alert title
            MsgBox "TMS has stopped due to 5 minutes of inactivity. Your workbook has automatically been updated and saved.", , "TMS Stopped"

            'Sub is automatically closed once 5 minutes is reached and the timer is stopped
            Exit Sub
        End If
    End If

    If lA = lB Then
        'If there's no running cycle, execute the following:
        'For debugging purposes
        'Debug.Print "Doing nothing..."
    End If

End Sub

Public Sub doNothing()
    'For debugging purposes
    'Debug.Print "Doing nothing..."
End Sub

模块3

Sub TimeStartStop()

CR = ActiveCell.Row

CC = ActiveCell.Column

If CC <= 2 And CR >= 6 Then
    TS = ThisWorkbook.ActiveSheet.Name

    ThisWorkbook.Sheets(TS).Cells(CR, CC) = Now
    ThisWorkbook.Sheets(TS).Cells(CR, CC + 1).Select
    If CC = 2 And ThisWorkbook.Sheets(TS).Cells(CR, 1) <> "" Then
        ActiveCell.FormulaR1C1 = _
                "=IFS(RC[-2] = """","""",((RC[-1]-RC[-2])*24*60)<0,"""",(RC[-1]-RC[-2])*24*60,(RC[-1]-RC[-2])*24*60)"
        'ThisWorkbook.ActiveSheet.Cells(CR, 3) = _
         ' (ThisWorkbook.ActiveSheet.Cells(CR, 2) - ThisWorkbook.ActiveSheet.Cells(CR, 1)) * 24 * 60
        ThisWorkbook.Sheets(TS).Cells(CR + 1, CC - 1).Select


    End If
End If
End Sub

模块4

Sub StartStopButtonClick()
'Macro assigned to the Start/Stop button. Automatically executes the TimeStartStop and checkIdle subs upon being clicked

    Dim lA As Long
    'Last non-blank cell of column A (Start)

    Dim lB As Long
    'Last non-blank cell of column B (Finish)

    Dim lC As Long
    'The next blank cell right below lA


        lA = Cells(Rows.Count, 1).End(xlUp).Row
        lB = Cells(Rows.Count, 2).End(xlUp).Row

        lC = lA + 1

    If lA = 4 Then
    'For a completely blank file
        lA = 6
        lB = 6
        Cells(lA, 1).Select

    ElseIf lA <> lB Then
    'If there's an unfinished cycle, go to the Finish cell
        Cells(lA, 2).Select


    ElseIf lA = lB Then
    'If there are no running cycles, go to the next empty Start cell
        Cells(lC, 1).Select

    Else:
        Debug.Print "Call Tech Support"
    End If

    'Execute the following modules:
    Call Module3.TimeStartStop
    Call Module2.checkIdle
End Sub

This is how the worksheet looks.

一旦通过宏(使用开始和结束时间计算周期时间)将数据输入其中,C 列(周期时间)中的单元格应该被锁定。

诚然,删除/删除行是一件令人头疼的事情。我必须制作一份用户手册作为这样做的指南。

一切正常,只是当我切换到另一个 Excel 文件(即使它以前打开过)时,计时器停止

即使用户切换到另一个 Excel 文件或打开一个新的 Excel 文件,我也希望宏继续运行。

非常感谢!

【问题讨论】:

  • 您应该尝试从您的代码中消除每个“ActiveSheet”和“ActiveCell”。我不知道这是否是唯一的问题,但你总是会遇到依赖于它的代码的问题。
  • @Jochen 即将提出类似的建议...Application.ActiveSheet.Range.... 将始终是您当前的活动工作表(即:您在屏幕上或上次选择的任何工作表),所以如果您打开一个新工作簿,您的活动表将在新工作簿中。如果你完全限定范围,你不应该有问题。为此,您始终可以使用ThisWorkbook,它返回存储宏的工作簿(假设是同一个工作簿)。完全限定范围意味着几乎所有以Range...Cells...ActiveSheet...ActiveCell... 开头的内容。

标签: excel vba


【解决方案1】:

如前所述,您需要完全限定某些范围...有足够的空间使代码更整洁,但请参阅下面我的更改以至少限定范围。

本工作簿

Private Sub Workbook_BeforeClose(Cancel As Boolean)

    Dim lA As Long
    'Last non-blank cell of column A (Start)

    Dim lB As Long
    'Last non-blank cell of column B (Finish)

    Dim myActiveCell As Range

    With ThisWorkbook.Sheets("Sheet1")  'Assuming sheet 1
        lA = .Cells(Rows.Count, 1).End(xlUp).row
        lB = .Cells(Rows.Count, 2).End(xlUp).row
    End With

    If lA <> lB Then
    'If there's an unfinished cycle, execute the following:
        Set myActiveCell = Range_End_Method
        Call TimeStartStop(myActiveCell)
        ThisWorkbook.Save
        Application.DisplayAlerts = False
    End If

    If lA = lB Then
    'If there's no unfinished cycle, save the sheet and immediately close the sheet
        ThisWorkbook.Save
        Application.DisplayAlerts = False
    End If

End Sub

模块 1

Option Explicit

Function Range_End_Method() As Range
'Finds the last non-blank cell in a single row or column

Dim FirstBlankCell As Range
Set FirstBlankCell = ThisWorkbook.Sheets("Sheet1").Range("B" & Rows.Count).End(xlUp).Offset(1, 0)

Set Range_End_Method = FirstBlankCell 'should never use Activate if you can help it - and in this case, you can

End Function

模块2

Public Sub checkIdle()

    Dim lA As Long
    'Last non-blank cell of column A (Start)

    Dim lB As Long
    'Last non-blank cell of column B (Finish)

    Dim myActiveCell As Range

    With ThisWorkbook.Sheets("Sheets1")
        lA = .Cells(.Rows.Count, 1).End(xlUp).row
        lB = .Cells(.Rows.Count, 2).End(xlUp).row
    End With

    'If block for if the sheet is blank
    If lA = 4 Then
        lA = 6
    End If
    If lB = 4 Then
        lB = 6
    End If

    DoEvents
    'Number of seconds idle
    Dim idleSecs As Long: idleSecs = GetIdleSecs()

    If lA <> lB Then
    'If there's a running cycle, execute the following
        'For Debugging purposes; this shows up in the immediate Window which you can open by pressing Ctrl + G
        Debug.Print "Idle for"; idleSecs

        'If number of seconds idle is less than 5 minutes, the timer will continue counting and updating every second
        If idleSecs < 300 Then
            Application.OnTime Now + TimeValue("00:00:01"), "checkIdle", False
        End If

        'idleSecs is the number of seconds needed before the whole If Block is executed
        '300 seconds because 5 minutes x 60 seconds
        If idleSecs = 300 Then
            Set myActiveCell = Module1.Range_End_Method
            Call Module3.TimeStartStop(myActiveCell)

            'Save the current workbook
            ThisWorkbook.Save

            'Message box: First parameter is the message body, third parameter is the alert title
            MsgBox "TMS has stopped due to 5 minutes of inactivity. Your workbook has automatically been updated and saved.", , "TMS Stopped"

            'Sub is automatically closed once 5 minutes is reached and the timer is stopped
            Exit Sub
        End If
    End If

    If lA = lB Then
        'If there's no running cycle, execute the following:
        'For debugging purposes
        'Debug.Print "Doing nothing..."
    End If

End Sub

模块3

Sub TimeStartStop(cell As Range)

CR = cell.row

CC = cell.Column

If CC <= 2 And CR >= 6 Then
    TS = ThisWorkbook.Sheets("Sheet1").Name 'or just "Sheet1".

    ThisWorkbook.Sheets(TS).Cells(CR, CC) = Now
    'ThisWorkbook.Sheets(TS).Cells(CR, CC + 1).Select   'Avoid using select!
    If CC = 2 And ThisWorkbook.Sheets(TS).Cells(CR, 1) <> "" Then
        ThisWorkbook.Sheets(TS).Cells(CR, CC + 1).FormulaR1C1 = _
                "=IFS(RC[-2] = """","""",((RC[-1]-RC[-2])*24*60)<0,"""",(RC[-1]-RC[-2])*24*60,(RC[-1]-RC[-2])*24*60)"
        'ThisWorkbook.ActiveSheet.Cells(CR, 3) = _
         ' (ThisWorkbook.ActiveSheet.Cells(CR, 2) - ThisWorkbook.ActiveSheet.Cells(CR, 1)) * 24 * 60

        'ThisWorkbook.Sheets(TS).Cells(CR + 1, CC - 1).Select        'Avoid using select!

    End If
End If
End Sub

模块4

Sub StartStopButtonClick()
'Macro assigned to the Start/Stop button. Automatically executes the TimeStartStop and checkIdle subs upon being clicked

    Dim lA As Long
    'Last non-blank cell of column A (Start)

    Dim lB As Long
    'Last non-blank cell of column B (Finish)

    Dim lC As Long
    'The next blank cell right below lA

    Dim myActiveCell As Range   'Pass this value through rather than selecting ranges

    With ThisWorkbook.Sheets("Sheet1") 'Here the activesheet is the one with the button, obviously... but...
        lA = .Cells(Rows.Count, 1).End(xlUp).row
        lB = .Cells(Rows.Count, 2).End(xlUp).row

        lC = lA + 1

        If lA = 4 Then
        'For a completely blank file
            lA = 6
            lB = 6
            Set myActiveCell = .Cells(lA, 1) 'Avoid using Select! Fully qualify the range!

        ElseIf lA <> lB Then
        'If there's an unfinished cycle, go to the Finish cell
            Set myActiveCell = .Cells(lA, 2)


        ElseIf lA = lB Then
        'If there are no running cycles, go to the next empty Start cell
            Set myActiveCell = .Cells(lC, 1)

        Else:
            Debug.Print "Call Tech Support"
        End If

    End With

    'Execute the following modules:
    Call Module3.TimeStartStop(myActiveCell)
    Call Module2.checkIdle
End Sub

PS:为什么会有不同的模块?

【讨论】:

  • 非常感谢@DarXyde!它工作得很好,我又遇到了一个错误......当我处理另一个 Excel 文件并且计时器完成时,我遇到了“运行时错误'1004':对象'_Global'的方法'相交' failed”,它指向 Sheet1 的代码行: Set WorkRng = Intersect(Application.ActiveSheet.Range("A:A"), Target) 我猜这是因为 ActiveSheet?我尝试使用 ThisWorkbook.Sheets("Sheet1") 但这不起作用... Re:单独的模块,我只是在修复其他人的工作(老实说,我只是 VBA 的初学者)跨度>
  • 你好@DarXyde!我刚刚想通了!我使用 ThisWorkbook.Sheets("Sheet1").Activate 恢复到计时器表。我非常感谢你的帮助!你是最好的!您的答案已被标记为解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
  • 2016-12-29
  • 1970-01-01
  • 2023-03-18
  • 2023-02-03
  • 2016-12-17
相关资源
最近更新 更多