我正在尝试构建一个宏,它将从我的工作表中的单元格中读取日期,并根据输入到用户窗体中的日期(月份和年份)对其进行检查。为此,我让用户窗体调用Report
在 中找到的Sub Sheet1
。表示用户输入的月份和年份的整数将随调用一起传递。
错误总是发生在Call Sheet1.Report(intMonth, intYear)
调用 的行上: Report
。错误内容为:Run-time error '1004': Application-defined or object-defined error
。
这是我的删节代码,从用户表单开始:
Private Sub cmdOK_Click()
'Transform month field into an integer (1-12)
Dim intMonth As Integer
Select Case cboMonth.Value
Case Is = strJan 'January - 01
intMonth = 1
Case Is = strFeb 'February - 02
intMonth = 2
Case Is = strMar 'March - 03
intMonth = 3
'and so on...
End Select
'Read year field as an Integer
Dim intYear As Integer
intYear = txtYear.Value
Call Sheet1.Report(intMonth, intYear)
End Sub
接下来,这是来自的代码Report
。它目前还不完整,因为我无法通过调用。正如我之前提到的,我总是在调用行上遇到错误:Call Sheet1.Report(intMonth, intYear)
。
Public Sub Report(myMonth As Integer, myYear As Integer)
'Some incomplete code...
'Like I said, the macro never gets past the call.
End Sub
有什么办法可以解决这个问题吗?任何帮助都非常感谢。谢谢!
答案1
现在您已经证明您的代码符合我的建议:
ThisWorkbook.Worksheets("Sheet1").Report intMonth, intYear
让我们听取 Mathieu 的建议。单击 VBE 窗口的项目视图中的工作表对象:
VBAProject > Microsfot Excel 对象 > Sheet1 (Sheet1)
第一部分是工作表对象名称,括号中的第二部分是 Excel 选项卡中显示的工作表名称。从 VBE 中的“视图”下拉菜单或按 F4 调出属性窗口视图。工作表属性窗口中的第一项应该是 (name),这是您将在代码中调用的对象名称。将其更改为描述性名称,例如“Report”。然后为您的宏使用描述性名称,例如“Update”。
现在您可以通过调用以下命令来创建新报告:
Report.Update intMonth, intYear
我使用这个命名约定是因为我认为您的宏会更新报告表。您也可以听取他关于“模型-视图-演示者”代码样式的建议,但这有点超出您的问题范围。
答案2
让UserForm
的默认实例运行也许是最容易的事情,但它也是很多问题的直接原因——从容易引入但难以发现的错误到维护和可扩展性问题:“快速、有效”的解决方案是“智能 UI”模式,它对于原型. 随着时间的推移,规模不断扩大的大型项目需要更加智能的架构。
程序员称之为“模型-视图-演示者”。看法是形式。数据是模型,然后是主持人协调一切。
从用户窗体调用 Sheet Sub
事实是,你不需要。情态动词UserForm
是对话,其作用无非就是收集用户的输入。通过让它只负责操作数据,而让宏/调用者负责控制流,您可以使代码更加健壮且更易于维护 - 尤其是当表单可以做很多事情时。
从一个简单的类模块开始MonthlyReportParams
:
Option Explicit
Public Month As Integer ' encapsulate into properties to implement
Public Year As Integer ' logic for validation on assignment.
Public Property Get IsValid() As Boolean
IsValid = Month >= 1 And Month <= 12 And _
Year >= 1900 And Year <= 2100
End Property
现在要做UserForm
的就是处理这些数据,模型。
Option Explicit
Private params As MonthlyReportParams
Private cancelled As Boolean
Private Sub Class_Initialize()
Set params = New MonthlyReportParams
End Sub
Public Property Get Model() As MonthlyReportParams
Set Model = params
End Property
Public Property Set Model(ByVal value As MonthlyReportParams)
Set params = value
MonthBox.value = params.Month
YearBox.value = params.Year
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub MonthBox_Change()
' make sure the textboxes contain numeric values before assigning to Integer
If IsNumeric(MonthBox.Value) Then params.Month = CInt(MonthBox.Value)
OnValidate
End Sub
Private Sub YearBox_Change()
' make sure the textboxes contain numeric values before assigning to Integer
If IsNumeric(YearBox.Value) Then params.Year = CInt(YearBox.Value)
OnValidate
End Sub
Private Sub OkButton_Click()
Me.Hide
End Sub
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub OnCancel()
cancelled = True
Me.Hide
End Sub
Private Sub OnValidate()
OkButton.Enabled = Model.IsValid
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
' runs when form is just about to close
If CloseMode = VbQueryClose.vbFormControlMenu Then
' user clicked the [X] button
Cancel = True ' don't destroy the form
OnCancel
End If
End Sub
现在,调出此表单的宏可以重新控制正在发生的事情:表单不再运行节目,我们可以在一个地方读取正在发生的所有事情:
Public Sub RunMonthlyReport(Optional ByVal targetSheet As Worksheet = Nothing)
If targetSheet Is Nothing Then
' no sheet was specified; work of the ActiveSheet
Debug.Assert Not ActiveSheet Is Nothing
Set targetSheet = ActiveSheet
End If
' create the model
Dim m As MonthlyReportParams
Set m = New MonthlyReportParams
m.Month = Month(Now)
m.Year = Year(Now)
' create the dialog, assign the model
With New MonthlyReportParamsDialog
Set .Model = m
.Show ' next line only runs after dialog has closed
If Not .IsCancelled Then
' run the report with the values in the model
targetSheet.Report m.Month, m.Year
End If
End With
End Sub
关于这种“责任逆转”的好处的更多信息,可以找到在本文中以及进一步的回调逻辑在本文中- 免责声明:我写了这两个博客;该博客是橡皮鸭VBIDE 插件 OSS 项目,我拥有。
答案3
HackSlash 回答了我的问题:
您的代码示例对我有用。请记住,您使用“Sheet1”作为对象是工作表对象名称,而不是实际 Excel 工作表选项卡上显示的工作表名称。如果您想通过工作表名称调用它,请尝试以下操作:ThisWorkbook.Worksheets("Sheet1").Report intMonth, intYear 注意:如果删除括号,则不需要 Call 语句。– HackSlash 7 分钟前