【问题标题】:Excel VBA - always show worksheet on openExcel VBA - 始终在打开时显示工作表
【发布时间】:2013-09-30 16:31:01
【问题描述】:

VBA代码如何满足以下条件?

  1. 打开时始终显示特定的工作表,即使在未启用宏的情况下打开工作簿也是如此。
  2. 工作簿用户可以在处理任何工作表时保存工作簿。
  3. 保存不得干扰用户 - 不得导航到其他工作表、消息框等。
  4. 常规保存功能(Ctrl-S,单击保存)必须保持可用,并且在使用时必须遵守上述标准。

我想避免我在此问题底部列出的尝试解决方案。

详情:
该工作簿是在 Windows 7 计算机上使用 Office 2007 创建的。这是一个 .xlsm 工作簿,有 2 个工作表,“调度程序”和“信息”。工作表选项卡不可见。打开工作簿时,并非所有用户都会启用宏。

打开工作簿后,用户只会看到一张工作表,如下所示:

  • “信息”在宏被禁用时显示,并且基本上告诉打开工作簿的任何人需要启用宏才能获得完整的工作簿功能。如果此时启用了宏,则会激活“调度程序”。
  • “调度程序”是存储和编辑数据的地方,如果启用了宏,它会自动显示。在未启用宏的情况下打开工作簿时,它不会呈现给用户。

如果打开工作簿并禁用宏,则必须首先显示“信息”。

尝试过的解决方案(我正在寻找更好的解决方案!):

  • Workbook.BeforeSave 事件中放置代码。 这会在激活“信息”的情况下保存,以便在打开工作簿时显示。但是,如果用户在“调度程序”中但未完成,我无法在此事件中找到在保存后重新激活“调度程序”的方法。
  • 使用Application.OnKey 重新映射 Ctrl-sCtrl-S 击键。 不幸的是,这忽略了使用鼠标保存的用户(单击文件...保存或 Office 按钮...保存)。
  • 在每个操作期间进行检查,如果需要激活“调度程序”。 换句话说,在Workbook.SheetActivate.SheetChange 事件中插入代码以在使用“保存”后将“调度程序”重新置于焦点。信息”激活。这会不断运行 VBA 代码,让我觉得这是让工作簿中的其他代码陷入困境的好方法。
  • Worksheet("Info").Activate 事件中放置代码,以将焦点更改回“调度程序”。这会导致在打开工作簿时显示“调度程序”而不是“信息”的结果,即使禁用了宏也是如此。

【问题讨论】:

  • "Info" must show up first thing if the workbook is opened and macros are disabled. 如果宏被禁用,那么为什么要尝试Attempted Solutions :) 或者我在这里遗漏了什么?
  • 好问题 - 工作簿包含宏,但由用户在共享驱动器上打开,这些用户可能在受信任的文件路径中有也可能没有该位置。所以,对于那些在没有启用宏的情况下打开的人,我使用“信息”表说,“打开宏!”一旦它们被打开,那么我需要确保,无论工作簿上发生什么变化,一旦保存,下一个打开禁用宏的工作簿的人会看到“信息”。
  • 我能想到的唯一方法是以只读模式保存文件。这样,它将始终以信息表作为第一张表打开...前提是您在信息表处于活动状态的情况下保存并关闭文件。 Excel 会记住您打开的最后一张工作表。
  • 不幸的是,这不是一个选项,因为其他人需要修改“调度程序”,并且工作簿需要保存这些修改:(

标签: excel vba


【解决方案1】:

这行不通吗? 更新为优雅地处理保存

Private Sub Workbook_Open()
    ThisWorkbook.Worksheets("Scheduler").Activate
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    ThisWorkbook.Worksheets("Info").Activate
    If (ShouldSaveBeforeClose()) Then
        Me.Save
    Else
        Me.Saved = True ' Prevents Excel Save prompt.
    End If
End Sub

Private Function ShouldSaveBeforeClose() As Boolean
    Dim workbookDirty As Boolean
    workbookDirty = (Not Me.Saved)
    If (Not workbookDirty) Then
        ShouldSaveBeforeClose= False
        Exit Function
    End If

    Dim response As Integer
    response = MsgBox("Save changes to WorkBook?", vbYesNo, "Attention")
    ShouldSaveBeforeClose= (response = VbMsgBoxResult.vbYes)
End Function

【讨论】:

  • 我认为这是一个不错的选择,除非用户可能不想保存他们所做的事情!这会强制保存那些不需要的更改。仍然为 BeforeClose 事件 +1,我没有想到这一点。
  • 我更新了答案以处理您对保存的有效担忧。
  • 您对保存问题的更新是可行的,但开始时过于复杂。我会放弃workbookDirty 并简单地检查if (thisworkbook.saved) then 否则基本上你正在服用Not Not Me.Saved 最好避免双重否定。
  • 我认为这并不符合所有标准。考虑这种情况 - 用户编辑“调度程序”,保存,编辑“调度程序”一些,但决定他们不想在关闭之前保存最近的编辑。工作簿不会在“信息”激活时打开。
【解决方案2】:

我没有时间对此进行测试,但您可以在 BeforeSave 事件处理程序中使用 Application.OnTime 来执行此操作。比如:

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
    Dim objActiveSheet
    Set objActiveSheet = Me.ActiveSheet
    If objActiveSheet Is InfoSheet Then Exit Sub
    If Module1.PreviousSheet Is Nothing Then
        Set Module1.PreviousSheet = objActiveSheet
        InfoSheet.Activate
        Application.OnTime Now, "ActivatePreviousSheet"
    End If
End Sub

然后在 Module1 中:

Public PreviousSheet As Worksheet

Public Sub ActivatePreviousSheet()
    If Not PreviousSheet Is Nothing Then
        PreviousSheet.Activate
        Set PreviousSheet = Nothing
    End If
End Sub

【讨论】:

  • 我了解这段代码的作用,但我很困惑为什么 .OnTime 最早时间 = 现在。 BeforeSave 事件结束后是否会进行实际保存?如果是这样,.OnTime 不会在保存之前发生吗?
  • @AaronThomas,在保存之前不会发生 OnTime:VBA 是单线程的,因此在当前代码将控制权返回给消息循环之前,使用 Application.OnTime 安排的任何内容都无法运行 - 要么运行完成或通过调用 DoEvents。在这种情况下,文件保存在计划的操作可以运行之前完成。事实上,如果文件保存是关闭工作簿的结果,关闭也将运行到完成,并且计划的操作根本不会运行。据我所知,所有这些都正是您想要的。
  • 除了一个问题外运行良好 - 作为其副作用,如果用户尝试关闭工作簿,然后单击“是”以保存他们的工作,Application.OnTime 在工作簿关闭后执行。这导致工作簿重新打开并执行子“PreviousSheet”。如果用户在关闭工作簿时尝试保存,关于如何杀死这个的任何想法?
  • 实际上,这部分已经处理好了。添加了一个关闭_check 全局,如果事件 Workbook_BeforeClose 发生则设置为 true,并将 Application.OnTime 更改为 If closing_check = False Then Application.OnTime Now, "ActivatePreviousSheet"。效果很好,谢谢!
  • @AaronThomas - 我不知道使用 OnTime 进行计划会导致包含计划宏的工作簿重新打开 - 因此我的评论“......并且计划的操作根本不会运行”我之前的评论是错误的。但仔细想想,它是有道理的,而且您的解决方案听起来不错。
【解决方案3】:

编辑 2:这是一个不使用 AfterSave 的重写。您可能需要根据需要调整从 GetSaveAsFilename 创建的对话框。

这依赖于覆盖默认保存行为并自己处理保存。

Private actSheet As Worksheet
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
    Cancel = True
    PrepareForSave
    manualSave SaveAsUI
    AfterSave ThisWorkbook.Saved
End Sub
Private Sub PrepareForSave()
    Set actSheet = ThisWorkbook.ActiveSheet
    ThisWorkbook.Sheets("Info").Activate
    hidesheets
End Sub
Private Sub manualSave(ByVal SaveAsUI As Boolean)
    On Error GoTo SaveError 'To catch failed save as
    Application.EnableEvents = False
    If SaveAsUI Then
        If Val(Application.Version) >= 12 Then
            sPathname = Application.GetSaveAsFilename(FileFilter:="Excel Files (*.xlsm), *.xlsm")
            If sPathname = False Then 'User hit Cancel
                GoTo CleanUp
            End If
            ThisWorkbook.SaveAs Filename:=sPathname, FileFormat:=52
        Else
            sPathname = Application.GetSaveAsFilename(FileFilter:="Excel Files (*.xls), *.xls")
            If sPathname = False Then
                GoTo CleanUp
            End If
            ThisWorkbook.SaveAs Filename:=sPathname, FileFormat:=xlNormal
        End If
    Else
        ThisWorkbook.Save
    End If
SaveError:
    If Err.Number = 1004 Then
        'Cannot access save location
        'User clicked no to overwrite
        'Or hit cancel
    End If
CleanUp:
    Application.EnableEvents = True
End Sub

Private Sub AfterSave(ByVal bSaved As Boolean)
    showsheets
    If actSheet Is Nothing Then
        ThisWorkbook.Sheets("Scheduler").Activate
    Else
        actSheet.Activate
        Set actSheet = Nothing
    End If
    If bSaved Then
        ThisWorkbook.Saved = True
    End If
End Sub
Private Sub hidesheets()
    For Each ws In ThisWorkbook.Worksheets
        If ws.Name <> "Info" Then
            ws.Visible = xlVeryHidden
        End If
    Next
End Sub
Private Sub showsheets()
    For Each ws In ThisWorkbook.Worksheets
        ws.Visible = True
    Next
End Sub
Private Sub Workbook_Open()
    AfterSave True
End Sub

在不启用宏的情况下首先显示Info 的唯一方法是保存工作簿的方式。保存时这是最合理的处理。

除非我误解了您的问题,否则不使用 BeforeSave 似乎是错误的。只要确保也使用AfterSave。这是一个例子:

Private actSheet As Worksheet
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
    showsheets
    actSheet.Activate
    Set actSheet = Nothing
    Thisworkbook.Saved = true 'To prevent save prompt from appearing
End Sub

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
    Set actSheet = ThisWorkbook.activeSheet
    ThisWorkbook.Sheets("Info").Activate
    hidesheets
End Sub

Private Sub Workbook_Open()
    showsheets
    ThisWorkbook.Sheets("Scheduler").Activate
End Sub
Private Sub hidesheets()
    For Each ws In ThisWorkbook.Worksheets
        If ws.Name <> "Info" Then
            ws.Visible = xlVeryHidden
        End If
    Next
End Sub
Private Sub showsheets()
    For Each ws In ThisWorkbook.Worksheets
        ws.Visible = True
    Next
End Sub

使用私有对象actSheet可以让“ActiveSheet”在保存后被重新选中。

编辑:我注意到您对 cme​​ts 有更多要求。代码已更新,现在保存后,只有 Info 表可见,但打开或保存后,每个表都会重新出现。

这使得任何打开没有宏的文件的用户都无法在激活其他工作表的情况下进行保存,甚至无法查看其他工作表。这肯定有助于激励他们启用宏!

【讨论】:

  • 从技术上讲,打开没有宏的工作簿的人仍然可以添加工作表并在选择该工作表的情况下保存。如果这成为问题,您可以保护工作簿,但您需要在代码中包含相应的逻辑。
  • "...只要确保也使用 AfterSave" - 并确保所有用户都使用 Office 2010 或更高版本,因为那是添加 AfterSave 事件的时候。
  • @Joe 啊,当时没有其他人建议这样做是有道理的。 :) 在这种情况下,您可以handle it all in the BeforeSave event 通过取消保存并通过代码自己处理保存。可能像您建议的那样使用Application.OnTime 可能会起作用,但我不想不得不诉诸于此。
  • 抱歉@DanielCook,不幸的是使用Office 2007。
  • @DanielCook 这看起来可行,但我认为覆盖整个“保存”过程似乎有点太多了。 answer that uses Application.OnTime 无需更改大部分实际保存过程即可。
【解决方案4】:

这个问题过去一直被鞭打死,很难找到真正有效的解决方案。看看这段代码,它应该能满足你的需要。基本上它会显示一个启动屏幕,如果用户不启用宏,所有其他工作表都会隐藏。如果用户点击保存,它仍然会正常保存并且不会干扰他们的工作。如果他们在打开工作表的情况下保存,则下次打开时仍将仅显示初始屏幕。下载下面的示例文件,您可以自己测试,确保您下载 Reafidy 发布的文件,它有超过 400 次浏览。如果您需要进一步修改,请告诉我。

Private Sub Workbook_BeforeClose(Cancel As Boolean) 
    bIsClosing = True 
End Sub 
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean) 
    Dim wsArray() As Variant 
    Dim iCnt As Integer 
    Application.ScreenUpdating = 0 

    Splash.Visible = True 

    For Each wsSht In ThisWorkbook.Worksheets 
        If Not wsSht.CodeName = "Splash" Then 
            If wsSht.Visible = True Then 
                iCnt = iCnt + 1: Redim Preserve wsArray(1 To iCnt) 
                wsArray(iCnt) = wsSht.Name 
            End If 
            wsSht.Visible = xlSheetVeryHidden 
        End If 
    Next 

    Application.EnableEvents = 0 
    ThisWorkbook.Save 
    Application.EnableEvents = 1 

    If Not bIsClosing Then 
        For iCnt = 1 To UBound(wsArray) 
            Worksheets(wsArray(iCnt)).Visible = True 
        Next iCnt 
        Splash.Visible = False 
        Cancel = True 
    End If 

    Application.ScreenUpdating = 1 
End Sub 
Private Sub Workbook_Open() 
    Dim wsSht As Worksheet 

    For Each wsSht In ThisWorkbook.Worksheets 
        wsSht.Visible = xlSheetVisible 
    Next wsSht 

    Splash.Visible = xlSheetVeryHidden 

    bIsClosing = False 
End Sub 

可以在here.找到示例文件

【讨论】:

  • @Aaron,这有帮助吗,您需要更多帮助吗?
  • 不幸的是,我无法下载示例文件进行检查,尽管我可以按照您的代码 sn-p。这是一个很好的答案,但我认为这不是最好的答案。我已经设置好了,一旦启用宏,用户就不太可能切换到“信息”屏幕,因此在每次保存时隐藏和取消隐藏工作表似乎不如在每次保存之前简单地切换到“信息”,因为一直如此在其他问题中提出。但是,这是一个很好的解决方案。
  • @Aaron,如果用户取消关闭,则关闭检查布尔值可以脱离接收器,尝试一下,您会发现它是误报。还要注意自动保存,自动恢复。最后,我不建议像您选择的那样使用调度程序/准时。它应该在 excel 恕我直言中正确取消,它只会导致经验上的麻烦。希望这听起来不像是酸葡萄,因为你没有选择我的答案,我真的不介意。只是尝试太有帮助。一切顺利。
【解决方案5】:

如何使用“代理工作簿”。

“代理工作簿”

  • 是唯一由用户直接打开的工作簿
  • 包含信息表
  • 包含 VBA 以使用 Workbooks.Open 打开您的“真实工作簿”(正如我检查过的 Workbooks.Open 文档,默认情况下它不会将文件名添加到您最近的文件历史记录中,除非您将 AddToMru 参数设置为 true)
  • 如果需要,VBA 代码甚至可以确保您的“目标工作簿”是可信的(我找到了一些示例代码 here

“目标工作簿”

  • 包含您的日程安排和任何其他工作表
  • 只有在“代理工作簿”中的 VBA 代码被执行时才会打开
  • 用户可以像往常一样随时保存

我手头没有 Office 2007 可以对此进行测试,但我认为应该可以。

【讨论】:

  • 这也是一个解决方案,但我认为不是最好的解决方案,因为它添加了用户需要访问的另一个工作簿。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多