Problems when calling a public sub - vb.net

I'm facing a deadend When trying to call this sub :
Public Sub backblue(ByVal frm As Form, ByVal boxname As String)
For i = 1 To 3
CType(frm.Controls(boxname & i.ToString()), TextBox).BackColor = Color.LightBlue
Next
End Sub
with button click event :
Private Sub Button1_click and bla bla....
backblue(Me, "txb1_")
End Sub
Can anybody show me a suggestion to fix the code.
It throws "Object Referrence not set to an instance bla bla" error
For information the textbox names are :
txb1_1 , txb1_2 , txb1_3
(these are some of the many textboxes in the form that i want its bakcolor changed)
and these three textboxes are already created through designer, not from execution.
i did check the textboxes names and there's nothing wrong.
the form class is also public.

if they are the only textboxs on said form you can just loop through
For Each box as Textbox In frm.Controls
box.BackColor = Color.LightBlue
Next

This error will occur if you do not declare the Form class to be public.
Also, make sure the textbox names are really correct, although this will probably cause a different error.
If you create the textboxes during execution, make sure they are initialized with New and added to the form's Controls collection.

Try this....
Public Sub backblue(ByVal frm As Form, ByVal prefix As String)
For i = 1 To 3
Dim bxName as String = prefix & i.ToString()
Dim bx as TextBox = CType(frm.Controls(bxName), TextBox)
If bx Is Nothing Then
MsgBox("Unable to find text box " +bxName)
Dim mtch() As Control = frm.Controls.Find(bxName, true)
If mtch.Length> 0 then
bx = mtch(0)
Else
Continue For
End if
End If
Bx.BackColor = Color.LightBlue
Next
End Sub
Although, a better solution would be to either create the textboxes inside a control and pass that control to BackBlue or to create an collection that has the controls and pass that in. Which brings up what is most likely yor problem your control is contained in a sub component and thus is not in the main form control collection
Alternative, you could use either the tag of the control or create a component control that implements IExtenderProvider and add it to the form --all of the above would effectively allow you to define the controls and/how they should be handled at designtime.

It may really seem that the names generated by this loop may not be the names of the original textboxes. My suggestion is before setting this Color property verify that the names generated by this loop are indeed the actual names. Maybe output this in a messagebox:
MessageBox.Show(boxname & i.ToString()) for each loop before you set the property

Related

Access VBA - How to get the properties of a parent subform, or, get the user-given name for a subform (not the object reference name)

In MS Access 2016, let's say I have 2 forms: frmMain and frmBaby.
I have embedded frmBaby as a subform on frmMain. I have embedded on frmBaby a control (let's say it's a textbox, but it could be any control) named tbxInput.
On frmMain, since frmBaby is a "control" on frmMain, I have given that control the traditional name of subfrmBaby.
Now, in VBA, an event on subfrmBaby passes the tbxInput control ByRef (as Me.tbxInput) to a function that is meant to return the .Left property of the parent of the control passed ByRef. That is, I need the function to determine the .Left property for the location of subfrmBaby on frmMain. (The function is more complicated than this, but for the sake of keeping this question let's just say the function is returning the .Left property value because the .Left value is what I need to perform the function.)
Let's say the function is: Public Function fncLocation(ByRef whtControl As Variant) as Long
(I use Variant so that null values can be passed.)
Here is the code that I expected to return the .Left value of the parent (i.e., subfrmBaby) of whtControl: lngLeft = whtControl.Parent.Left
However, that gives me an error of: "Application or object-defined error"
When I use the immediate window to check things out I find that whtControl.Parent.Name is "frmBaby" and not "subfrmBaby" which makes it problematic to reference the subform on frmMain since I cannot figure out how to get the actual name given to the control on frmMain from the object passed to the function and so I cannot reference the subform by name either.
Questions:
How can I get the .Left value for the parent of the control passed to this function?
How can I get the actual name assigned to the subform control on frmMain? In this case, I need the name of "subfrmBaby" rather than "frmBaby."
Thanks in advance for ideas.
You can do this by iterating the controls on the main form, assuming whtControl is the form object of the subform (if it's a textbox, it's whtControl.Parent.Parent and If c.Form Is whtControl.Parent Then)
Dim mainForm As Form
Set mainForm = whtControl.Parent
Dim c As Access.Control
Dim subformControl As Access.Control
For Each c In mainForm.Controls
If TypeOf c Is SubForm Then
If c.Form Is whtControl Then
Set subformControl = c
Exit For
End If
End If
Next
If Not subformControl Is Nothing Then
Debug.Print subformControl.Left
End If
Note that iterating controls comes at a performance penalty, but this code should still take milliseconds, not seconds. Also, since we test reference equality, it works even if the same subform is present multiple times on the parent form.
I just had this issue, and I think I solved it! Thanks to Eric A's answer above to get me started. I tweaked it and built on it for my use. In my case, I needed to save the "full" address of a control to build and facilitate a control log (used to log both user actions for auditing and to allow for users to "undo" an action). I have several duplicated subforms in several sub-form controls, and a few sub-sub forms (each displaying differently filtered and sorted data), so I couldn't rely on simply knowing the subform's name, I also needed the subform control name. This also leverages others' work (as noted in the code notes with some tweaks to allow easier re-use for us. I've posted it here, hopefully it will help someone else. I know I've used SO a lot.
How we use it:
On a form, after logging an action, we record the control's ID info, which calls a function to get the toppost form (this is used in conjunction with afterUpdate event so we refresh the main form and subform). We also use the HWND to validate some other items elsewhere, and to grab a form if we don't have the initial form reference. If you use this and modify it, please point back to here and give comments.
Specific Function Code to get Control "address" and get control from address
' Posted on StackOverflow 2022 February 18 in response to Question:
' https://stackoverflow.com/q/66425195/16107370
' Link to specific answer: https://stackoverflow.com/a/71176443/16107370
' Use is granted for reuse, modification, and sharing with others
' so long as reference to the original source is maintained and you
' help lift others up as others have done those who helped with this concept
' and code.
Private Function GetControlAddress(ByRef ControlTarget As Object, _
ByRef ParentForm As Access.Form) As String
' Used in concert with building a form ID, this allows reflection back to the specific
' subform control and containing subform.
Dim ControlSeek As Access.Control
If TypeOf ControlTarget Is Form Then
' You need to dig through the whole list to get the specific controls for proper reflection down.
For Each ControlSeek In ParentForm.Controls
If ControlSeek Is ControlTarget Then
GetControlAddress = ParentForm.Name & FormIDHWNDSep & ParentForm.Hwnd & FormIDHWNDSep & ControlTarget.Name & FormIDFormSep
Exit For
ElseIf TypeOf ControlSeek Is SubForm Then
If ControlSeek.Form Is ControlTarget Then
GetControlAddress = ParentForm.Name & FormIDHWNDSep & ParentForm.Hwnd & FormIDHWNDSep & ControlSeek.Name & FormIDFormSep
End If
End If
Next ControlSeek
Else
' If you're not looking for a form, then you can skip the slow step of running through all controls.
GetControlAddress = ParentForm.Name & FormIDHWNDSep & ParentForm.Hwnd & FormIDHWNDSep & ControlTarget.Name & FormIDFormSep
End If
End Function
Public Function GetControlByAddress(ByRef StartingForm As Access.Form, ByRef strControlAddress As String) As Access.Control
' Given a control address and a starting form, this will return that control's form.
Dim ControlTarget As Access.Control
Dim TargetForm As Access.Form ' This is a reference to the hosting control
'Dim ControlSeek As
Dim FormIDArr() As String
Dim FormInfo() As String
Dim ControlDepth As Long
Dim CurrentDepth As Long
If strControlAddress = vbNullString Then GoTo Exit_Here
FormIDArr = Split(strControlAddress, FormIDFormSep)
' Because there's always a trailing closing mark (easier to handle buidling address), we skip the last array
' value, as it's always (or supposed to be...) empty.
ControlDepth = UBound(FormIDArr) - LBound(FormIDArr)
' Split out the form's Specific Information to use the details.
FormInfo = Split(FormIDArr(CurrentDepth), FormIDHWNDSep)
' The specific control is located in the 3rd element, zero referenced, so 2.
Set ControlTarget = StartingForm.Controls(FormInfo(2))
' If ControlDepth is 1 (control is on passed form) you can skip the hard and slow work of digging.
If ControlDepth > 1 Then
For CurrentDepth = 1 To ControlDepth - 1
' Note: you start at 1 because you already did the first one above.
' Split out the form's Specific Information to use the details.
FormInfo = Split(FormIDArr(CurrentDepth), FormIDHWNDSep)
Set TargetForm = ControlTarget.Form
Set ControlTarget = TargetForm.Controls(FormInfo(2))
Next CurrentDepth
End If
Exit_Here:
Set GetControlByAddress = ControlTarget
End Function
Required Helper Functions
Note, I use a property for the separators as there is some user locale handling (no included), and it also ensures that if we do change the separator it remains consistent. In this example, I simply set them to a character which is unlikely to be used in a form name. You will need to ensure your forms don't use the separator characters.
Public Function hasParent(ByRef p_form As Form) As Boolean
' Borrowed concept from https://nolongerset.com/get-top-form-by-control/
' and modified for our uses.
On Error Resume Next
hasParent = (Not p_form.Parent Is Nothing)
Err.Clear ' The last line of this will cause an error. Clear it so it goes away.
End Function
Private Function GetFormObjectByCtl(ByRef ctl As Object, _
ByRef ReturnTopForm As Boolean, Optional ByRef strControlAddress As String) As Form
strControlAddress = GetControlAddress(ctl, ctl.Parent) & strControlAddress
If TypeOf ctl.Parent Is Form Then
If ReturnTopForm Then
If hasParent(ctl.Parent) Then
'Recursively call the function if this is a subform
' and we need the top form
Set GetFormObjectByCtl = GetFormObjectByCtl( _
ctl.Parent, ReturnTopForm, strControlAddress)
Exit Function
End If
End If
Set GetFormObjectByCtl = ctl.Parent
Else
'Recursively call the function until we reach the form
Set GetFormObjectByCtl = GetFormObjectByCtl( _
ctl.Parent, ReturnTopForm, strControlAddress)
End If
End Function
Public Function GetFormByCtl(ctl As Object, Optional ByRef strControlAddress As String) As Form
Set GetFormByCtl = GetFormObjectByCtl(ctl, False, strControlAddress)
End Function
Public Function GetTopFormByCtl(ctl As Object, Optional ByRef strControlAddress As String) As Form
Set GetTopFormByCtl = GetFormObjectByCtl(ctl, True, strControlAddress)
End Function
Public Property Get FormIDHWNDSep() As String
FormIDHWNDSep = "|"
End Property
Public Property Get FormIDFormSep() As String
FormIDFormSep = ";"
End Property
Interesting. I don't think you can.
As you have seen, the parent of whtControl is its form, frmBaby.
The parent of that one is frmMain. The subform control is not part of the object chain when "going up", only when going down.
If you always use the naming scheme as in the question, you could do something like this (air code):
strSubform = whtControl.Parent.Name
strSubformCtrl = "sub" & strSubform
Set ctlSubform = whtControl.Parent.Parent(strSubformCtrl)

Access 2013 - Save Value from Textbox and display again when form is opened after close

I want to save a value from textbox in a string for example and display it again when the form get's openend.
I have two textboxes PriceRangeOne and PriceRangeTwo .. The user enter here for example 20 and 40
The problem i have is that when the user switches between Form and Report the values in this textboxes are beeing deleted. How can i save them?
I tried adding a sourcecontrol to the fields but had name errors eventhough i used different names.
I tried adding this to on change and retrieve it in an onload
Dim eingabe As String = textBox1.Text or .Value
Still didn't worked. Does anyone know a way to do this?
Typically, the most efficient and reliable way to do this is to have some form auto-open when the database is opened. It could be a dashboard, or just some form with nothing else on it. Whatever you use, launch it when the database opens and then minimize it. Now you have a form that's always open, as long as the application is open. Add a couple of textboxes to this form/dashboard.
When you close your form referenced in this question, write the values of PriceRangeOne and PriceRangeTwo to the textboxes on the form I described above. Then, when you open a new form or report, you can reference the values in those textboxes. Since the form is always open, you can reference these values at any time from any form or report until you close your database.
Solved it with variables.
I declared global variables in my standart module
For example
Public PriceOne As Double
Public PriceTwo As Double
Than i did this in my form in Close() and Open():
Private Sub Form_Close()
PriceOne = Me.Field
PriceTwo = Me.FieldTwo
End Sub
Private Sub Form_Open(Cancel As Integer)
Me.Field = PriceOne
Me.FieldTwo = PriceTwo
End Sub
Works perfect!
Courtesy of How to save the last value of a textbox. | Access World Forums:
Private Sub Form_Close()
'Set the default value for textbox
DoCmd.SetWarnings False
DoCmd.RunSQL "UPDATE table SET table.field = [Forms]![FormName]![Textbox] " & vbCrLf & _
"WHERE (((table.ID)=1));"
DoCmd.SetWarnings True
End Sub
Private Sub Form_Load()
'Load the default value for textbox
Me.Textbox.Value = DLookup("[field]", "[table]", "[ID]=1")
End Sub

Call form controls' properties in a function

I want to create a function that receives forms as an input. I need to use controls' properties of the passed form. For example:
Private Sub Drawing(ByVal frm As Form)
X = frm.a.Left + frm.a.Width
End Sub
Visual basic doesn't accept this method that I use. How can I solve it?
The code should be like this :
Private Sub Drawing(ByVal frm As Form)
Dim i As Control()
i = frm.Controls.Find("a", True)
if i.Length <> 0 Then
X = i(0).Left + i(0).Right
End If
End Sub
"Visual Basic doesn't accept this method that I use" is a very vague description of your problem. You should be specific with what errors you get and what you want the result to be, because we are not mindreaders and can therefore not tell what the problem is unless we get clear information.
But if you're trying to change a control's/form's X-position you have to change the entire Location property.
Change the current form's X-position:
Me.Location = New Point(frm.a.Left + frm.a.Width, Me.Location.Y)
Change a control's X-position (where YourControl is the name of the control you want to move):
YourControl.Location = New Point(frm.a.Left + frm.a.Width, YourControl.Location.Y)

Microsoft Access applying 1 function to all fields automatically

I have a form that keeps track of assigned patient equipment. I have it set so that any changes made to text fields on the form automatically move down to the "comments" section of the form (this is done so that any changes made are documented in case the user forgets to manually document changes). I have a sub that I wrote that accomplishes this that I am currently calling for every single text field. This works but is messy.
Is there a way to apply the sub to all the fields in one procedure without calling it for every individual field? Code is below, please let me know if I can clarify anything.
Private Sub pPEMoveValue(sField)
'Moves the old field value down to the comments section automatically
Dim sOrigValue As String
Dim sCommentValue As String
sOrigValue = sField
sCommentValue = Nz(mPEComments, "")
Me.mPEComments = sCommentValue & vbNewLine & sOrigValue
End Sub
Private Sub sPEBatCharger_Dirty(Cancel As Integer)
pPEMoveValue (Nz(Me.sPEBatCharger.OldValue, ""))
End Sub
This is the solution I came up with to do what you are looking to do. I took advantage of the MS Access Tag system. You can add tags to your controls so you can sort of "Group" them.
First put the form in design view and adjust the tag for all of the fields you want to record to say "Notes".
Then in the Form's BeforeUpdate even you would add this:
Private Sub Form_BeforeUpdate(Cancel As Integer)
Call FindControlsForComments(Me.Form)
End Sub
Then you would use this function to find any fields that have the "Notes" tag and run it through the function you created:
Public Function FindControlsForComments(frm As Form)
Dim ctrl As Access.Control
For Each ctrl In frm
'If the control is tagged for notes
If ctrl.Tag = "Notes" Then
'If the old value is different than the current value
If Nz(ctrl.OldValue, "") <> Nz(ctrl.Value, "") Then
'Add to comment
Call pPEMoveValue(Nz(ctrl.Value, ""))
End If
End If
Next ctrl
End Function
You may have to adjusted this slightly to work with your system but this has worked well for me.

How to access to the properties of an UserControl from code side?

make my own UserControl and I can aggregate new TabPages to a TabControl and then, inside of then TabPage, I add my own UserControl using the following code.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim TabX As New Windows.Forms.TabPage("Tab " & TabCount.ToString) '(ConfiguracionTabPage)
Dim MyControl As New ClientesEmpresa
MyControl.Name = "Control" & TabCount.ToString
If ClientesTabControl.TabPages.Count = 10 Then
ClientesTabControl.TabPages.RemoveAt(9)
End If
TabX.Controls.Add(MyControl)
TabX.Name = "Tab" & TabCount.ToString
TabX.Text = "Tab" & TabCount.ToString
MyControl.TitularLbl.Text = "Coca Cola"
Me.ClientesTabControl.TabPages.Insert(0, TabX)
Me.ClientesTabControl.SelectedIndex = 0
TabCount += 1
End Sub
My user control have several Labels, TextBox and TabPages(inside of a TabControl).
Now I want to change some properties dynamically from the source code, but I don't know how to access them.
The most similar theme that I found is this How to Acces of an User control in c#, but, as the title says, is in C#, how I can do it in VB.NET?
Sorry, I just notice that the Enter key post the comment. :(
Thanks for your feedback, I understand what are you saying but I missing something in the middle.
When I create the control in running time in the above code I can access easily to the properties of the created object, in this case my UserControl, but I don't understand how to reach the properties of a particular instance of that control from outside of Button_Click; ie. another button_click event(second button)
I was thinking to use something like
Dim ControlList As Windows.Forms.Control() = Me.ClientesTabControl.TabPages(0).Controls.Find("ModeloLbl", True)
or
ClientesTabControl.TabPages(0).Controls.OfType(Of AlarmasVehiculo)()
But I'm stuck here.
------------------------------------- 3th post ---------------
Thanks Steve, I was resolved using "Control.Find" and a For Each but your solution is easier.
There's any way to get the name of the selected tab or I must to create an Array when I create the New TabPage?, the idea is to update the text of the controls inside of the selected tab only when is selected by the user or every 5 seconds but just the in selected one.
Thanks.
To borrow M4N's answer from the C# question, and translate it to VB:
Cleanest way is to expose the desired properties as properties of your usercontrol, e.g:
Public Class MyUserControl
' expose the Text of the richtext control (read-only)
Public ReadOnly Property TextOfRichTextBox As String
Get
Return richTextBox.Text
End Get
End Property
' expose the Checked Property of a checkbox (read/write)
Public Property CheckBoxProperty As Boolean
Get
Return checkBox.Checked
End Get
Set (value As Boolean)
checkBox.Checked = value
End Set
End Property
'...
End Class
In this way you can control which properties you want to expose and whether they should be read/write or read-only. (of course you should use better names for the properties, depending on their meaning).
Another advantage of this approach is that it hides the internal implementation of your user control. Should you ever want to exchange your richtext control with a different one, you won't break the callers/users of your control.
To answer your second question, if you need to access your dynamically created controls, you can do so easily using their names, for instance:
Dim c As ClientesEmpresa= CType(Me.ClientesTabControl.TabPages("Tab1").Controls("Control1"), ClientesEmpresa)
c.CheckBoxProperty = True