vba 界面
Modules.zip (Modules.zip)
目录 (Table of Contents)
1 About CC Interface V1.0
1关于CC Interface V1.0
1.1 Features of CCInterface
1.1 CCInterface的功能
1.2 Requirements
1.2要求
2 General information
2一般信息
2.1 What is an "Interface"?
2.1什么是“接口”?
2.2 What does that mean for programmers?
2.2这对程序员意味着什么?
2.2.1 Example Interface
2.2.1示例界面
2.2.2 Example Module Implementing the Interface
2.2.2实现接口的示例模块
2.2.3 Example Module using the Interface
2.2.3使用接口的示例模块
2.2.4 Advantages of Interfaces
2.2.4接口的优点
2.3 Why an interface can be useless in VBA
2.3为什么接口在VBA中可能无用
2.3.1 Issues with "Implements"
2.3.1有关“实施”的问题
2.3.2 Possible Methods to Workaround
2.3.2可能的解决方法
2.4 Enough of Workarounds
2.4足够的解决方法
3 How to use
3使用方法
3.1 How it works
3.1工作原理
3.1.1 The Idea
3.1.1想法
3.1.2 How to Use
3.1.2使用方法
1. Adjusting VBA References
1.调整VBA参考
2. Using in your own Access file
2.在您自己的Access文件中使用
3. First create the interface wrapper class module
3.首先创建接口包装器类模块
4. Second rewrite the code in the class module using the interface
4.其次,使用接口在类模块中重写代码
5. Third rewrite the code using the interface anywhere
5.第三步,使用任何地方的接口重写代码
6. Final work
6.最后工作
1.关于CC Interface V1.0 (1. About CC Interface V1.0)
The purpose of CCInterface is to completely replace the standard interface technology built in into VBA. Not that the interfaces which you can insert into your code using the "Implements" statement are not a really useful technology. It would be a very great feature if it would work without problems. Unfortunately at least with Access 2010 (and maybe later versions) interfaces in VBA are full of bugs and can destroy your project.
CCInterface的目的是完全替代VBA中内置的标准接口技术。 并不是说您可以使用“ Implements”语句将其插入代码中的接口并不是真正有用的技术。 如果它可以正常工作,那将是一个非常好的功能。 不幸的是,至少在Access 2010(可能还有更高版本)中,VBA中的接口充满了错误,并可能破坏您的项目。
This is where the CCInterface module comes in whose purpose is to allow using interfaces without the "Implements" statement so that your code is stable without loosing the possibility of using interfaces.
这是CCInterface模块进入的地方,其目的是允许使用不带“ Implements”语句的接口,以便您的代码稳定而不丢失使用接口的可能性。
(
)
1.1 CCInterface的功能 (1.1 Features of CCInterface)
- Checks the implementation of an interface in a class module检查类模块中接口的实现
- Converts a class module by changing the normally as "Private" declared objects (subs, function, properties) into "Public" declarations通过将通常作为“私有”声明的对象(子,函数,属性)更改为“公共”声明来转换类模块
- Creates a new interface wrapper class module and inserts the required code into this wrapper class and also into the class module which uses the interface创建一个新的接口包装器类模块,并将所需的代码插入此包装器类以及使用该接口的类模块中
- Disables the "Implements" statement with a comment character (')禁用带有注释字符(')的“实施”语句
(
)
1.2要求 (1.2 Requirements)
- Needs a reference to "Microsoft Visual Basic for Applications Extensibility" (only during design time)需要对“ Microsoft Visual Basic应用程序可扩展性”的引用(仅在设计时)
- Please always make a backup of your database file before trying to use CCInterface!尝试使用CCInterface之前,请始终备份数据库文件!
(
)
2一般信息 (2 General information)
2.1什么是“接口”? (2.1 What is an "Interface"?)
An interface object is an object that should be used by different other objects for the same purpose. It gives these different objects the opportunity to be used in a specific standard way.
接口对象是同一目的应由其他不同对象使用的对象。 它为这些不同的对象提供了以特定的标准方式使用的机会。
To give an example from the real world: If you want to listen to your favorite music you need a device which outputs sound. This can be, for example, a pair of loudspeakers or a headphone. These devices cannot produce music, they only offer the ability to output sound.
举例说明:如果您想听自己喜欢的音乐,则需要一台可以输出声音的设备。 例如,这可以是一对扬声器或耳机。 这些设备无法产生音乐,只能提供输出声音的功能。
So you now also need a device that can play music. This could be a CD player or an MP3 player. Both have no device to output the music as sound so you need your headphones or loudspeakers. To get them connected, you need an object between them, a phone jack plug and a socket. (So these are in fact two interfaces, the plug on the speaker side and the socket on the player side. To not make it too complicated we handle it as one interface here.)
因此,您现在还需要可以播放音乐的设备。 这可能是CD播放器或MP3播放器。 两者都没有将音乐输出为声音的设备,因此您需要耳机或扬声器。 为了使它们连接,您需要在它们之间有一个物体,一个电话插Kong插头和一个插座。 (因此,实际上这是两个接口,扬声器侧的插头和播放器侧的插座。为了不至于太复杂,我们在这里将其作为一个接口来处理。)
The interface has itself no functionality. It cannot produce sound and it cannot play. But it offers both objects (two players and two speaker systems here) the same standard possibilities; to be connected with each other and to forward the sound signal to the output.
该接口本身没有任何功能。 它不能产生声音,也不能播放。 但是它为两个对象(此处有两个播放器和两个扬声器系统)提供了相同的标准可能性; 相互连接并将声音信号转发到输出。
So the interface object offers to the player a way to be connected to speakers or headphones, and in the same way it offers to the headphones a way to use an MP3 or CD player (and a lot of others). All players and all output devices will use the same interface so the manufacturers of an output device don't need to think about what player it will be interfaced with nor do the manufacturers of the players need to think about which output device will be connected.
因此,接口对象为播放器提供了一种连接扬声器或耳机的方式,并且以相同的方式为耳机提供了使用MP3或CD播放器(以及许多其他方式)的方式。 所有播放器和所有输出设备将使用相同的接口,因此输出设备的制造商无需考虑将与之连接的播放器,播放器的制造商也无需考虑将连接哪个输出设备。
2.2这对程序员意味着什么? (2.2 What does that mean for programmers?)
An interface in a programming language like VBA is a collection of procedures (subs, functions or properties). In opposite to normal class modules it usually doesn't contain its own code, it only contains the empty procedures with all parameters, as needed.
像VBA这样的编程语言的接口是过程(子,函数或属性)的集合。 与普通类模块相反,它通常不包含自己的代码,仅包含根据需要带有所有参数的空过程。
The sense of an interface is to provide what the name says: an interface, not code. It should only describe the interface generally without being itself an active object.
接口的含义是提供名称,即:接口而不是代码。 它仅应一般地描述接口,而不能成为活动对象。
That means: If you create an MP3 player class then your class module provides the functionality to output audio from a given file. Your interface class module (an interface is always a class module) is a 3.5mm phone jack which should be used from an output class. The interface module itself is a "dead" module which itself does simply nothing. It is also named a "contract" between the one and the other class module which wants to use it - therefore it doesn't contain any code.
这意味着:如果创建MP3播放器类,则您的类模块提供了从给定文件输出音频的功能。 接口类模块(接口始终是类模块)是一个3.5毫米电话插Kong,应从输出类中使用。 接口模块本身是一个“死”模块,它本身什么也不做。 它也被称为要使用它的一个和另一个类模块之间的“合同”-因此它不包含任何代码。
2.2.1示例界面 (2.2.1 Example Interface)
Option Compare Database
Option Explicit
' This should be used to inform the objects that the
' program should be closed now.
Public Sub BeforeQuitProgram(ByRef Cancel As Boolean)
End Sub
Create a new class module in the VBA editor and insert the code above. We name it "IStdFunctions" here (usually interface class modules are named with a big "I" at the beginning of the name).
在VBA编辑器中创建一个新的类模块,并插入上面的代码。 我们在这里将其命名为“ IStdFunctions”(通常,接口类模块在名称的开头以大“ I”命名)。
This should be used to tell the using object module that we want to quit the application before it really does. Moreover it contains a "Cancel" parameter which should give the using object the opportunity to avoid that the program will be closed (maybe because the user needs to decide what to do with unsaved data).
这应该用于告诉使用对象模块我们要在应用程序真正退出之前退出它。 此外,它包含一个“取消”参数,该参数应使使用对象有机会避免程序被关闭(可能是因为用户需要决定如何处理未保存的数据)。
You have now created your interface. Really. That's it, nothing else. That was easy.
现在,您已经创建了界面。 真。 就是这样,仅此而已。 那很简单。
(
)
2.2.2实现接口的示例模块 (2.2.2 Example Module Implementing The Interface)
Now how you can insert the new "phone jack" into your module?
现在如何将新的“电话插Kong”插入模块?
At first, using an interface is only possible to class modules. Fortunately these are not only pure class modules but also forms and reports which have a code module - these are also class modules (that's the reason why you can open more than one equal form at the same time).
首先,只能使用接口对模块进行分类。 幸运的是,这些不仅是纯类模块,而且还有具有代码模块的表单和报表-它们也是类模块(这就是为什么您可以同时打开多个相等表单的原因)。
So we create a blank form now, name it "frmTest" and set the "Has Module" property of the form to "Yes" which immediately creates the class module which you can see in the list in the VBA editor (Form_frmTest).
因此,我们现在创建一个空白表单,将其命名为“ frmTest”,并将表单的“ Has Module”属性设置为“ Yes”,这将立即创建类模块,您可以在VBA编辑器(Form_frmTest)的列表中看到该模块。
The code for the form is also not complicate for now:
表单的代码目前还不复杂:
Option Compare Database
Option Explicit
Implements IStdFunctions
The code contains only the "Implements" statement which tells VBA that the interface class "IStdFunctions" should be used in this class module.
该代码仅包含“ Implements”语句,该语句告诉VBA在该类模块中应使用接口类“ IStdFunctions”。
Try to compile your VBA code (using the "Debug" menu) now and you'll get an error message which says that you need to insert the "BeforeQuitProgram" sub in your form's class module (it will only tell you about the first missing procedure, so if your interface module will have more procedures it reports about the next missing code in the next try to compile if you only inserted this one).
立即尝试编译VBA代码(使用“调试”菜单),您将收到一条错误消息,提示您需要在表单的类模块中插入“ BeforeQuitProgram”子项(它只会告诉您第一个缺失项程序,因此如果您的接口模块将有更多的程序,它将在下一次尝试编译(如果仅插入此代码)时报告下一个缺少的代码。
So what "Implements" also does is that it tells the compiler which procedures needs to be implemented. You can find a list if you open the left drop-down list in the VBA editor where you'll find "IStdFunction" (because of the "Implements" statement) which you need to select. Then you'll find all procedures of the interface in the right drop-down list:
因此,“实现”的作用还在于它告诉编译器需要执行哪些过程。 如果在VBA编辑器中打开左侧的下拉列表,则会找到一个列表,您会在其中找到需要选择的“ IStdFunction”(由于“ Implements”语句)。 然后,您将在右侧的下拉列表中找到该接口的所有过程:
Selecting the procedure will insert it into your form code now, but because we only have one it was already inserted by selecting the interface name. If you would have more you would need to select each entry one by one until all are inserted (inserted are displayed in bold font in the drop-down list).
选择该过程将立即将其插入到您的表单代码中,但是由于我们只有一个,因此已经通过选择接口名称将其插入。 如果您有更多选择,则需要一个一个一个地选择每个条目,直到全部插入(在下拉列表中插入的字体以粗体显示)。
This is the code which was inserted:
这是插入的代码:
Private Sub IStdFunctions_BeforeQuitProgram(Cancel As Boolean)
End Sub
You can now compile your project because now the contract is fulfilled, all procedures of your interface module are implemented into the form module now.
您现在可以编译项目,因为现在合同已履行,您接口模块的所有过程现在都已实现到表单模块中。
But if you read the code more thoroughly you'll see that there are differences between the form's code and the code in the interface module:
但是,如果您更彻底地阅读了代码,您会发现表单的代码和接口模块中的代码之间存在差异:
- The sub is declared as "Private"该子被声明为“私人”
- The name of the sub has the prefix "IStdFunctions_"子名称的前缀为“ IStdFunctions_”
This has the following reasons:
这有以下原因:
- It is "Private" because these procedures should not be seen externally. You can see that if you try to access "Me.IStdFunctions_BeforeQuitprogram" - IntelliSense will not show that in the list as "Me" also it is only able to see the "Public" procedures, the same as any external object trying to access it. Of course you can directly call the sub internally by leaving out "Me." as any other private sub in the form - but you shouldn't do that. Try to never call an interface procedure directly without using the interface in the way it should (explained later), in the same way as you never should directly call an event procedure like "Form_Load" and let the event happen with the forms loads organically. In both cases it is easy to be reached by creating an additional procedure and using it in Form_Load and the other procedure, the same with interface procedures.之所以称为“私有”,是因为这些过程不应从外部看到。 您可以看到,如果尝试访问“ Me.IStdFunctions_BeforeQuitprogram”,则IntelliSense不会在列表中将其显示为“ Me”,并且只能看到“ Public”过程,与尝试访问它的任何外部对象相同。 当然,您可以通过省略“我”直接在内部调用该子。 就像表格中的其他私人子标题一样-但您不应该这样做。 尝试永远不要直接调用接口过程而不以应有的方式使用接口(稍后说明),就像永远不要直接调用诸如“ Form_Load”之类的事件过程一样,并让事件以自然加载的形式发生。 在这两种情况下,都可以通过创建一个附加过程并在Form_Load和其他过程(与接口过程相同)中使用它来轻松实现。
- The prefix "IStdFunctions_" should make the procedure unique in your form's code. Maybe you want to implement another interface later which itself also has a sub "BeforeQuitProgram" - this would not be possible if you doesn't have a unique name.前缀“ IStdFunctions_”应使该过程在表单的代码中唯一。 也许您以后想实现另一个接口,该接口本身也有一个子“ BeforeQuitProgram”-如果您没有唯一的名称,则不可能。
(
)
2.2.3使用接口的示例模块 (2.2.3 Example Module Using The Interface)
So now you have an interface class module and a form which implements your interface. We now need a third module that wants to use the interface. This can also be a standard module so please create a standard module named "modTest" now.
因此,现在您有了一个接口类模块和一个实现您的接口的表单。 现在,我们需要第三个想要使用该接口的模块。 这也可以是标准模块,因此请立即创建一个名为“ modTest”的标准模块。
This is the code for the new standard module:
这是新标准模块的代码:
Option Compare Database
Option Explicit
Public Sub TestIStdFunctions()
Dim objIStdFunctions As IStdFunctions
Dim frm As Form_frmTest
DoCmd.OpenForm "frmTest"
Set frm = Forms("frmTest")
Set objIStdFunctions = frm
Stop
End Sub
Run the code now, it will break at the "Stop" command highlighting it.
现在运行代码,它将在“停止”命令突出显示时中断。
Now you can go to the immediate window and if you enter "objIStdFunctions." you will see the following:
现在,您可以进入立即窗口,如果输入“ objIStdFunctions”。 您将看到以下内容:
This is the only sub which is in the interface object so no surprise.
这是接口对象中唯一的子对象,因此不足为奇。
You can call that here now if you enter "objIStdFunctions.BeforeQuitProgram False". Nothing happens, because there's no code implementation anywhere. But what did we do in the test sub above?
如果输入“ objIStdFunctions.BeforeQuitProgram False”,则可以立即在此处调用。 什么也没发生,因为任何地方都没有代码实现。 但是我们在上面的测试子中做了什么?
First we declared two object variables, one as the interface class and one as the specific form class.
首先,我们声明了两个对象变量,一个是接口类,一个是特定表单类。
Then the form is opened and the "frm" variable is assigned to the opened form. You can now look into the IntelliSense list if you type "frm." into the immediate window, you'll not find the "IStdFunctions" as described above as it is declared "Private".
然后打开表单,并将“ frm”变量分配给打开的表单。 现在,如果您键入“ frm”,则可以查看IntelliSense列表。 在即时窗口中,找不到如上所述的“ IStdFunctions”,因为它被声明为“ Private”。
Now the speciality of the interface classes is used: We do not write "Set objIStdFunctions As New IStdFunctions" as we would expect for any normal class. Instead we assign the form reference of the loaded form which is in the "frm" variable to the object variable of the interface class. This couples all procedures which are in the form module belonging to the interface class to the object variable. So using "BeforeQuitProgram" doesn't start anything in the interface class "IStdFunction", instead it calls the (privately declared!) sub "IStdFunctions_BeforeQuitProgram" sub in the form module!
现在使用接口类的特殊性:我们不会像任何普通类那样编写“将objIStdFunctions设置为新的IStdFunctions”。 相反,我们将“ frm”变量中已加载表单的表单引用分配给接口类的对象变量。 这会将属于接口类的表单模块中的所有过程都耦合到对象变量。 因此,使用“ BeforeQuitProgram”不会在接口类“ IStdFunction”中启动任何内容,而是会调用表单模块中的(私下声明!)子项“ IStdFunctions_BeforeQuitProgram”!
That's cool, isn't it? You now have an alternative way of calling a list of procedures in a form. But that you could also have done by "normal" methods like declaring these procedures as "Public". So what are the advantages of using an interface now?
很好,不是吗? 现在,您可以使用另一种方法来调用表单中的过程列表。 但是,您也可以通过“正常”方法来完成,例如将这些过程声明为“公共”。 那么现在使用界面有什么好处?
(
)
2.2.4接口的优势 (2.2.4 Advantages Of Interfaces)
First, the interface is not dependent on your form. You can implement it into any other kind of class modules, too, so you could also insert that on the same way to a report or to a general class module.
首先,界面不依赖于您的表单。 您也可以将其实现到任何其他种类的类模块中,因此也可以以相同的方式将其插入报表或常规类模块中。
In the same way as the MP3 player uses the phone jack, the CD player or mobile phone or amplifier could insert the phone jack. But internally they may need to do different things to fulfil the "contract" with the interface; maybe the CD player must initiate the CD load, or the MP3 player needs to load a file and decode it, or the amplifier needs to look for the activated sound source and route that to the phone jack - but in the end the interface does the same for all devices: It outputs the sound exactly the same for each device.
与MP3播放器使用电话插Kong的方式相同,CD播放器或手机或放大器可以插入电话插Kong。 但是在内部,他们可能需要做一些不同的事情才能实现与界面的“合同”。 也许CD播放器必须启动CD加载,或者MP3播放器需要加载文件并对其进行解码,或者放大器需要寻找已激活的声源并将其路由到电话插Kong-但最后,接口会所有设备均相同:输出的声音与每个设备完全相同。
That's the same here; you want to tell different objects to be informed about quitting the program. So you could add the form reference "frm" and also a report reference "rpt" (created with OpenReport etc. on the same way) into a collection and then use this:
这里是一样的。 您想告诉其他对象有关退出程序的信息。 因此,您可以将表单引用“ frm”和报表引用“ rpt”(使用OpenReport等创建方式相同)添加到集合中,然后使用:
Dim bolCancel As Boolean
For Each objIStdFunctions In MyCollection
objIStdFunctions.BeforeQuitProgram bolCancel
If bolCancel then Exit Sub
Next
All completely different objects in the collection use the same functionality, they all can be used as interface objects and they all can decide how the functionality is implemented in their specific instances. The form may look for unsaved data and set the "Cancel" variable to "True" if the user must do anything here before stopping the program, the report may stop printing the report and clear the printer queue for example.
集合中所有完全不同的对象都使用相同的功能,它们都可以用作接口对象,并且它们都可以决定如何在其特定实例中实现该功能。 如果用户在停止程序之前必须在此处执行任何操作,则该表格可能会查找未保存的数据并将“ Cancel”变量设置为“ True”,例如,报告可能会停止打印报告并清除打印机队列。
The module using the interface doesn't need to know anything about the specific objects, it only uses the interface as this would be the only list of existing procedures. The specific object (like the form implementing the interface) doesn't need to know who has called the interface code and doesn't need to access it directly in any way (it can't as it has no reference to it). You could also provide a reference to the calling module if you want to access anything from there - without knowing what it is if this also has an interface to communicate with.
使用该接口的模块不需要了解有关特定对象的任何信息,它仅使用该接口,因为这将是现有过程的唯一列表。 特定对象(例如实现接口的表单)不需要知道谁调用了接口代码,也不需要以任何方式直接访问它(因为它没有引用它,所以不能)。 如果您要从那里访问任何内容,也可以提供对调用模块的引用-如果它还具有要与之通信的接口,则不知道它是什么。
In this way you could, for example, exchange information between modules, e.g. a form and a popup form where the popup form should be given an ID to display and the popup form should return a chosen ID to the calling form without knowing which form it was. So the popup form could be used for two different main forms which should select a supplier for example and return the chosen supplier without directly accessing the specific main form - because you forward a reference to the main form's interface object only using an interface of the popup form and the popup form accesses this interface object reference only and can be sure that it will be sent to the right main form.
这样,您可以(例如)在模块之间交换信息,例如表单和弹出表单,其中应给弹出表单提供一个要显示的ID,而弹出表单应将选定的ID返回给调用表单,而不知道该表单是哪种表单是。 因此,弹出表单可用于两种不同的主表单,这些主表单应例如选择一个供应商并返回选择的供应商,而无需直接访问特定的主表单-因为您仅使用弹出窗口的接口将对转发的引用转发给了主表单的接口对象表单和弹出表单仅访问此接口对象引用,并且可以确保将其发送到正确的主表单。
This decouples the code of both forms completely so it is never a problem to rename a control on the main or the popup form as both never accesses a control name of the other form directly.
这完全解耦了两种形式的代码,因此在主形式或弹出形式上重命名控件从来都不是问题,因为两者都从未直接访问另一种形式的控件名称。
2.3为什么接口在VBA中可能无用 (2.3 Why an interface can be useless in VBA)
(
)
2.3.1有关“实施”的问题 (2.3.1 Issues With "Implements")
Really great what you can do with an interface and how it can make your life easier and the code more stable.
使用接口可以做的事真的很棒,它如何使您的生活更轻松并且代码更稳定。
"Stable" is unfortunately the big problem using interfaces. As it is a not often used feature of VBA it seems that it is not really tested by the Microsoft developers thoroughly. At least with Access 2010 (never had this issue with A2007) and maybe later versions using the "Implements" statement creates some really bad issues which are nearly impossible to solve.
不幸的是,“稳定”是使用接口的大问题。 由于它不是VBA的常用功能,因此似乎没有经过Microsoft开发人员的全面测试。 至少在Access 2010中(A2007从来没有这个问题),也许更高版本使用“ Implements”语句会产生一些非常糟糕的问题,几乎无法解决。
- if you use "Implements" very often in a bigger project the project gets into a state where it is not possible to keep it compiled. You can compile it, save it, no problem. If you close Access and reopen your project and look into the "Debug" menu it is not compiled again (you can see that because the "Compile" entry is not greyed out again).如果您在较大的项目中经常使用“实现”,则项目将进入无法进行编译的状态。 您可以编译,保存它,没问题。 如果关闭Access并重新打开项目,然后查看“调试”菜单,则不会再次编译该项目(您可以看到,因为“编译”条目不会再次变灰)。
- If the project is not compiled like described above Access crashes in many cases when you try to open the module where the interface is implemented. Sometimes it works if you open e.g. a form without an implemented interface and then open the form with the interface.如果未按上述方式编译项目,则在许多情况下,当您尝试打开实现接口的模块时,Access崩溃。 有时,如果您打开例如没有实现接口的表单,然后使用该接口打开表单,则它会起作用。
- Sometimes the project seems to be compiled when you open the VBA editor first and everything seems to work. But if you open the project without opening the VBA editor, then opening the form using the interface again crashes Access.有时,当您首先打开VBA编辑器时,该项目似乎已被编译,并且一切似乎正常。 但是,如果您在不打开VBA编辑器的情况下打开项目,则再次使用该界面打开表单会使Access崩溃。
- Sometimes only deactivating the "Implements" command makes a form displaying the contents and reactivating it again keeps the form from displaying records.有时,只有停用“ Implements”命令才能使表单显示内容,然后再次将其重新激活会使表单无法显示记录。
- Not to mention that inserting an interface procedure using the drop-down sometimes creates "RHA" as parameter name instead of the name defined in the interface class.更不用说使用下拉列表插入接口过程有时会创建“ RHA”作为参数名称,而不是接口类中定义的名称。
2.3.2可能的解决方法 (2.3.2 Possible Methods To Workaround)
- You can try to decompile the project with a copy of course and then compile it again. Sometimes it helps for a while.您可以尝试使用课程副本对项目进行反编译,然后再次对其进行编译。 有时它会帮助一段时间。
- You can try to export all objects with "Application.SaveAsText" and reimport them with "Application.LoadFromText" in a new database file, the compile it again. Sometimes it helps for a while.您可以尝试使用“ Application.SaveAsText”导出所有对象,然后使用“ Application.LoadFromText”将它们重新导入到新的数据库文件中,然后再次进行编译。 有时它会帮助一段时间。
- You can try to add a space into the "Implements" line so it creates a syntax error, leave the line, go back, remove the syntax error and compile it again. You'll see that it takes a lot longer to compile as now all interface codes are compiled again. Sometimes this helps to get the namespace of VBA corrected and it works for a while.您可以尝试在“ Implements”行中添加一个空格,以便创建语法错误,离开该行,返回,删除语法错误,然后再次编译。 您会发现编译所需的时间要长得多,因为现在所有接口代码都已重新编译。 有时,这有助于更正VBA的名称空间,并且可以工作一段时间。
- You can try to deactivate all "Implements" statements in the entire project using "find and replace" and then compile it, reactivate all again and compile it again, maybe close and open the project in-between. That helps for a while - maybe...您可以尝试使用“查找并替换”来停用整个项目中的所有“实施”语句,然后对其进行编译,再次重新激活并再次对其进行编译,或者关闭并打开它们之间的项目。 这会帮助一段时间-也许...
2.4足够的解决方法 (2.4 Enough Of Workarounds)
After using all these workarounds for month I now decided to create my own one, the CCInterface modules.
在使用所有这些变通方法一个月之后,我现在决定创建自己的CCInterface模块。
I wanted to be sure that I can provide the functionality of a normal interface so that I don't need to reprogram my whole project which is a lot code. I also don't wanted to loose the interface technology in general as I'm a fan of the idea. So this is why I programmed this little tool to help me doing that.
我想确保我可以提供普通界面的功能,这样就无需重新编程整个项目,而这是很多代码。 我也不想总体上松开接口技术,因为我是这个想法的拥护者。 所以这就是为什么我编写了这个小工具来帮助我做到这一点的原因。
3使用方法 (3 How to use)
(
)
3.1工作原理 (3.1 How it works)
(
)
3.1想法 (3.1 The Idea)
As I cannot implement my own code into the VBA compiler I need to use what I have and that is that I need to use a class module.
由于无法在VBA编译器中实现自己的代码,因此需要使用已有的代码,即需要使用类模块。
So instead of rewriting the code I created a wrapper class which should be the replacement of the real interface class. So in our example above with "IStdFunctions" I add a class module "IStdFunctions_Wrapper" which also contains all procedures of the normal interface class but additionally it also contains a reference to the object using it (e.g. a form reference) and code to call the objects in the form (or whatever class module using the interface).
因此,我没有重写代码,而是创建了包装器类,该包装器类应替换真实的接口类。 因此,在上面带有“ IStdFunctions”的示例中,我添加了一个类模块“ IStdFunctions_Wrapper”,该模块还包含常规接口类的所有过程,但此外,它还包含使用该对象的对象的引用(例如,表单引用)和调用该对象的代码。形式的对象(或使用该接口的任何类模块)。
Unfortunately that would need to make all the interface procedures originally declared as "Private" to "Public" which is of course against the rules of a real interface as they now could be called from everywhere. But this is something I can live with as long as the rest is the same as before.
不幸的是,这将需要使所有最初声明为“私有”的接口过程都变为“公共”,这当然违反了实际接口的规则,因为现在可以从任何地方调用它们。 但这是我可以忍受的,只要其余与以前相同即可。
The code of the wrapper interface class ("IStdFunctions_Wrapper") would look like this:
包装器接口类的代码(“ IStdFunctions_Wrapper”)如下所示:
Option Compare Database
Option Explicit
' --------- Interface Wrapper for interface IStdFunctions------------
Private prv_objWithInterface As Object
Public Property Get ObjWithInterface() As Object
Set ObjWithInterface = prv_objWithInterface
End Property
Public Property Set ObjWithInterface(obj As Object)
Set prv_objWithInterface = obj
End Property
' This should be used to inform the objects that the
' program should be closed now.
Public Sub BeforeQuitProgram(ByRef Cancel As Boolean)
If Not prv_objWithInterface Is Nothing Then
prv_objWithInterface.IStdFunctions_BeforeQuitProgram Cancel
End If
End Sub
So we have a private variable to save the object's reference (a form in this example) and public properties to handle it (using properties would also allow to add more code to handle the reference, otherwise a public variable would also be enough).
因此,我们有一个私有变量来保存对象的引用(在此示例中为表单),并具有公共属性来处理它(使用属性还可以添加更多代码来处理引用,否则使用公共变量就足够了)。
Next, we need the form implementing the interface. It already exists, we have created that above. It has the "Implements" statement and the procedure from the old interface. The wrapper should now handle this so we need to change the code a little bit:
接下来,我们需要实现接口的表单。 它已经存在,我们已经在上面创建了它。 它具有“实现”语句以及旧界面中的过程。 包装器现在应处理此问题,因此我们需要对代码进行一些更改:
Option Compare Database
Option Explicit
'Implements IStdFunctions
Private prv_objIStdFunctions_Wrapper As IStdFunctions_Wrapper
Public Property Get ObjIStdFunctions() As IStdFunctions_Wrapper
If prv_objIStdFunctions_Wrapper Is Nothing Then
Set prv_objIStdFunctions_Wrapper = New IStdFunctions_Wrapper
Set prv_objIStdFunctions_Wrapper.ObjWithInterface = Me
End If
Set ObjITest1 = prv_objIStdFunctions_Wrapper
End Property
Public Sub IStdFunctions_BeforeQuitProgram(Cancel As Boolean)
End Sub
The difference is that the interface sub is now declared as "Public" and a new object variable for the wrapper class module is created and a property which automatically initialises it if it was not created before or lost due an unhandled error (of course unhandled errors only exist during design time...). Moreover the "Implements" statement is deactivated with a comment character.
区别在于,接口子现在被声明为“ Public”,并且为包装器类模块创建了一个新的对象变量,并且如果该属性不是之前创建的或者由于未处理的错误(当然是未处理的错误)而丢失,则该属性将自动初始化仅在设计时存在...)。 此外,“实施”(Implements)语句通过注释字符停用。
Next the module using the interface need a little change:
接下来,使用该接口的模块需要进行一些更改:
Option Compare Database
Option Explicit
Public Sub TestIStdFunctions()
Dim ObjIStdFunctions As IStdFunctions_Wrapper
Dim frm As Form_frmTest
DoCmd.OpenForm "frmTest"
Set frm = Forms("frmTest")
Set ObjIStdFunctions = frm.ObjIStdFunctions
Stop
End Sub
The difference here is that the interface object variable is now changed to "IStdFunctions_Wrapper" and the assignment is extended by the new property "ObjIStdFunctions" above.
此处的区别在于,接口对象变量现在更改为“ IStdFunctions_Wrapper”,并且分配由上面的新属性“ ObjIStdFunctions”扩展。
If you run the "TestIStdFunctions" sub now and reach the "Stop" you can now access the new interface like before. IntelliSense also displays only the interface procedures and you could also use "For Each" like in 2.2.4, only you would need to not add the form/report reference directly to the collection, instead you would add "frm.ObjIStdFunctions" or "rpt.ObjIStdFunctions". If you want to use more than one interface in the For loop you could add the form and report references to the collection and use "For Each obj in MyCollection" where "obj" is declared as "Object" and then use "obj.ObjIStdFunctions" or "obj.ObjIOtherInterface".
如果现在运行“ TestIStdFunctions”子目录并到达“停止”,则可以像以前一样访问新界面。 IntelliSense还仅显示界面过程,您也可以像2.2.4一样使用“ For Each”,只需要不必将窗体/报告引用直接添加到集合中,而是可以添加“ frm.ObjIStdFunctions”或“ rpt.ObjIStdFunctions”。 如果要在For循环中使用多个接口,可以将表单和报表引用添加到集合中,并使用“对于MyCollection中的每个obj”,其中将“ obj”声明为“ Object”,然后使用“ obj.ObjIStdFunctions” ”或“ obj.ObjIOtherInterface”。
3.1.2使用方法 (3.1.2 How To Use)
As there is a lot to do to rewrite all the procedures in the project that already heavily use the interface I wrote this class to automate a lot of those issues.
由于要重写已经大量使用该接口的项目中的所有过程,还有很多工作要做,因此我编写了此类来自动化其中的许多问题。
(
)
1.调整VBA参考 (1. Adjusting VBA References)
To use it you must check the references in VBA ("Tools" - "References") and add the "Microsoft Visual Basic for Applications Extensibility 5.3" reference (or whatever version you have, 5.3 here).
若要使用它,您必须检查VBA中的引用(“工具”-“引用”),并添加“ Microsoft Visual Basic for Applications扩展性5.3”引用(或此处使用的任何版本5.3)。
This is only needed at design time, you can remove the reference and the two "CCInterface" modules for the final release of your project.
这仅在设计时需要,您可以删除参考和两个“ CCInterface”模块,以最终完成项目。
If you've adjusted the references, start "Debug" - "Compile" to make sure the code is compiled and all references are correct.
如果已调整引用,请启动“调试”-“编译”以确保代码已编译且所有引用正确。
(
)
2.在您自己的Access文件中使用 (2. Using in your own Access file)
Before using it please make sure that you have created a backup of your project in a safe place, I'm not responsible if your project gets broken.
在使用它之前,请确保已在安全的地方创建了项目的备份,如果您的项目损坏了,我不承担任何责任。
As this tool doesn't use specific features of newer versions of Access it should work in all Access versions.
由于此工具未使用Access的较新版本的特定功能,因此它应在所有Access版本中均适用。
For now you need to insert the code modules "modCCInterface" and "clsCCInterface" into your project. It is also recommended to insert the "modRecreate" module which I wrote to create a new ADP from an existing one by exporting and importing all objects and settings. It has the sub "procRecreateSaveAllAsText" procedure which only does the export. Calling that before using CCInterface makes sure you also have a textual backup of all your objects. As it was written for ADPs it cannot export tables or queries, but at least all forms, reports, macros and modules.
现在,您需要将代码模块“ modCCInterface”和“ clsCCInterface”插入到项目中。 还建议插入“ modRecreate”模块,该模块是我编写的,用于通过导出和导入所有对象和设置从现有的ADP创建新的ADP。 它具有子“ procRecreateSaveAllAsText”过程,该过程仅执行导出。 在使用CCInterface之前先进行调用,以确保您还拥有所有对象的文本备份。 由于它是为ADP编写的,因此它不能导出表或查询,但是至少导出所有表单,报表,宏和模块。
(
)
3.首先创建接口包装器类模块 (3. First create the interface wrapper class module)
Go to the immediate window and use this command to create the interface wrapper class from your interface module. In this example I use the "IStdFunctions" interface of the previous examples:
转到立即窗口,然后使用此命令从您的接口模块创建接口包装器类。 在此示例中,我使用前面示例的“ IStdFunctions”接口:
CCIF.CreateInterfaceWrapper "IStdFunctions"
That's all, the new interface class module "IStdFunctions_Wrapper" should now exist in the module list in the VBA editor.
就是这样,新接口类模块“ IStdFunctions_Wrapper”现在应该在VBA编辑器的模块列表中存在。
Compile and save the code now.
现在编译并保存代码。
(
)
4.其次,使用接口在类模块中重写代码 (4. Second rewrite the code in the class module using the interface)
That's as easy as above:
就像上面一样简单:
CCIF.RewriteModuleWithInterface "IStdFunctions", "Form_frmTest"
In this example the form "frmTest" was rewritten to use the wrapper instead. That means, the deactivated "Implements", the new property and variable "ObjIStdFunctions" and all interface procedures are now Public.
在此示例中,表单“ frmTest”被重写为使用包装器。 也就是说,已停用的“ Implements”,新属性和变量“ ObjIStdFunctions”以及所有接口过程现在都是Public。
You now need to do that for any object using the interface, don't forget to compile and save before you go on to the next object.
现在,您需要使用该接口对任何对象执行此操作,在继续下一个对象之前,请不要忘记进行编译和保存。
(
)
5.第三步,使用任何地方的接口重写代码 (5. Third rewrite the code using the interface anywhere)
That was not as easy as the above steps. The module can find the object that declares " As IStdFunctions" and rewrite that. But as there are many possibilities to initialize and use that, it only rewrites the declaration and adds a "TODO:" comment to it so you can easily find that and add the adjustment (adding ".ObjIStdFunctions" when assigning the reference).
那并不像上面的步骤那么容易。 该模块可以找到声明为“ As IStdFunctions”的对象并将其重写。 但是由于有很多初始化和使用它的可能性,所以它仅重写声明并在其上添加“ TODO:”注释,因此您可以轻松地找到它并添加调整项(在分配引用时添加“ .ObjIStdFunctions”)。
To rewrite the declarations you can use this one:
要重写声明,可以使用以下声明:
CCIF.ReplaceInterfaceUsageWithWrapper "IStdFunctions"
(
)
6.最后工作 (6. Final work)
- As explained in 3.1.2.5 you need to adjust the reference assignments on your own.如3.1.2.5中所述,您需要自行调整参考分配。
- If you have functions in your interface that are not using standard variables like "Long", "String" etc. and that need a "Set" statement, you need to add the "Set" into the wrapper interface class on your own. e.g. If your return type is "DAO.Recordset" you will get a Set property and a "Set" statement in properties, but as there is no way to distinguish between a Set or Let in functions by VBE the CCInterface code would need to decide if it would need a let or set here so I added also a "TODO" comment here instead and always use Let in all cases. As you only add the interface wrapper code once, this is only a little work to do.如果接口中的函数没有使用“ Long”,“ String”等标准变量,而需要“ Set”语句,则需要自行将“ Set”添加到包装器接口类中。 例如,如果您的返回类型为“ DAO.Recordset”,则将获得Set属性和属性中的“ Set”语句,但是由于无法通过VBE区分Set或Let in函数,因此CCInterface代码需要确定如果需要在此处设置let,那么我也在此处添加了“ TODO”注释,并且在所有情况下始终使用Let。 由于只添加一次接口包装程序代码,因此这只是一件小事。
- If you've converted everything you can remove the "modCCInterface", "clsCCInterface" and "modRecreate" modules and remove the above inserted reference.如果已对所有内容进行了转换,则可以删除“ modCCInterface”,“ clsCCInterface”和“ modRecreate”模块,并删除上面插入的引用。
In future, if you want to insert a new module with the interface and you already have the wrapper module you can use "Implements" to insert all the procedures, before compiling that to check if everything is OK.
将来,如果您想使用该接口插入一个新模块,并且已经有了包装器模块,则可以使用“ Implements”插入所有过程,然后再进行编译以检查是否一切正常。
Then:
然后:
- Change the "Private"s to "Public"s for all interface procedures 将所有接口过程的“专用”更改为“公共”
- Copy and paste the header code (the private variable declaration "prv_objIStdFunctions" in the example and the "ObjIStdFunctions" properties) from another module and deactivate the "Implements". Of course you can also copy the interface procedures from another module instead of using "Implements" to do that. 从另一个模块复制并粘贴标头代码(示例中的私有变量声明“ prv_objIStdFunctions”和“ ObjIStdFunctions”属性),然后停用“实现”。 当然,您也可以从另一个模块复制接口过程,而不用使用“ Implements”来实现。
- If you have not removed the CCInterface modules you can also use that to do the job.如果尚未删除CCInterface模块,则也可以使用它来完成该工作。
vba 界面