是否可以使用VBA自定义类的有状态默认实例?

阴极船长

我正在开发一些与财务相关的Excel加载项,供我的商业团队中的一小组使用。

我想创建一个持久对象,该对象存储与应用程序相关的特定元数据值的状态,以便任何类或标准模块中的代码都可以访问它们,因此我只需要声明一次或在应用程序启动时设置其默认值。示例包括:

  • 应用名称
  • 发行/版本号
  • 用于存储/检索设置的顶级注册表项
  • “首次运行”状态
  • 正在启用日志记录

在过去,我会为这些事情声明全局常量,但是我试图使用一种最新的模式(如果有的话)来解决这个问题。

我的想法是创建一个PredeclaredID属性设置为true的类,并在工作簿加载和/或类初始化事件中填充默认值。

我知道在类的默认实例中维护状态不是一种好的做法,但是我想不出另一种创建持久对象的方法。我对工厂模式有些了解,很乐意使用一种工厂模式,但是持久性却让我失望。

任何见解或替代方法表示赞赏。


更新2020年6月10日

我在这种方法上取得了一些成功。这是我一直在研究的原型,结合了freeflow的有用建议。

类属性和属性过程

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "AppSettings"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Attribute VB_Description = "Persistent object that exposes app-specific settings to other procedures and objects"
'@Folder "MyAddin.02 AddIn"
'@ModuleDescription ("Persistent object that exposes app-specific settings to other procedures and objects")
'@PredeclaredId
Option Explicit

Private Type TAppSettings
    Name As String
    LoggingEnabled As Boolean
    Author As String
    AuthorEmail As String
    ReleaseName As String
    Version As String
    TopLevelRegKey As String
    AppFirstRun As Boolean
    LogPath As String
End Type

Private this As TAppSettings

Public Property Get Name() As String
    Name = this.Name
End Property

Public Property Get LoggingEnabled() As Boolean
    LoggingEnabled = this.LoggingEnabled
End Property

Public Property Let LoggingEnabled(ByVal value As Boolean)
    this.LoggingEnabled = value
End Property

Public Property Get Author() As String
    Author = this.Author
End Property

Public Property Get AuthorEmail() As String
    AuthorEmail = this.AuthorEmail
End Property

Public Property Get ReleaseName() As String
    ReleaseName = this.ReleaseName
End Property

Public Property Get Version() As String
    Version = this.Version
End Property

Public Property Get TopLevelRegKey() As String
    TopLevelRegKey = this.TopLevelRegKey
End Property

Public Property Get AppFirstRun() As Boolean
    AppFirstRun = this.AppFirstRun
End Property

Public Property Let AppFirstRun(ByVal value As Boolean)
    this.AppFirstRun = value
End Property

bootstrapper:此过程将应用程序的默认值传递给Populate方法,并从类Initialize事件中调用这些属性在此处硬编码为常量,但可以从工作簿自定义属性或外部设置文件中获取。

'@Description "Sets properties of default AppSettings object. Called by class initialise event"
Private Sub BootStrapper()

    Const APP_NAME As String = "MyTestApp"
    Const APP_AUTHOR As String = "John Smith"
    Const APP_AUTHOR_EMAIL As String = "[email protected]"
    Const APP_RELEASE_NAME As String = "R1"
    Const APP_VERSION As String = "V1.0"
    Const APP_FIRSTRUN_DEFAULT As Boolean = True
    Const APP_LOGGING_DEFAULT As Boolean = False

    Dim success As Boolean

    On Error Resume Next
    success = Me.Populate( _
              Author:=APP_AUTHOR, _
              AuthorEmail:=APP_AUTHOR_EMAIL, _
              Name:=APP_NAME, _
              ReleaseName:=APP_RELEASE_NAME, _
              Version:=APP_VERSION, _
              AppFirstRunDefault:=APP_FIRSTRUN_DEFAULT, _
              AppLoggingDefault:=APP_LOGGING_DEFAULT)
    On Error GoTo 0

    If Not success Then
    Err.Raise _
        vbObjectError + 1024, _
        "AppSettings.Bootstrapper", _
        "Failed to bootstrap the static AppSettings class"
    End If

End Sub

Populate方法:设置类属性,创建顶级注册表项,并在注册表中读取/存储读取/写入属性的设置。

'@Description "Populates AppSettings object properties"
Friend Function Populate( _
       ByVal Author As String, _
       ByVal AuthorEmail As String, _
       ByVal Name As String, _
       ByVal ReleaseName As String, _
       ByVal Version As String, _
       ByVal AppFirstRunDefault As Boolean, _
       ByVal AppLoggingDefault As Boolean) As Boolean

    Populate = False
    On Error Resume Next                         'Handle any errors by function return value

    ' Populate properties for those values passed in to the function
    With this
        .Author = Author
        .AuthorEmail = AuthorEmail
        .Name = Name
        .ReleaseName = ReleaseName
        .Version = Version
        .TopLevelRegKey = .ReleaseName & "\" & .Version
    End With

    ' Populate the AppFirstRun and LoggingEnabled settings from the registry
    ' If registry settings don't exist then this is the true first run so set defaults
    Dim registryValue As String
    With this
        registryValue = GetSetting(.Name, .TopLevelRegKey, "AppFirstRun", vbNullString)
        If registryValue = vbNullString Then     'Set default if no registry value
            .AppFirstRun = AppFirstRunDefault
            SaveSetting .Name, .TopLevelRegKey, "AppFirstRun", CStr(AppFirstRunDefault)
        Else
            .AppFirstRun = CBool(registryValue)
        End If
        registryValue = GetSetting(.Name, .TopLevelRegKey, "LoggingEnabled", vbNullString)
        If registryValue = vbNullString Then     'Set default if no registry value
            .LoggingEnabled = AppLoggingDefault
            SaveSetting .Name, .TopLevelRegKey, "LoggingEnabled", CStr(AppLoggingDefault)
        Else
            .LoggingEnabled = CBool(registryValue)
        End If
    End With

    If Err.Number = 0 Then Populate = True
    On Error GoTo 0

End Function

初始化过程:该Class_Initialize代码检查调用方是否在尝试新建该类的另一个实例,如果没有,则调用引导程序。如果对象超出范围(例如通过Set AppSettings = Nothing),则在重新生成默认实例时会自动重新填充属性。

Private Sub Class_Initialize()
' AppSettings is a stateful default instance singleton class
' Prevent additional instance creation with "New"

    If Not Me Is AppSettings Then
        Err.Raise _
            vbObjectError + 1024, _
            "Invalid Object Instance", _
            TypeName(Me) & ": New is not permitted on Static Classes"
    End If

    'Call BootStrapper procedure to auto-initialise properties
    BootStrapper

End Sub
自由流动

完全可以接受使用带有PredeclaredId的Class来存储状态,前提是它是该类的唯一实例。确保这一点的一种方法是使用Class_Initialize子项来测试当前实例是否为PredeclaredId,如果不是,则进行错误提示(例如,有人尝试使用PredecalredId新建类。

例如,对于名为PersistentData的类

Private Sub Class_Initialize()

    If Not Me Is PersistentData Then

        Err.Raise _
            vbObjectError + 17, _
            "Invalid New", _
             TypeName(Me) & ": New is not permitted on Static Classes"

    End If

    'Add other class specific initialisation here

End Sub

要记住的一个好点是,Class_Initialize子程序将在表达式中首次遇到PersistentData时运行。此动作可用于“自动”初始化类。

还应该建议您,出色的VBA Rubberduck插件提供注释,该注释允许从代码本身内设置PredeclaredId,而不是在将代码导出到文本编辑器后手动设置。

还有其他用于存储此类数据的选项,例如文档变量,自定义文档属性和customxml。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

是否可以使用自定义颜色通过VBA填充单元格?

来自分类Dev

是否可以使用 PyQt 创建自定义光标?

来自分类Dev

是否可以使用 App 自定义结账液

来自分类Dev

Android XML是否可以使用带有自定义属性的工具名称空间?

来自分类Dev

是否可以使用Keras从自定义损失函数调用/使用实例属性或全局变量?

来自分类Dev

是否可以使用我的自定义序列化或使用Externalization来序列化类的瞬态字段?

来自分类Dev

是否可以使用CDI注入没有生产者方法的自定义JBoss模块?

来自分类Dev

是否可以使用现有的Singleton实例修补类实例?

来自分类Dev

是否可以使用默认模板制作的github页面还按原样从同一存储库呈现自定义html文件?

来自分类Dev

是否可以使用地图v2的默认ZoomControls放大/缩小自定义的ZoomLevel(例如0.5x)?

来自分类Dev

使用WordPress在自定义表格中是否可以使用LEFT JOIN替代方法?

来自分类Dev

是否可以使用JavaScript使用formstack创建自定义计算?

来自分类Dev

值类具有自定义的不适用,是否将其实例化?

来自分类Dev

是否可以使 Asana 自定义字段成为链接?

来自分类Dev

是否可以使用自定义标题和内容打开新的浏览器窗口?

来自分类Dev

是否可以自定义WPF视图创建机制以使用我自己的ViewFactory?

来自分类Dev

是否可以使用EF7执行自定义SQL查询

来自分类Dev

是否可以使用对自定义结构的FILE *起作用的函数?

来自分类Dev

是否可以使用javascript动态将字体更改为自定义字体?

来自分类Dev

Spring Integration TCP中是否可以使用自定义标头?

来自分类Dev

是否可以使用linq2db添加自定义列?

来自分类Dev

是否可以使用其他布局算法创建自定义的<ul>?

来自分类Dev

是否可以自定义WPF视图创建机制以使用我自己的ViewFactory?

来自分类Dev

是否可以使用C#(.net)创建Cloud Watch自定义指标?

来自分类Dev

WatchOS并发症:是否可以使用自定义文本对齐方式?

来自分类Dev

Google 标签管理器 - 是否可以使用自定义 clientID 推送数据层

来自分类Dev

是否可以使用自定义证书对 osx 应用程序进行协同设计?

来自分类Dev

MVC TextBoxFor type "date" 是否可以使用自定义格式?

来自分类Dev

是否可以使用自定义属性来强制执行方法签名

Related 相关文章

  1. 1

    是否可以使用自定义颜色通过VBA填充单元格?

  2. 2

    是否可以使用 PyQt 创建自定义光标?

  3. 3

    是否可以使用 App 自定义结账液

  4. 4

    Android XML是否可以使用带有自定义属性的工具名称空间?

  5. 5

    是否可以使用Keras从自定义损失函数调用/使用实例属性或全局变量?

  6. 6

    是否可以使用我的自定义序列化或使用Externalization来序列化类的瞬态字段?

  7. 7

    是否可以使用CDI注入没有生产者方法的自定义JBoss模块?

  8. 8

    是否可以使用现有的Singleton实例修补类实例?

  9. 9

    是否可以使用默认模板制作的github页面还按原样从同一存储库呈现自定义html文件?

  10. 10

    是否可以使用地图v2的默认ZoomControls放大/缩小自定义的ZoomLevel(例如0.5x)?

  11. 11

    使用WordPress在自定义表格中是否可以使用LEFT JOIN替代方法?

  12. 12

    是否可以使用JavaScript使用formstack创建自定义计算?

  13. 13

    值类具有自定义的不适用,是否将其实例化?

  14. 14

    是否可以使 Asana 自定义字段成为链接?

  15. 15

    是否可以使用自定义标题和内容打开新的浏览器窗口?

  16. 16

    是否可以自定义WPF视图创建机制以使用我自己的ViewFactory?

  17. 17

    是否可以使用EF7执行自定义SQL查询

  18. 18

    是否可以使用对自定义结构的FILE *起作用的函数?

  19. 19

    是否可以使用javascript动态将字体更改为自定义字体?

  20. 20

    Spring Integration TCP中是否可以使用自定义标头?

  21. 21

    是否可以使用linq2db添加自定义列?

  22. 22

    是否可以使用其他布局算法创建自定义的<ul>?

  23. 23

    是否可以自定义WPF视图创建机制以使用我自己的ViewFactory?

  24. 24

    是否可以使用C#(.net)创建Cloud Watch自定义指标?

  25. 25

    WatchOS并发症:是否可以使用自定义文本对齐方式?

  26. 26

    Google 标签管理器 - 是否可以使用自定义 clientID 推送数据层

  27. 27

    是否可以使用自定义证书对 osx 应用程序进行协同设计?

  28. 28

    MVC TextBoxFor type "date" 是否可以使用自定义格式?

  29. 29

    是否可以使用自定义属性来强制执行方法签名

热门标签

归档