我们不想在编辑实际模板时在Open
或BeforeClose
事件(或任何其他)中运行代码。.xltm
这应该有助于:
Public Function IsTemplate() As Boolean
IsTemplate = (ThisWorkbook.FileFormat = xlOpenXMLTemplateMacroEnabled)
End Function
我们只需If IsTemplate() Then Exit Sub
在每个事件方法的开头添加即可。
使用模板创建新文件后,有两种保存方式。由于文件被转换为插件,因此用户不能真正单击 Excel 本身中的“保存”按钮,但可以从 VBE 中或通过关闭 Excel 进行保存。
从 VB 编辑器保存
用户可以在编辑代码时按下 VBE 中的 Save 按钮或简单地从键盘上按 Ctrl+S。为此,我们可以使用一BeforeSave
对AfterSave
事件。像这样的东西:
Option Explicit
Private m_appFileFormat As XlFileFormat
'Utility for avoiding unwanted changes while trying to edit the actual template
Private Function IsTemplate() As Boolean
IsTemplate = (Me.FileFormat = xlOpenXMLTemplateMacroEnabled)
End Function
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
If IsTemplate() Then Exit Sub
If m_appFileFormat <> 0 Then
Application.DefaultSaveFormat = m_appFileFormat
m_appFileFormat = 0
End If
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If IsTemplate() Then Exit Sub
If SaveAsUI Then
If m_appFileFormat = 0 Then m_appFileFormat = Application.DefaultSaveFormat
Application.DefaultSaveFormat = xlOpenXMLAddIn
End If
End Sub
Private Sub Workbook_Open()
If IsTemplate() Then Exit Sub
If Not Me.IsAddin Then Me.IsAddin = True
End Sub
关闭 Excel
用户可能会单击 Excel 关闭按钮。这是您的问题实际指向的地方。
解决此问题的一种方法是更改Application.DefaultSaveFormat
fromBeforeClose
事件,这与我们现在需要一种恢复应用程序格式的方法的缺点相得益彰。更糟糕的是,用户可能会按下取消或不保存。
不幸的是,没有事件表明应用程序正在关闭或关闭已被取消。我唯一能想到的就是捕获状态损失。为此,您可以使用自己的subclassProc方法,但这需要太多样板文件。相反,我建议我们使用假对象并利用IUnknown::Release
.
在标准的“bas”模块中添加以下代码:
Option Explicit
#If Mac Then
Private Declare PtrSafe Function CopyMemory Lib "/usr/lib/libc.dylib" Alias "memmove" (Destination As Any, Source As Any, ByVal Length As LongPtr) As LongPtr
#Else
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
#End If
Private m_appFileFormat As XlFileFormat
Private Sub Release(ByVal instancePtr As LongPtr)
'Do not press Reset while in this method as that will "nuke" the application
If m_appFileFormat <> 0 Then Application.DefaultSaveFormat = m_appFileFormat
m_appFileFormat = 0
End Sub
Private Property Let MemLongPtr(ByVal memAddress As LongPtr, ByVal newValue As LongPtr)
#If Win64 Then
Const PTR_SIZE As Long = 8
#Else
Const PTR_SIZE As Long = 4
#End If
CopyMemory ByVal memAddress, newValue, PTR_SIZE
End Property
Public Sub RestoreAppFileFormatAtStateLoss(ByVal appFileFormat As XlFileFormat)
If m_appFileFormat <> 0 Then Exit Sub
Static o As Object
Static vtbl(0 To 2) As LongPtr
Static vtblPtr As LongPtr
'We only need Release, QueryInterface and AddRef and not useful
vtbl(2) = VBA.Int(AddressOf Release)
'Point to vTable
vtblPtr = VarPtr(vtbl(0))
MemLongPtr(VarPtr(o)) = VarPtr(vtblPtr)
m_appFileFormat = appFileFormat
End Sub
模块中的最终代码ThisWorkbook
可以变成:
Option Explicit
Private m_appFileFormat As XlFileFormat
'Utility for avoiding unwanted changes while trying to edit the actual template
Private Function IsTemplate() As Boolean
IsTemplate = (Me.FileFormat = xlOpenXMLTemplateMacroEnabled)
End Function
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
If IsTemplate() Then Exit Sub
If m_appFileFormat <> 0 Then
Application.DefaultSaveFormat = m_appFileFormat
m_appFileFormat = 0
End If
End Sub
Private Sub Workbook_BeforeClose(ByRef Cancel As Boolean)
If IsTemplate() Then Exit Sub
If Me.Saved Then Exit Sub
If Me.Path = vbNullString Then
RestoreAppFileFormatAtStateLoss Application.DefaultSaveFormat
Application.DefaultSaveFormat = xlOpenXMLAddIn
Else
Select Case MsgBox("Wanna save before you quit?", vbQuestion + vbYesNoCancel, "Unsaved Changes")
Case VbMsgBoxResult.vbYes
Me.Save
Case VbMsgBoxResult.vbCancel
Cancel = True
End Select
End If
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If IsTemplate() Then Exit Sub
If SaveAsUI Then
If m_appFileFormat = 0 Then m_appFileFormat = Application.DefaultSaveFormat
Application.DefaultSaveFormat = xlOpenXMLAddIn
End If
End Sub
Private Sub Workbook_Open()
If IsTemplate() Then Exit Sub
If Not Me.IsAddin Then Me.IsAddin = True
End Sub
我已经测试了几个场景,似乎Application.DefaultSaveFormat
恢复正确。抱歉,如果我错过了任何内容。