I'm trying to move the OnClick code from one of the buttons on a form to a function, but I'm having some issues. The button in question, called "Reset", changes the properties of most of the objects on the form. Stuff like:
Me.btnName.Caption = "Caption"
Me.btnName.Visible = True
Me.btnName.Top = 123
Me.btnName.Height = 456
'Etc
When moving this over to a function I can't seem to get this to work. I've tried a few different ways of writing it that I found while searching around, but none of them seem to work. I'm messing with some objects in the main form and some in the subform, so I'll show a few examples of both. Edited to include error messages
Forms("FormName").btnName.Caption = "Caption"
'Application-defined or Object-defined error
Forms("FormName").Controls("btnName").Caption = "Caption"
'Ms Access cannot find the referanced form (I've double and triple checked that it is correct)
Forms("FormName").SubFormName.Form.btnName.Caption = "Caption"
'Application-defined or Object-defined error
Forms!FormName!btnName.Caption = "Caption"
'Cannot find the referenced form
Forms!FormName!SubFormName!btnName.Caption = "Caption"
'Cannot find the field reffered to in your expression
Forms!FormName.Controls!btnName.Caption = "Caption"
'Cannot find the referanced form
Forms!FormName!SubFormName!Form.btnName.Caption = "Caption"
'MS Access can't find the field 'SubFormName'
No matter what I try of these I can't seem to get it to work. Maybe I'm just doing something simple wrong, or maybe you can't change properties like this from a function. Regardless, if anyone knows, I would appreciate the help.
Note that the function I'm trying to use is in a separate module, not in the code behind the form.
So you have a OnClick handler on some form's code-behind, responsible for assigning a bunch of properties for objects that live on that form.
What you have done is called encapsulation: outside code doesn't need to care about the Top and Height properties (and others) of the form's btnName button - truth is, outside code shouldn't even need to care that there's a button on the form.
So you're taking this nicely encapsulated object, and moving code around for no apparent reason.
If you need that functionality to be invoked from the outside, then yes, move it out of the OnClick handler.. but not outside the form's code-behind.
Move it to some Public Sub OnReset() procedure, and if outside code needs to invoke that logic, then have it call theForm.OnReset.
Private Sub ResetButton_Click()
OnReset
End Sub
Public Sub OnReset()
Me.btnName.Caption = "Caption"
Me.btnName.Visible = True
Me.btnName.Top = 123
Me.btnName.Height = 456
'Etc
End sub
That way you leave the implementation details of the form within the form itself, while giving outside code an abstraction to say "I don't care what your buttons are named; I don't care what size they are or what their captions are - but when I say 'Reset', you shall re-initialize whatever values you've got for them" - outside code doesn't need to know what Reset does specifically.
Put it this way: the day you rename that btnName button, would you rather need to simply adjust the form's code-behind, or hunt down every possible place in the entire project that could possibly be changing that button's Caption or whatever other property value?
FYI Foo!Bar.Something is late-bound code, shorthand for Foo.Item("Bar").Something: neither Bar nor Something are validated at compile-time. Contrast with Me.btnName.Whatever, where a typo is immediately picked up by the compiler: early-bound code that the compiler is able to validate should always be preferred.
You need to set the property to something:
Forms!FormName!btnName.Caption = "New Caption"
or, if on a subform:
Forms!FormName!<NameOfSubformCONTROL>.Form!btnName.Caption = "New Caption"
Related
I have some very (very) basic MS Access knowledge. I'm trying to expand a bit into either VBA or macros as I'd like to put in some conditional visibility for my form. Basically, I have a checkbox. If it's checked, I want three or four more fields to pop up. Someone was able to point me to a basic VBA formula of if (this checkbox) = true then, (fieldx).visible = true, else, (fieldx).visibility = false, end if.
But I'm so new to this that I need more help and explanation. I tried putting it in but couldn't get it to work (no error message, just nothing changed at all).
Specific questions:
-Does this formula seem right?
-If I want multiple fields to be visible, can I combine them into one formula or should I create a new "if" statement for all?
-Where do I enter this code? I'm running the Office 365 version. For all I know, I'm not even putting it in the right place.
-How do I determine the field names to replace the (this checkbox) and (fieldx) in the formula? I tried entering the name I title the fields as, but with the spaces in the name I got an error message, and without the spaces nothing happened. Is there a specific naming convention to turn the field names into formula-appropriate titles? Is the name listed somewhere?
-Once I get the formula entered, is there something I have to do to get it to run/take effect? I tried saving, closing and reopening with no changes.
-Is this the best way to go about this?
If there's anything else you think I should know, I would love to hear it - but please keep in mind I'm very new to this so if you could keep it at "dummy" or ELI5 levels of explanation, I'd appreciate it!
after creating a form with 4 textboxes and a checkbox put the form in design mode (lower right corner has design mode selected, select a textbox and hit property sheet on the ribbon (or f4).
On the property sheet note the visible property. set the visible property to false. Now the textbox will be invisible when the form starts.
Tip you can select all the textboxes at the same time and set their properties all at once.
Every control on the form and even the various parts of the form have properties you can set and play with. For instance you can give any name you want to any control. On the property sheet go to the other tab and set the name property.
Tip: choose a name you you will remember without having to look it up and describes the controls function.
Next select the checkbox (not the checkbox's label). On the property sheet go to the event tab and select the on click event. hit the ellipsis and choose code builder. Access is Event Driven. We want the textboxes to appear when the checkbox is selected so we put that code in the checkbox click event.
after choosing code builder we get the code window where we can browse among all the events for all our forms. for now all you should see is something like:
Private Sub mycheckbox_Click()
End Sub
So insert some code to handle the checkboxes like:
Private Sub mycheckbox_Click()
If mycheckbox = True Then
txtbox1.Visible = True
txtbox2.Visible = True
txtbox3.Visible = True
txtbox4.Visible = True
Else
txtbox1.Visible = False
txtbox2.Visible = False
txtbox3.Visible = False
txtbox4.Visible = False
End If
End Sub
now when the checkbox is not checked no textboxes are visible.
but when the checkbox is checked they appear
In my modules, I want to use my controls from my form. For example: I want to set focus on a textbox after a certain Sub.
My current solution is to make a subroutine to set all controls in a public variable (see below).
My questions:
What is the best practice? How do people usually do this?
When should I call my subroutine? (is it the first call in the FORM_LOAD sub?)
Public TBnr As TextBox
Public Sub controlsInitieren()
Set TBnr = Forms("frm_TreeView_Example").pstNr
End Sub
Well, as a general rule, while many platforms seperate out the UI part and the code part? Well, Access is quite much a different approach - it is standard fair to place the required code inside of the form "class" (all forms in Access are a "class", and you can even have muliple instances of the SAME form open more then one time).
So, in general, your code should be in the forms code (class) module.
However, you could and call a external routine.
So in the form, you could call your above routine like this:
Call MySetFocus(me, "NameOfControlToSetFocusTo")
And your sub would look like this:
Sub MySetFocus(f as form, sCtrl as string)
f(sCtrl).SetFocus
End Sub
However, as noted, the amount of code above is more code then simply in the forms code module going:
me.ControlName.SetFocus
However, while the above is a less then ideal example, passing the form "instance" (me) to a external sub or function allows you to referance any property or method or feature that exists in the form in an external routine.
So in place of say
me("LastName") = "Zoo"
In the above sample routine, you would and could go;
f("LastName") = "Zoo"
So any place you would and could use "me" in the form, you can use the form instance you passed from the form. As noted, it is a good idea to use "me", since as I noted, Access does allow multiple copies of the form to be opened at the same time - and thus your code can't distinguish between what form instance you are using unless you pass the current "in context" form. So like in JavaScript, using "this" ?
In access that current instance of the class object is "me", and you are free to pass that instance to any sub or function you want as per above.
The best practice is to use only procedures inside the form code. In such a case you refer to a text box control in this way: Me.Textbox1.SetFocus. If you want to set some controls properties during the form loading, you can do that in the frm_TreeView_Example_Initialize event;
They usually do it in the way I described at item 1;
If you want to use such a strange/unusual way you can do it calling the subroutine whenever you want. But, in order to set a specific property value of the form frm_TreeView_Example controls you can simply use frm_TreeView_Example.TextBox1.SetFocus. You can use this way of setting in a module procedure, even before the form has been shown. You can simply show it in that procedure code using at the end: frm_TreeView_Example.Show;
I have a problem with the snippet of the code I have got to extend and improve. It is not my original code and I cannot change the logic that much of it, just to be clear.
I have one main form called MDIServer, which has a timer set for every second. In Timer.Tick I have some other code which works fine (timer is running okay). Newly, I had to check there, if one form is Active and if so, change some stuff (labels text and tags) in that form and refresh it.
I add there this code:
If IsActiveForm("frmName") Then
frmName.ChangeSomething()
End If
The Sub ChangeSomething is, how you can see, located in the form I want to refresh and do the changes. In that function I simply change the label text and tags of few controls.
My question is: Form is not refreshing => labels are not visible changed, why?
I think I tried already almost anything with Refresh() function in the ChangeSomething() function or in the timer after I called this function. Also I tried to add there new timer (in frmName) and do the changes there, which works perfectly with
Label.Text = "something new"
Label.Refresh()
So I guess problem is somewhere with the refreshing form from Timer in different form. I also tried to do it with my own InvokeReguired() function etc...
P.S. When I am debugging the code, labels and tags are changing and every single function which has to be called, is called, but it is just not visible on the form itself.
EDIT Info
formName is not declared in MDIServer explicitely and in this case and many other cases, forms are used as default instances. Timer is from System.Windows.Forms.Timer. Also MDIServer is not a MDIParent of the formName and I cannot use Me.ActiveMdiChild Is. Lets just say, these two forms are not dependent on each other in any way.. and everything is done through name of the form (default instance, so nothing like Dim frm As Form and frm = frmName).
I would be really glad for any tip or anything :D
Thanks guys,
Vojta
So, I fixed my problem after some research and the problem was (expected) that I am not calling the subroutine ChangeSOmething() for one specific instance of the form frmName. So I had to change my code, that I will call it exactly for the instance which is active and visible.
New code looks like this:
Dim frmCollection = Windows.Forms.Application.OpenForms
Dim listfrmname = frmCollection.OfType(Of frmName).ToList()
If listfrmName.Count > 0 Then
Dim tmpFrm As frmName = listVZT15.Last()
tmpFrm.ChangeSomething()
End If
I also could not use combination of frmCollection.OfType(Of frmName).Any and frmCollection.Item("frmName"), because when I was closing the form and opening again, it created new and new instances (I dont know, why it is not closing the old one, but like I said, it is not my code). So the logic is, to list all open forms of the needed type, and then take the last instance from that list and for that instance call the subroutine. Also Me.Refresh() is placed in the subroutine ChangeSomething() itself.
Thanks everyone for help, it surely helped me to understand, how the instances works here.
I wanna show the output of the calculations in a second form but I am writing the code in the first form. How can I do it?
Use the parent form name in front of the control that you are trying to work with. You may need to set the modifier to Friend.
Say that the label exists in form2 and you have code in form1 that is needing to change it. So you would do it like this: form2.label.text = "the string value here"
However, keep in mind that if the control was created or is owned by a different thread then the one that is trying to edit the control, you will receive a runtime exception.
To resolve that you will need to create a delegate for the calling sub or function.
I've added some form controls to a collection and can retrieve their properties when I refer to the members by index.
However, when I try to use any properties by referencing members of the collection I see a 'Could not set the ControlSource property. Member not found.' error in the Locals window.
Here is a simplified version of the code:
'Add controls to collection'
For x = 0 To UBound(tabs)
activeTabs.Add Item:=Form.MultiPage.Pages(Val(tabs(x, 1))), _
key:=Form.MultiPage.Pages(Val(tabs(x, 1))).Caption
Next x
'Check name using collection index'
For x = 0 To UBound(tabs)
Debug.Print "Tab name from index: " & activeTabs(x + 1).Caption
Next x
'Check name using collection members'
For Each formTab In activeTabs
Debug.Print "Tab name from collection: " & formTab.Caption
Next formTab
The results in the Immediate window are:
Tab name from index: Caption1
Tab name from index: Caption2
Tab name from collection:
Tab name from collection:
Why does one method work and the other fail?
This is in a standard code module, but I have similar code working just fine from within form modules. Could this have anything to do with it?
Edited to add
formTab was declared as a Control, but I find that if it is declared as an Object then the code works.
This will probably solve my problem, but in the interests of furthering my knowledge I would be grateful for any explanation of this behaviour, particularly with regard to the difference in running the code in the different types of module.
This is a really great question. Your edit at the end of the post reveals a lot about how VBA works and what's going on here. I'm not 100% this what's going on, but I'll explain what I think is happening.
A Collection in VBA (and VB6, for that matter; same code base) is not strongly typed. This means that everything in a collection is technically an "object." In the .NET world (as of .NET 2.0), it's possible to have strongly typed collections so that you could say "everything in this collection is a Control object." In VBA, this isn't possible with a Collection.
In your first iteration, where you are referring to the item indexed in the activeTabs collection, activeTabs(x + 1) is referring to an object. When you tell VBA to look up .Caption of that object, it doesn't know what the underlying type is (I think), so it has to simply look to see if the underlying object type contains a property or method called Caption. As you can see, Tab controls do in fact contain a property called Caption.
In your second interation, where you are doing a For Each loop, I think the problem is that the Control type probably doesn't have a property called Caption, though different types of controls probably do. For example, a text box control probably doesn't have a Caption property whereas as label control does have a Caption property.
You have a few options to fix your second loop. 1) You could declare formTab as a Tab control (I'm not sure exactly what it's called). The Tab control should have a Caption property. 2) If every control in activeTabs is not specifically a Tab control (in which case, you should probably call it activeControls instead of activeTabs), you could check within your loop to see if the formTab is actually a Tab control. If it is, cast it as a Tab control and then call .Caption. Until you cast it as a Tab control, VBA won't know that it has a Caption property since a regular Control object doesn't have a caption property.
In the end, you can get away with using objects as in your first loop and letting the runtime figure out what to do, but that can give really bad performance. In general, it's better to work with your specific types in a strongly-typed language. It also helps to show in your code that you know specifically what you're working with rather than leaving it to the runtime to decide what properties and methods you can work with.