Access VBA中的表单引用技术 [英] Form reference technique in Access VBA

查看:81
本文介绍了Access VBA中的表单引用技术的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据



因此,对于某些简单类型的UI交互,您不需要表单。



好吧,下一个场景?



我要传递一些值吗?



好吧,在某些情况下,如果您使用子窗体,那么您实际上不必传递值,因为您可以轻松引用这两种窗体。



要使用子形式的代码?



您可以去:



Me。 parent!InvoiceNumber



因此,在子表单中,您可以始终使用 me.Parent引用父表单。这是内置的,非常好,因为您永远都不会对表单名称进行硬编码。



因此,如果您复制表单,将其重命名,等等,那么您的代码不会破裂。因此,在此示例中,我们不必传递子表单可能需要的值,因为已经有内置的获取方法和方法以及父表单中的引用值/控件。



不是必需的-仅在您需要时!



这取决于您在做什么。



说,我在客户表单上,现在我想为该客户添加新发票。



我可以通过两种方式做到这一点。



只需启动一个新的创建/编辑发票表格即可创建新记录。但是,在这种情况下,我将不得不将新记录将附加到的新客户的新表单传递给我们(假设发票表单是该客户表单的子表)。



在这种情况下:
我们将以添加模式启动发票表格。
我们将必须将此新发票记录所属的正确客户ID传递给表单。



在这种情况下?几乎不需要使它成为对话框形式。
几乎不需要等待调用代码。



因此,在主要形式中,我们将有一个名为创建新发票的按钮。



它将/将要:

  DoCmd.OpenForm frmEditInvoice,, ,,acFormAdd,,Me!ID 

因此,我们只需启动下一个表单即可。在该主要客户表单中没有真正需要运行的其他代码。或必须等待直到用户完成添加/创建发票的任何代码。因此,几乎不需要对话框。



但是,我们确实需要传递一个非常重要的信息到发票创建表单。那是发票将附加到(相关)的客户的主键 ID。



因此,我使用了公开的开放Args表单方法。这是一个很好的简单选择,可以将值传递给您打开的表单。当然,局限性在于 open args仅适用于传递一个值。但是,通常就足够了。



因此,现在在发票表单加载事件中,我们必须设置相关记录。我们可以去:

  Me!Customer_id = me.OpenArgs 

但是,我建议有一个提示:



将发票格式设为模态。模式表示什么是用户必须在完成后关闭发票表单,然后用户在关闭发票工作表单后返回主客户表单?



如果您不这样做,不能使发票表格具有模式性?嗯,用户可能会开始输入发票表单,决定他们不喜欢或想要发票表单,然后简单地返回客户表单,然后编辑甚至搜索/查找其他客户。现在,您有了一个打开的发票表单,该表单尚未填写,也没有保存,并且用户是否开始跳转到其他客户?好吧,我们不想要这样。



因此,如果您将发票编辑表单设置为模式,那么用户必须关闭该表单才能返回到上一个表单形成。因此,使用模式形式(而不是对话框-完全不同)是控制用户和应用程序流程的好方法。如果您没有某些控件,那么您和您的代码将很快失去控制,因为您打开了客户表单,并且用户很可能会尝试添加/编辑甚至不属于的发票那位顾客。因此,使用模式将迫使用户完成或关闭表单以返回到他来自的地方。



此外,以上内容是空中代码,我非常强烈建议您不要使用on-load事件来通过on-open或on-load事件设置(连接)记录。您实际上想使用插入事件。但是这篇文章太长了,我需要2-3页的解释来解释为什么这样有意义。



无论如何,上述情况?
我们不需要对话框形式(通常用于回答是/否种类的问题)。



我们只需要传递一个小值–因此,传递该值使用了打开表单的 open args参数。



并且,我们将该表单设置为模式,因为我们不希望用户跳转然后潜回客户表单,直到他们完成输入发票为止,他们必须关闭该发票表单才能返回到客户表单。



但是,启动单独的Windows表单的概念,而用户不得不四处移动调整大小,并担心与窗口混淆?我们不再那么做了!



由于智能手机和平板电脑?以及所有网络浏览器如何工作?好吧,一个单独的窗口正在迅速成为老派。这就是Access具有新的选项卡式界面的原因-因此它现在的工作原理与浏览器非常相似。



由于我们行业的平板电脑和用户界面趋势如何? (用户现在期望软件如何工作?)。好吧,然后访问中新的选项卡式界面选项是一个常见选择,并且该选择通常会影响您如何将UI的表单流程拼凑在一起。



除非您想提示用户,然后才采取行动,否则不建议使用对话框形式。



好,所以在上面的示例中?假设在创建发票时,我们希望并需要从该客户表单中获得更多的价值。假设我们需要默认的送货地址(送货地址表中的单个外键值),或者默认的发票条款(您知道,净30天,等等–再次在某些表中输入FK值),当然还有PK客户ID值。



所以,现在我们需要传递3个值。



好吧,我们不能使用openargs,因为这仅对一个值有用(有些人确实传递了定界字符串,并解析出这些值-因此您可以用一些箍将openargs传递多个值,但对于除一个值)。



所以,现在让我们将3个值传递给我们打开的发票表格。



好吧,首先,我们可以问我们是否真的需要传递值。



我们可以执行以下代码:

  Dim f作为字符串
f = frmEditInvoice

DoCmd.OpenForm f,,,acForm添加

表单(f )!customer_id = Me!ID
表格(f)!ShippingAddress = Me!ShipID
表格(f)!InvoiceTe rms = Me!InvoiceTermsID

因此,在上面,主表单(调用表单)启动了新表单,然后只需以目标(第二个)形式设置一些值。



因此,在许多情况下,您实际上并不需要传递所有这些东西。只需打开表单,然后在启动/打开该表单后立即设置所需的文本框或值。



不过,例如,让我们将这些值传递给发票



在这种情况下,我们现在将使用 frmPrevious技巧。实际上,我们不会传递所有值,而是仅传递调用表单,因此发票表单可以自由引用并使用该调用表单中的任何内容。 (欢迎来到这里使用对象的世界!所以,过去的想法是传递一堆值?您真的不需要。一旦发票表单具有对先前表单的引用(指针)?那么它可以从该调用表单中获取/使用/查看尽可能多的值,这包括在主表单中声明的​​变量的用户!



因此,如果可以/ grab控件,值,甚至是调用表单中的变量吗?那么,那么您实际上不必传递任何东西,并且几乎不需要。



因此,为拯救世界贫困而不必尝试传递大量的值,让我们使用主动屏幕的建议。



因此,我们的代码变为:

  DoCmd.OpenForm frmEditInvoice,,,,acFormAdd 

并且以发票形式?



在主模块级别,我们有:

 选项比较数据库
选项显式
public frm形式为

表格加载:
我们可以去:

  Dim frmPrevious As Form 

Set frmPrevous = Screen.ActiveForm

Me!customer_id = frmPrevious!ID
Me!ShippingAddress = frmPrevious!ShipID
Me!InvoiceTerms = Me!InvoiceTermsID

那么,通过获取对调用表单的引用来再次说明吗?好吧,我现在可以获取2或20个值。因此,在对象编程中,您不会传递一堆值,而是传递对相关对象的引用。虽然Access不是完整的OO语言?它可以很好地利用对象,甚至可以创建自定义对象。



因此,到目前为止,我们不需要,不需要,甚至不保证使用



我倾向于仅在需要提示用户并且需要几项内容供用户输入而不是是/否。



那么,这里传递值的想法是什么?好吧,整个想法是,当您可以引用先前的对象时不需要传递值,因此可以随意获取尽可能多的值。



因此,采用对象方法将需要对传统编程进行一些更改,在传统编程中,我们将所有内容视为调用的某个子例程,并将值传递给该子例程。在这种情况下,我们实际上是在选择先前的表单对象,一旦完成,我们就可以从先前的表单中获取任何东西–如果需要,可以包括VBA变量。



请记住,您在先前格式中引用的内容不一定是控件。您也可以引用变量。因此,您可以设置3个变量,设置它们的值,然后以调用/打开的形式使用?它可以获取/获取引用这些值。因此,您可能遇到一种情况,其中许多表单都调用一种特殊类型的表单,并且这些表单将具有各种不同的控件和字段名称,但是只要任何调用表单都遵循您的新编造在调用表单中声明3个已知变量的标准?然后,接收表单可以引用这些VBA值。



我当然会承认,在大多数情况下,您要传递的值将是表单控件中的值,但是您不仅限于控件-在模块级别声明的变量以及在调用表单中公共的变量都可以在目标表单中使用。



例如;

  frmPrevous.SomeVBAVariableNameGoesHere 

最后的笔记和清理screen.ActiveForm的用法。



您不希望Willy Nilly到处都使用screen.ActiveForm,因为它可以更改



但是,非常重要:



您可以选择,获取/获取目标表单的on-open事件中的screen.ActiveForm。您可以先快速完成此操作。



您甚至可以等待,并在加载事件中执行此先前的表单提取技巧。当前的表单只有在打开事件和加载事件都已100%完成后才能激活。



我在大约20年了,我不知道在加载或打开事件中通过获取先前的表单引用来获取先前的调用表单是失败的。



它非常可靠。一旦获取了该参考,屏幕焦点的变化等就不再重要了,因为您一次仅在一次表单打开事件中获取了一个有效的参考,或者如我所述,您甚至可以迟到一次在加载事件中。



所以这里的建议有很大的不同。我不是建议或提倡使用screen.ActiveForm在任何旧的地方使用,但我建议一次性抓取screen.ActiveForm以获取调用表单的参考-我发现这种方法100%可靠。


As per a suggestion in the comments from here, I am starting a thread to learn the reference technique for passing values and variables from form to form, also discussed here and here. As a side note, to the best of my knowledge, it's not the technique referred to here and here.

Those previous questions demonstrated that the following code in the called form, sets up this technique.

Dim prevForm As Form

Private Sub Form_Load()
   Set prevForm = Screen.ActiveForm
End Sub

For an Access beginner such as myself, the commenter from the first link also suggests using a dialog form for the form being called, for simplicity. Should it already be set that way in the form's Property sheet, or handled on-the-fly using the DoCmd.OpenForm's acDialog argument?

解决方案

Ok, there is several issues here.

As always, one particular suggestion applies to a particular case and goal you are trying to achieve.

So, for example:

I need to get a simple yes/no from the user.

So, for a simple yes/no, or "do you want to do this" type of prompt?

Then you can use this:

Eg this:

  If MsgBox("Do you want to create a invoice", vbQuestion + vbYesNo, _
         "Create") = vbYes Then
     '
     ' code here to create a invoice, or maybe launch a invoice form.

  Else
     ' user cancled - clikced no
     ' code here to do what you want if user answers no,
     ' or maybe we do nothing

  End If

And the prompt is like this:

So, for some simple types of UI interaction you don’t need a form.

Ok, the next scenario?

I want to pass some values?

Well, in some cases, if you use a sub form, then you don’t really have to pass values, since you can with ease reference both forms.

For code in the sub form?

You can go:

Me.parent!InvoiceNumber

So, in a sub form, you can "always" reference the parent form with "me.Parent". This is built in, and VERY nice, since you NEVER thus hard code the forms name.

So, if you copy the form, rename it etc., then your code will not break. So in this example, we don’t’ have to pass values that the sub form might need, since there is already a built in means and approach to get at, and reference values/controls in the parent form.

Not necessary – only if you need to!!!

It will depend on what you are doing.

Say, I am on a customer form, and I now want to add a new invoice for that customer.

I can do this two ways.

Simply launch a new "create/edit invoice" form to a new record. However, in this case I would have to pass to the new form WHICH customer ID the new record will be attached to (we assume that the invoice form is a child table of the customer form).

So in this case: We will launch the invoice form in "add mode". We will have to pass to the form the correct customer ID that this new invoice record belongs to.

In this case? VERY little need to make this a dialog form. And very little need exists to have calling code wait.

So, in the main form, we would have a button called create new invoice.

It would/could go:

DoCmd.OpenForm "frmEditInvoice", , , , acFormAdd, , Me!ID

So, we simply launch the next form. And there is no real other code in that "main" customer form that has to run. Or any code that has to "wait" until the user is done adding/creating invoices. So, there is little or no need to use a dialog form.

But, we did need ONE very important bit of information passed to the invoice create form. That is the primary key "ID" of the customer that the invoice will be attached to (related to).

So, I used the "open Args" of the open form method. This is a nice a simple option to pass a value to a form you open. The limitation of course is that "open args" is ONLY good for passing one value. But, it often will suffice.

So, now in the invoice forms on-load event, we have to setup the related record. We could go:

Me!Customer_id = me.OpenArgs

However, there is ONE tip I would suggest:

Make the invoice form "modal". What modal means is that the user MUST close the invoice form when done, and thus user returns to the main customer form upon closing the invoice working form?

If you don’t make the invoice form modal? Well, a user might start typing into the invoice form, decide they don’t like or want the invoice form, and will simply go back to the customer form, and edit, or even search/find a different customer. Now you have an open invoice form, one that not completed, and not saved, and if the user starts jumping around to different customers? Well, we don’t want that.

So, if you set the invoice edit form as "modal", then the user must close that form to return to the previous form. So using modal forms (not dialog – very different), is a great way to control the user and "flow" of your application. If you don’t have "some" control, then you and your code will fast spiral out of control, since you have a customer form open, and the user might well be trying to add/edit an invoice that does not even belong to that customer. So, using modal will force the user to finish or close the form to return back to where he came from.

Also, the above is "air code", I VERY MUCH STRONG recommend you don’t use the on-load event to set (connect) the record using the on-open, or on-load event. You actually want to use the on-insert event. But this post is too long, and I’ll need 2-3 pages of explain as to why this makes sense.

Anyway, so, the above case? We did not need a dialog form (usually for answering yes/no kinds of questions).

We ONLY has to pass one little value – so, passing that value we used the "open args" parameter of the open form.

And, we set that form as "modal", since we do not want the user to jump and sneak back to the customer form until such time they are "done" entering the invoice, and they MUST close that invoice form to return back to the customer form.

However, this "concept" of separate windows forms being launched and users having to "move around" re-size and worry about messing with a window? We don’t’ do that much anymore!

Due to smartphone, and tablets? And how all the web browsers work? Well, a separate window is fast becoming "old school". And that is why Access has the new "tabbed" interface – so it works much like a browser now.

As a result of tablets and UI trends in our industry? (And what users now expect how software works?). Well, then the new "tabbed" interface option in access is a common choice and this choice will often effect how you cobble together forms flow for the UI.

Also, I don’t recommended dialog forms unless you wanting to prompt the user, and then take action as a result.

Ok, so in our above example? Let’s say when we create the invoice, we want and need a FEW more values from that customer form. Say we want the default shipping address (a single foreign key value to the shipping address table), maybe the default invoice terms (you know, net 30 days etc. – again a FK value to some table), and of course the PK customer ID value.

So, now we need to pass say 3 values.

Well, we can’t use openargs, since that is quite much only good for one value (some people do pass a delimited string, and parse out the values – so you can with some hoops pass multiple values with openargs, but it tends to be messy for anything more than one value).

So, so, now let’s pass 3 values to that invoice form we open.

Well, first, we can ask do we really need to pass values.

We could do this code:

Dim f    As String
f = "frmEditInvoice"

DoCmd.OpenForm f, , , , acFormAdd

Forms(f)!customer_id = Me!ID
Forms(f)!ShippingAddress = Me!ShipID
Forms(f)!InvoiceTerms = Me!InvoiceTermsID

So, in above, the main form (calling form) launched the new form, and then simply set some values in that target (2nd) form.

So, in many cases you really don’t and did not need to pass all that stuff. Just open the form, and set the textboxes or values you want right after you launch/open that form.

However, for an example, let’s pass the values to the invoice form.

In this case, we are now going to use the "frmPrevious" trick. In effect, we don’t pass all the values, but ONLY pass the calling form, and thus the invoice form is free to reference and use "any thing" from that calling form. (Welcome to the world of using objects here! So, the old school idea of passing a whole bunch of values? You really don’t need to. Once that invoice form has a reference (a pointer) to the previous form? Well then it can grab/use/see as many values from that calling form. And this INCLUDES the user of variables you declared in your main form!

So, if you can get/grab controls, values, and even variables from the calling form? Well, then you really don’t have to pass much of anything, and there is little need.

So, to save world poverty and not have to try and pass a gazillion values? Let’s use the suggestion of screen active.

So, our code becomes this:

DoCmd.OpenForm "frmEditInvoice", , , , acFormAdd

And in the form invoice?

At the main module level, we have this:

Option Compare Database
Option Explicit
public frmPrevious    as form

And the forms on-load: we can go:

Dim frmPrevious      As Form

Set frmPrevous = Screen.ActiveForm

Me!customer_id = frmPrevious!ID
Me!ShippingAddress = frmPrevious!ShipID
Me!InvoiceTerms = Me!InvoiceTermsID

So, note again, by grabbing a reference to the calling form? Well now I can grab 2 or 20 values. So, in object programming, you don’t pass a whole bunch of values, but a reference to the object in question. While Access is not a full OO language? It has good use of objects, and you can even create custom objects.

So, up to this point, we not needed, nor wanted, nor even warranted the use of a dialog form.

I tend to only suggest a dialog form WHEN you need to prompt the user and you need "several" things for the user to enter as opposed to a simple yes/no.

So, the idea here about passing values? Well, the whole idea is that you don’t need to pass values when you can get a reference to the previous "object", and thus grab as many values as you please.

So, adopting an object approach will require some change from traditional programming where we think of everything as some subroutine we call and we pass values to that sub. In this case, we are in effect picking up the previous forms object, and once we do that, then we can quite much grab anything from that previous form – including VBA variables if you wish.

Keep in mind, that what you reference in that previous form does not necessary have to be controls. You can also reference variables. So, you could setup 3 variables, set their values, and then in the form you called/opened? It can get/grab reference those values. So, you might have a case in which one special type of form is called by MANY forms, and those forms will have all kinds of different controls and field names, but as long as ANY of the calling forms follows your new "made up" standard of having say 3 known variables declared in the calling form? Then the receiving form can reference those VBA values.

I would certainly admit that in most cases the values you are passing are going to be values in controls on the form, but you are not limited to just controls – variables declared at the module level and as public in the calling form can be used in the target form.

Eg;

frmPrevous.SomeVBAVariableNameGoesHere

Final notes and clearing up the use of screen.ActiveForm.

You do NOT want willy nilly to just use screen.ActiveForm all over the place, as it can change without you really controlling when and how.

However, VERY important:

You PICK UP, GET/GRAB the screen.ActiveForm in the on-open event of the target form. You wnat to do this first, and fast.

You can even wait and do this previous form pick up trick in the on-load event. The current form DOES NOT become active until such time both the on-open event, and the on-load event have 100% completed.

I have used this approach for about 20 years and I am not aware of ONE failure in regards to having picked up the previous calling form by grabbing the previous form reference in the on-load, or on-open event.

It is VERY reliable. Once that reference has been picked up, then screen focus changes etc. will NOT matter, since you grabbed a working reference ONE TIME and only one time in the forms on-open event, or as I noted, you can even do this as late in the on-load event.

So a HUGE difference of a suggesting here. I am not suggesting or advocating using screen.ActiveForm any old place, but I am suggesting a ONE TIME grabbing of screen.ActiveForm to get the calling form's reference - I have found this approach to be dead 100% reliable.

这篇关于Access VBA中的表单引用技术的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆