Vba 如何在处理表时编写清晰且可维护的代码?
在我的项目中,我经常利用表、底层的ListObjects和ListColumns。我喜欢它们,因为它们比裸范围对象更容易引用和更新。然而,我仍然没有找到一种合理且可维护的方法来处理由许多ListColumn组成的多个ListObject,并且在项目中的所有工作表中都被引用 假设我有一个工作表(将(Name)属性设置为“WorksheetA”),其中包含一个表(称为TableA),该表只有很少的列(称为Column1、Column2、…、Column10) 现在我想引用另一个工作表代码中的一列。我可以这样做:Vba 如何在处理表时编写清晰且可维护的代码?,vba,excel,worksheet,listobject,Vba,Excel,Worksheet,Listobject,在我的项目中,我经常利用表、底层的ListObjects和ListColumns。我喜欢它们,因为它们比裸范围对象更容易引用和更新。然而,我仍然没有找到一种合理且可维护的方法来处理由许多ListColumn组成的多个ListObject,并且在项目中的所有工作表中都被引用 假设我有一个工作表(将(Name)属性设置为“WorksheetA”),其中包含一个表(称为TableA),该表只有很少的列(称为Column1、Column2、…、Column10) 现在我想引用另一个工作表代码中的一列。我
WorksheetA.ListObjects("TableA").ListColumns("Column7")
现在,直接使用字符串是一种不好的做法,因为它很难维护并且容易出错
那现在呢
我可以创建专用模块,将字符串存储为常量。例如,名为“常量”的模块:
然后,我的引用可以转换为:
WorksheetA.ListObjects(Constants.TABLE_A).ListColumns(Constants.COLUMN7)
但是,此解决方案有一些缺点:
Private Const TABLE_A As String = "TableA"
Private Const COLUMN7 As String = "Column7"
Public Function GetTableAName() As String
GetTableAName = TABLE_A
End Function
Public Function GetTableA() As ListObject
Set GetTableA = WorksheetA.ListObjects(TABLE_A)
End Function
Public Function GetTableAColumn7() As ListColumn
Set GetTableAColumn7 = GetTableA().ListColumns(COLUMN7)
End Function
这个解决方案实际上解决了上面提到的所有三个问题,但仍然有点“脏”和耗时,因为添加一个新表会引入为每个列创建函数的需求
你知道如何处理这个问题吗
EDIT1(为清楚起见):假设用户不得更改任何名称(表名和列名)。如果用户这样做,他/她应该受到责备
EDIT2(为清晰起见):我仅以Column7作为列名作为示例。让我们假设列的名称更有意义。这是我的两分钱。我不是一个受过教育的程序员,但我确实为此得到了报酬,所以我想这会让我变得专业 第一道防线是我创建一个类来建模一个表。我从表中填充类,没有其他代码知道数据的位置。当我初始化时,我将运行如下代码
clsEmployees.FillFromListObject wshEmployees.ListObjects(1)
然后在类中,代码如下所示
vaData = lo.DataBodyRange.Value
...
clsEmployee.EeName = vaData(i,1)
clsEmployee.Ssn = vaData(i,2)
etc
每个工作表只有一个ListObject。这是我的规矩,我从不违反。任何有权访问工作表的人都可以重新排列列并打断我的代码。如果我想使用Excel作为数据库,有时我会这样做,那么这就是我要冒的风险。如果它是如此关键以至于我不能承担风险,那么我将数据存储在SQL Server、SQLite或JET中
我实际上可以调用ListColumns名称,而不是将范围放入数组中。这样,如果有人重新排列了列,我的代码仍然可以工作。但它引入了他们可以重命名列,所以我只是在用一种风险换另一种风险。这将使代码更具可读性,因此这可能是您想要进行的交易。我喜欢数组填充的速度,所以这就是我做的交易
如果我的项目足够小,或者应该直接使用ListObjects,那么我将遵循与任何字符串相同的规则
- 我在代码中只使用了一次字符串
- 如果我不止一次地使用它,我会使过程级别为常量
- 如果我在多个过程中使用它,我会尝试将其作为参数传递
- 如果我不能将其作为参数传递,我将使模块级别为常量
- 如果这两个过程在不同的模块中,我首先问自己,为什么这两个过程在使用相同常数的不同模块中。相关程序不应该在同一模块中吗
- 如果这两个过程真的属于不同的模块,那么我尝试将其作为参数传递
- 如果这些都不起作用,那么它确实是一个全局常量,我在我的MGlobals模块中设置了它
如果MGlobals占据了超过半个屏幕,我就做错了,我需要后退一步,仔细想想我要完成什么。然后我创建一个自定义类。有趣的问题。如果有一个简单的解决方案,我想我们会看到它应用于数据访问库,如DAO、ADO、JDBC等。开发人员要封装这些“字段名”和“表名”,唯一能做的就是编写“持久”类,即每个表或数据对象一个类。这是“干净”的解决方案,但它增加了一层,而且很耗时。建议用于大型数据库项目,但我怀疑Excel项目是否会达到如此规模,以致于此解决方案变得非常必要。我猜您担心在字符串方法中,用户可能会更改表名,从而破坏引用。如果工作表上只有一个表,则可以使用索引号(1)而不是字符串。这仅对每张图纸最多一张表有效。不知道,如果这适用于你所有的床单。如果你的列名真的像
Column7
那样难以描述,那么为什么不通过数字索引来引用它们呢?我是常量的大力支持者,但我认为你的想法太过分了。我一点也不在乎这个。你做了很多工作却毫无益处。它不会阻止用户更改表或列的名称。然后就要看你想对这些列做什么了。我主要在VBA中使用数组。因此,您不必处理所有引号和对象引用,而是可以从数组中的结构化引用中获取列的所有值,如下所示:v=[Table1[Column7]]
您还可以创建一个通用的ListColumn返回函数。将ws-name、表名和列名传递给它,它将返回ListColumn对象。我不会在任何地方定义这些东西的名称。
vaData = lo.DataBodyRange.Value
...
clsEmployee.EeName = vaData(i,1)
clsEmployee.Ssn = vaData(i,2)
etc