If combo box contains text like multiple criteria then disable text boxes below - vba

I'd like to disable text boxes if the SponName combo box contains certain company names. I thought I could try a for loop for this, but I'm not sure if I created the right variables or used the for loop properly. Please help!
I've tried cobbling together the following code from the threads below but can't quite adapt it to my problem. I'm still learning VBA and am not great with declaring variables. There's no error message, but the text fields just won't disable after updating SponName.
VBA code for if like multiple criteria
Private Sub SponName_AfterUpdate()
Dim sponcontains As Variant
sponcontains = SponName.Text
Criteria = Array("Company1*", "Company2*", "Company3*", "Company24*")
For i = LBound(Criteria) To UBound(Criteria)
If InStr(sponcontains, Criteria(i)) > 0 Then
cps_manufsite_name.Enabled = False
cps_manufsite_city.Enabled = False
cps_manufsite_st.Enabled = False
cps_manufsite_ctry.Enabled = False
Else
cps_manufsite_name.Enabled = True
cps_manufsite_city.Enabled = True
cps_manufsite_st.Enabled = True
cps_manufsite_ctry.Enabled = True
End If
Next i
End Sub

Use Text property only when control still has focus. You need Value property which is default for data controls so don't have to reference. Explicitly declare all variables. Should have Option Explicit at top of all modules. Set the VBA editor > Tools > Options > Require Variable Declaration to automatically add to new modules - will have to type into existing. Use of * wildcard not appropriate with InStr() function - it's just another literal character.
Like this - default value for Boolean variable is False:
Option Compare Database
Option Explicit
___________________________________________________________________________________
Private Sub SponName_AfterUpdate()
Dim sponcontains As Variant, criteria As Variant, booE As Boolean, i As Integer
sponcontains = Me.SponName
criteria = Array("Company1", "Company2", "Company3", "Company24")
For i = LBound(criteria) To UBound(criteria)
If InStr(sponcontains, criteria(i)) > 0 Then
booE = True
Exit For
End If
Next
cps_manufsite_name.Enabled = Not booE
cps_manufsite_city.Enabled = Not booE
cps_manufsite_st.Enabled = Not booE
cps_manufsite_ctry.Enabled = Not booE
End Sub
But your solution without loop is just as valid and most likely shorter. Again, use Boolean variable to set Enabled state instead of duplicating.
booE = InStr("Company1,Company2,Company3,Company24", Me.SponName) > 0
Consider what would happen if you had to modify this list. Would have to modify code and distribute new db version to users. Alternatively, use a table with an attribute (can be a yes/no field) for company status. Code can do a lookup to table. Better to use company ID value to search. Your combobox should have that as a column in its RowSource and usually that would be the combobox's value.
booE = DLookup("Status", "Companies", "CompanyID = " & Me.SponName)
Use of Conditional Formatting can often avoid VBA altogether.

Related

Take list box selection, add value to other list box without allowing duplicates

I have two list boxes on a form I am making. The first list box is linked to a table with various company names. The goal I am after is after double clicking a companies name, the value is inserted in the second list box.
It worked fine until I tried to add code to prevent duplicates from appearing in the second list box, so you couldn't accidentally insert the same company twice. I have tried several different iterations, but with no luck. Anyone able to help with this one? My end goal would be for a msgbox to pop up alerting the user that duplicates are not allowed.
Private Sub ContractorLstbx_DblClick(Cancel As Integer)
Dim found As Boolean
found = False
Dim ID As Long
Dim Contractor As String
For Each newItem In Me.ContractorLstbx.ItemsSelected
For j = 0 To Me.SelectedContractorLst.ListCount - 1
If (Me!ContractorLstbx.ItemData(newItem).Column(1) = Me.SelectedContractorLst.ItemData(j).Column(1)) Then
found = True
Exit For
End If
Next j
If found = False Then
ID = Me.ContractorLstbx.ItemData(newItem)
Me.SelectedContractorLst.AddItem ContractorLstbx!.ItemData(newItem).Column(0) & ";" & Me!ContractorLstbx.ItemData(newItem).Column(1)
End If
found = False
Next newItem
End Sub
This is the full code for your solution. I tried it on test sample and working fine. just copy and paste the code. If you need your comparison to be case sensitive (I mean A <> a) then use Option Compare Binary as in my code below. If it is required to be case insensitive (A = a) just leave the default Option Compare Database or better force it using Option Compare Text
Option Compare Binary
Private Sub ContractorLstbx_DblClick(Cancel As Integer)
Dim found As Boolean
found = False
Dim ID As Long
Dim Contractor As String
For i = 0 To Me.ContractorLstbx.ItemsSelected.Count - 1
For j = 0 To Me.SelectedContractorLst.ListCount - 1
If (Me.ContractorLstbx.Column(1, Me.ContractorLstbx.ItemsSelected(i)) = Me.SelectedContractorLst.Column(1, j)) Then
found = True
Exit For
End If
Next j
If found = False Then
ID = Me.ContractorLstbx.ItemData(Me.ContractorLstbx.ItemsSelected(i))
Me.SelectedContractorLst.AddItem (ContractorLstbx.Column(0, Me.ContractorLstbx.ItemsSelected(i)) & ";" & Me.ContractorLstbx.Column(1, Me.ContractorLstbx.ItemsSelected(i)))
End If
found = False
Next i
End Sub

Access VBA Public Function - Variable for Control and Field?

I want to set up a public function like the following:
(A)
Public Function CheckYNField(chkControl As Control, chkField As String)
If chkField = "Y" Then
chkControl = -1
ElseIf chkField = "N" Then
chkControl = 0
Else: chkControl = 0
End If
End Function
(B)
Public Function CheckYNFlipper(chkControl As Control, chkField As String)
If chkField = "Y" Then
chkField = "N"
chkControl = 0
ElseIf chkField = "N" Then
chkField = "Y"
chkControl = -1
Else: chkField = "Y"
chkControl = -1
End If
End Function
The reason for this is, that I have a form, which has an underlying SQL table. I have no control over the data, but I must represent it, for the ability to maintain it. I have 10 fields, in the underlying table, which have a Y or N as their values. The data types are actually nvarchar(10). At the same time, I have to show these fields as checkbox controls, on the form, for ease of use.
The above code, is an attempt I am making to A - set the checkbox control to align with the current value in the table--> -1 = Y and 0 = N, and to B - update the table value, and switch the check box to checked or unchecked, from what it was, to the opposite, based on the onclick event of that control.
I want to make chkField and chkControl variables to the public function, that would be the table-field, and the form-control. I can't seem to get the right syntax, and was hoping someone might have clarification on how to do this.
for the form load and current, I tried this:
CheckYNField Forms("frmFormNameA").Controls(chkCheckNameA), tblTableName.FieldA
for the on click, I tried this:
CheckYNFlipper Forms("frmFormNameA").Controls(chkCheckNameA), tblTableName.FieldA
I've tried some other methods, but doesn't seem to be working. I'm doing something wrong, but I can't tell what. Appreciate any tips!
Edit/Update:
I tried Kostas K.'s solution, abandoning the idea of making a public functions with parameters for the fields and controls. I put the following, on load and on current:
With Me
If .txtfieldboundtablefieldA.Value = "Y" Then
.unboundchkA.Value = True
ElseIf .txtfieldboundtablefieldA.Value = "N" Then
.unboundchkA.Value = False
Else: .unboundchkA.Value = False
End If
End With
This is on a continuous form, so that it can show like a giant grid. There are the identifying bound field controls, and then a series of these checkboxes, to display the Y/N true/false status of each of these particular fields. I can't bound the checkboxes to the fields, because it changes the field value in the table to -1 or 0, and we need it to stay Y or N. I added a bound text field, to hold the table/field value for each row (hence the call to a text box control in the above revised code). The checkbox is unbound, and is there to display the field value, and allow the user to check and uncheck, so I can use on-click code to change the table field value between Y and N.
The above code is not seeming to show the correct checkbox value for each bound text field, based on each row. It shows based on the row that currently has focus. If I click on a row, where the table field is Y, all rows checkboxes on the form show true. If I move to a row, where the table field is N, all checkboxes for all rows change to false. I am struggling to just initially get 1 checkbox to show accurately, on every row of the continuous form, based on every record in the table. This is a small table, like 30 records. I really didn't think it would be so difficult to present this the way we need to.
Any ideas, how I could better do this?
Edit:
Set the Control Source of the checkbox to:
= IIf([YourYesNoField] = "Y", -1, 0)
In order to update when clicked:
Private Sub chkCheckNameA_Click()
Dim yesNo As String
yesNo = IIf(Me.chkCheckNameA.Value = 0, "Y", "N") 'reverse value
CurrentDb.Execute "Update [YourTable] SET [YourYesNoField]='" & yesNo & "' WHERE [ID]=" & Me![ID], dbFailOnError
Me.Requery
End Sub
You could try something this.
Check the Y/N field and assign the function's boolean return value to the checkbox (A).
On the checkbox click event, check its value and update the Y/N field (B).
'Form Load
Private Sub Form_Load()
With Me
.chkCheckNameA.Value = CheckYNField(![FieldA])
End With
End Sub
'Click
Private Sub chkCheckNameA_Click()
With Me
![FieldA] = IIf(.chkCheckNameA.Value = 0, "N", "Y")
End With
End Sub
'True returns -1, False returns 0
Private Function CheckYNField(ByVal chkField As String) As Boolean
CheckYNField = (chkField = "Y")
End Function
chkControl is a Control, so you need to access a property of that control. Try changing your code to:
Public Function CheckYNField(chkControl As Control, chkField As String)
If chkField = "Y" Then
chkControl.Value = -1
ElseIf chkField = "N" Then
chkControl.Value = 0
Else: chkControl.Value = 0
End If
End Function
and then the same idea in your other function.

ms-access shorten vba code

I'm using the following code alot in my project:
txtVoornaam.Locked = False
txtVoornaam.BorderStyle = 4
txtVoornaam.BorderColor = RGB(255, 165, 0
txtAchternaam.Locked = False
txtAchternaam.BorderStyle = 4
txtAchternaam.BorderColor = RGB(255, 165, 0)
txtAfdeling.Locked = False
txtAfdeling.BorderStyle = 4
txtAfdeling.BorderColor = RGB(255, 165, 0)
I wonder if there is a way to not display this in my code or shorten this. The code gets very long if i use this a few times..
Whenever you need to repeat a set of instructions, instead of copy+pasta'ing code your first reaction should be to ask yourself "how can I avoid copying this chunk over and over?" - and the solution is pretty much always to extract a method and parameterize it.
So you take one of the repeated chunks:
txtAchternaam.Locked = False
txtAchternaam.BorderStyle = 4
txtAchternaam.BorderColor = RGB(255, 165, 0)
and then copy it one last time in a new scope:
Private Sub RenameMe()
txtAchternaam.Locked = False
txtAchternaam.BorderStyle = 4
txtAchternaam.BorderColor = RGB(255, 165, 0)
End Sub
Then you extract the parameters:
Private Sub RenameMe(ByVal target As Control)
target.Locked = False
target.BorderStyle = 4
target.BorderColor = RGB(255, 165, 0)
End Sub
Then you replace the repeated chunks with calls to that new procedure:
RenameMe txtVoornaam
RenameMe txtAchternaam
RenameMe txtAfdeling
Or if that's still tedious you can iterate controls and call that procedure in the loop body - whatever works best for you.
And if you need more flexibility, extract more parameters and make them Optional as needed:
Private Sub RenameMe(ByVal target As Control, Optional ByVal lockCtrl As Boolean = False, Optional ByVal brdrStyle As Long = 4, Optional ByVal brdrColor As Long = 42495)
target.Locked = lockCtrl
target.BorderStyle = brdrStyle
target.BorderColor = brdrColor
End Sub
Now the hard part is to give RenameMe a meaningful name that properly conveys what's going on here. I'd suggest FormatControl or something along these lines.
An option if you have several controls that you are creating through a form would be to do the following:
Dim names() As String
names = Split("txtVoornaam,txtAchternaam,txtAfdeling", ",")
Dim ctrl As Variant
Dim ctrlName As Variant
For Each ctrl In Me.Controls
For Each ctrlName In names
If StrComp(ctrlName, ctrl.Name) = 0 Then
ctrl.Locked = False
ctrl.BorderStyle = 4
ctrl.BorderColor = RGB(255, 165, 0)
End If
Next ctrlName
Next ctrl
This code iterates through each of the control names that fit your list.
However, this is much less efficient than Mat's Mug's answer because you are iterating through the entire list of controls in your form, but it does showcase how you might take a list of static names and iterate through them and a collection.
If you wanted to change all the text controls this would be the way to do it; simply remove the ctrlName check.
As Parfait has correctly pointed out, you could shorten the code to the following if you are confident in your control names:
Dim names() As String
names = Split("txtVoornaam,txtAchternaam,txtAfdeling", ",")
Dim ctrlName As Variant
For Each ctrlName In names
With Me.Controls(ctrlName)
.Locked = False
.BorderStyle = 4
.BorderColor = RGB(255, 165, 0)
End With
Next ctrlName
function Lockdown(strControl)
with me(strControl)
.locked = false
.borderstyle = 4
.bordercolor = RGB(255,165,0)
end with
use me or forms!formname depending on where you're calling from
if your controls are the same, obviously put them in a single sub/function that you can call from anywhere. i would not try to lock or change the format of textboxes, instead just enable/disable, and it will handle the format for you:
textbox.enabled = true/false
if you are doing this on multiple forms and really want a single sub/function to control enabling/disabling the textboxes on each form, then there are various ways of doing that as well, solution will depend on your needs, but certainly doable and some have already commented above.
for example, you can use the "tag" property of the textboxes to flag the textboxes on that form that you want to enable/disable. you can then have a single sub/function that would take in the form as reference, and then you can read the "tag" property of all textboxes on that form and if they are the ones you flagged, you would proceed to enable/disable them

Object Variable or With Block Variable not Set Error Access Vba

I tried reading other posted questions about this error but I am still having trouble understanding it.
I was having errors after renaming variables and posted this question, I use option explicit and debug my code and it fixed my naming problems, But I still am having one problem in that 2 out of my 21 comboboxes in the operations row are still getting this same error. How can they get an error when they are using the same functions as the other 19. I checked their names and they are correct how could this be??
I have a form that I have build that is set up to look and function similar to and excel spreadsheet. This is the layout, Column Names first and then Control type
Operation Time/Min DecTime LaborRate Cost MarkUp Total
ComboBox TextBox Label Label Label TextBox Label
So all together I have 7 columns with 21 rows (not including column names) in which I can input data. Any where there is a label there is no user input its value is simply derived from the other fields.
I used the following naming convention for my form controls cb_op1, cb_op2, cb_op3 ... I just increment it by one to mimic the row number (control name plus row number). Operation = cb_op, Time = tb_time, DecTime = tb_Dectime, LaborRate = tb_LbrRate, Cost = tb_Cost, MarkUp = tb_MU, Total = tb_Total.
I wrote functions for the AfterUpdate events for my controls. This first function gets the value of the control based on its row number and then allows me to assign it to a variable this fuction is used for multiple controls update events.
Private Function GetControlValue(name As String) As Object
Dim obj As Object
For Each obj In Me.Controls
If obj.name = name Then
Set GetControlValue = obj
Exit Function
End If
Next
End Function
The next function is used to actually update the other field in the row based on the after update event. I use the GetControlValue Function listed above in this function. This is just for the operations column.
Private Function OperationsUpdate(RowNumber As Integer)
Dim Operations As Object
Dim Time As Object
Dim DecTime As Object
Dim LaborRate As Object
Set Operations = GetControlValue("cb_op" & RowNumber)
Set Time = GetControlValue("tb_time" & RowNumber)
Set DecTime = GetControlValue("tb_DecTime" & RowNumber)
Set LaborRate = GetControlValue("tb_LbrRate" & RowNumber)
Set Cost = GetControlValue("tb_Cost" & RowNumber)
If IsNull(Time.Value) Then
If IsNull(Operation.Value) Then
Cost.Value = ""
LaborRate.Value = ""
Else
LaborRate.Value = DLookup("LaborRate", "OperationsType", "[operationsID] = " & Operation.Value)
End If
Else
If IsNull(Operation.Value) Then
Cost.Value = ""
LaborRate.Value = ""
Else
LaborRate.Value = DLookup("LaborRate", "OperationsType", "[operationsID] = " & Operation.Value)
Cost.Value = DecTime.Value * LaborRate.Value
End If
End If
FormObjectsUpdate2 (RowNumber)
Set Operations = Nothing
Set Time = Nothing
Set DecTime = Nothing
Set LaborRate = Nothing
Set Cost = Nothing
End Function
Finally this is how my After update events look per control. I post just the first three not all 21 to save space.
Private Sub cb_op1_AfterUpdate()
OperationsUpdate (1)
End Sub
Private Sub cb_op2_AfterUpdate()
OperationsUpdate (2)
End Sub
Private Sub cb_op3_AfterUpdate()
OperationsUpdate (3)
End Sub
I just posted the function and the code for the first column operations all the other after update events are now having the same issues too.

Referencing value from Excel Listbox item in .Match function in VBA

I am hoping to use the string value of a selected Listbox item in a .Match function within VBA - I need the the value '1' to be entered into the row where the value of the selection matches a value in column "A:A", on a specific column.
What I thought I would be able to do is to use a .value argument for the selected ListBox item, however this seems to either error out or give me a Boolean response, which isn't what I am after (I am after the actual string value of the item).
I have already looped through all items to set the Selected argument to True, and then I am looping through the list one by one to add '1' to the correct range.
Here is the code I thought would work (but doesn't, it throws an error of "Run-time error '13': Type mismatch" which is presumably down to the .Value not being a String.
For x = 0 To Me.CreditsEmployeesListBox.ListCount - 1
Me.CreditsEmployeesListBox.Selected(x) = True
Next
For i = 0 To Me.CreditsEmployeesListBox.ListCount - 1
If Me.CreditsEmployeesListBox.Selected(i) = True Then
employeeRow = WorksheetFunction.Match(Me.CreditsEmployeesListBox(i).Value, IndexSheet.Range("A:A"), 0)
IndexSheet.Range(Cells(employeeRow, showCodeColumn).Address).Value = 1
End If
Next
It errors out on the 'employeeRow = ...' line. Here, I am essentially trying to ask it:
employeeRow = WorksheetFunction.Match(<value of the currently referenced ListBox item>,IndexSheet.Range("A:A"),0)
Is this possible with VBA or am I going about this the wrong way?
Thanks
Matt
As an "hybrid" answer (as there is more than one problem) try this:
For x = 0 To Me.CreditsEmployeesListBox.ListCount - 1
Me.CreditsEmployeesListBox.Selected(x) = True
Next
Dim employeeRow As Variant
For i = 0 To Me.CreditsEmployeesListBox.ListCount - 1
If Me.CreditsEmployeesListBox.Selected(i) = True Then
employeeRow = Application.Match(Me.CreditsEmployeesListBox.List(i), IndexSheet.Columns(1), 0)
If IsNumeric(employeeRow) Then IndexSheet.Cells(employeeRow, showCodeColumn).Value = 1
End If
Next
This also should avoid VBA-errors.
If any questions are left, just ask :)