Wildcard in Access VBA - ms-access

This is kind of a follow up question to this post: Access VBA recordset string comparison not working with wildcard but I don't have the rep to answer/comment on it to ask it in house. What I'm curious about is this line of code specifically:
If ![ACOD] = "*_X" Then '"$ICP_X" works
Debug.Print ![ACOD] 'For testing
'.Delete
End If
I want to know if this can be modified so that on a button click, it looks at all fields in a form with the field name of *_New (with the hope to catch all fields where the name ends in _New) and if they are not Null then confirm that the user wanted to make a the change indicated in the field. I was thinking of something along the lines like this:
If Not isNull(*_New.value) Then
If Msgbox ("Do you want to make these changes?",vbOKCancel, "Confirm Changes") = 1 Then
'### Do something with the record ###
End If
End If
EDIT
As of posting the above information, I did not have the Microsoft VBScript Regular Expressions Reference installed, currently I have version 5.5 (it was the latest version). With that installed (referenced?) and seeing the information from this site MS Access with VBA Regex, I'm wondering if it's better to do something like this:
Dim re As RegExp
Set re = New RegExp
re.IgnoreCase = True
re.Global = True
re.Pattern = "*_New"
If ##Not sure on syntax to match all fields## Then
Msgbox(##Same stuff as above MsgBox##)
End If
EDIT 2
Here's a sample case for my form I'm working on. Each of the fields to the right have names that end in _New. What I want to do is on the button click, to check and see what fields on the right have been filled in and ask the user if they want to confirm the changes to the record.

Not sure what you are trying to achieve but there is a way to access the control collection in a form. Here is a public function where you can loop through all controls and check its name.
Public Function FN_CONFIRM_CHANGES(iSender As Form)
Dim mCtl As control
For Each mCtl In iSender
If VBA.Right(mCtl.name, 4) = "_New" Then
Debug.Print mCtl.name & " is a match and its a " & VBA.TypeName(mCtl)
End If
Next mCtl
End Function
Call this function like
FN_CONFIRM_CHANGES Me 'Where me is referencing the form you are in.
You can modify the above code to return a boolean value to stop further execution if user decided not to save your changes or whatever logic you are trying to implement.

Related

Access VBA - Turn off aggregation when opening form/sub-form

What I'm trying to do is, whenever a user opens a form (and the sub-form that opens by default), I want to search through all the columns (controls?) on the form, check to see if they are currently set to aggregate (sum, count etc.) with Access' built-in Totals row, and if so, set them to not aggregate.
The reason for this is there are several millions records that are stored, so when someone queries it down to 3-4 and turns on Sum, then closes it, when the next person opens it, it tries to sum millions of numbers and freezes up. The form displays the queried results from a table which is populated via SQL (I think, if that sentence makes sense). Here's what I have so far:
Private Sub Form_Load()
'this form_load is in the UserApxSub sub-form, for reference
Call De_Aggregate
End Sub
Private Sub De_Aggregate()
Dim frm As Form, con As Control
Set frm = Forms!UserAPX!UserApxSub.Form!
For Each con In frm.Controls
If con.ControlType = acTextBox Then
If con.Properties("AggregateType").Value <> -1 Then
'crashes on following line
con.Properties("AggregateType").Value = -1
End If
End If
Next con
End Sub
I have not so much experience in Access VBA (usually work in Excel VBA) so please forgive me if I'm entirely off the mark here. The command con.Properties("AggregateType").Value = -1 doesn't throw an error, but Access just straight-up crashes when reaching that line specifically.
I've tried a number of variations in the syntax with no success, and I've also tried looping through other elements of the file (tabledefs, querydefs, recordsets, etc.) as well to see if I'm trying to change the wrong value, but the controls on this subform are the only things in the entire .mdb file that results when I search for elements with the AggregateType property.
I switched out the line that errors with Debug.Print con.Name & " - " & con.Properties("AggregateType").Value and I can check, have nothing return anything other than -1, turn on aggregation in some column manually, and have it return the correct result (0 for sum for example), so I think I'm looking in the right place, just missing some key factor.
I've been working on this for a couple weeks with no success. Any way to fix what I have or point me toward the right direction would be greatly appreciated!
This is not necessarily the answer but I don't have enough reputation
to give a "comment"...
I tried your scenario and verified can change the property value as you are however I did not iterate through all controls and simply used an onDoubleClick event on a column to simulate.
I would suggest trying to fire your sub with Form_Open or Form_Current to see if the property is getting reset after your code has been called for some reason.
UPDATE:
You are referencing the "Subform" Object of your main Form:
Set frm = Forms!UserAPX!UserApxSub.Form!
Try referencing the actual UserApxSub FORM explicitly.
Something like Set frm = Forms!UserApxSub! (assuming UserApxSub is the name of the form)
then stick in the Form_Open of your main form:
Private Sub Form_Open(Cancel As Integer)
'// the following would set a single control only. You can add your loop through all controls
Me!{your control name}.Properties("AggregateType").Value = -1 '// set control in your main form
Form_UserApxSub!{your control name}.Properties("AggregateType").Value = -1 '// set control in your "sub" form
End Sub

Iterate through tabs in TabControl to hide tabs based on names

I am working on a Microsoft Access Database (Office 2010) and I have a tab control on the bottom of my form that displays equipment information based on the type of equipment you select. I am trying to make it dynamically display and hide tabs as determined by the name of the tabs.
To accomplish this I have used the following naming convention for my tabs.
Tab_Static_Description
Tab_Static_Comments
Tab_Static_Maintenance
Tab_Config_Computer1
Tab_Config_Computer2
Tab_Config_Printer1
Tab_Config_Scanner1
Tab_Config_Telephone1
Tab_Config_Display1
My TabControl is called "Tabs"
I have set the form to execute the function below(the function i need help with) on form load and onchange of the equipment drop down menu.
To call the function i use the following code.
DisplayTab
Here is the code to far. I have been googling this a bit and I have yet to find someone doing something similar to me and have found myself a little lost on this one. Any help would be greatly appreciated.
Function DisplayTab(EquipmentType As String)
Dim Ctl As Control
For Each Ctl In Me.Tabs
If Ctl.Name.contains("Tab_Static_") Then
Me.Tabs.Pages.Item(Ctl).Visible = True
ElseIf Ctl.Name.contains("Tab_Config_") Then
If Ctl.Name.contains("Tab_Congig_" & EquipmentType) Then
Me.Tabs.Pages.Item(Ctl).Visible = True
Else
Me.Tabs.Pages.Item(Ctl).Visible = False
End If
Else
MsgBox "There is an unusually named tab. Please contact the database adminsitrator."
End If
Next Ctl
End Function
The error I am getting is "Invalid qualifier" but after googling that message it doesn't exactly make sense.
If Ctl.Name.contains("Tab_Static_") Then
is not valid VBA syntax. You are probably thinking of some similar method that might be availabe in VB.NET. In VBA you would do something like
If InStr(Ctl.Name, "Tab_Static_") > 0 Then

How can I check if an instance of a Access 2010 form is still open?

Preface
I have a need to create multiple copies of a search form (in Access 2010) that returns a value to the calling (which is also the form that created the instance of the form).
As mentioned these forms could and will have multiple copies running at the same time for example a user could want to add a company to something so they:
Click "select company" and open an instance of the company search screen
Then open the company editor (leaving the original company search/selection screen open) as they notice the company has a parent company that hasn't been added yet.
They then click the "Select Parent Company" button that opens ANOTHER instance of the search and select screen
They find the parent company
Select it which closes the second search screen and the parent company is added to the first company.
The user then selects the modified company using the original search screen which again closes the original search screen and returns the selected company to what ever form they originally initialised the first search...
This all allows the users to update and correct data as and when they find error which reduces the likelihood of them forgetting and makes it much quicker!
MOST of this is fine now but I have had a lot of problems with instances of a form not being able to open as a "acDialog" thus stopping the calling code running until the search was done (see this question for more info) and the solution I have gone with is to simulate the pausing of the calling code by using a endless loop and checking if the search screen instant is still visible. Then when the user selects something on the search screen instant it puts the value in a hidden field in the search screen and hides it's self (not closed). The calling function then sees it's hidden grabs the value from the hidden field and unloads the instant.
Problem
I can check if the form is hidden using FormInstant.Visable but if the user closes the form this causes an error and the code I would normally use to check if the form exists requires a form name and as it's an instant of a form all the forms have the same name! I do have a reference to the form as it is stored in a local "form" object... The code I would normally use is:
CurrentProject.AllForms("FormName").IsLoaded
So how can I check for a instant of a form being loaded still?
An old question but here's what experience taught me: If One, Two, ... instances of FormDefn opened then user closes One (Master which is the only one that can be designed), Forms(FormName) gives an error, Forms(Form) gives wrong object,
but Forms(NumberIndex) does still exist with .Name = FormName!
OpenForm creates the Forms(FormName) object. Once closed Forms(FormName) gives an error. Any "Set xForm = New Form_xxx" creates forms in the forms collection that can only be accessed by the collection number index, and cant be designed.
So to later find a multi-instance form use something like:
Dim FormIdx As Integer
Dim lForm As Access.Form
For FormIdx = 0 To Application.Forms.Count - 1
Set lForm = AccessFunc.Appl.Forms(FormIdx)
If lForm.Name = pFormName Then
IsFormOpened = True
Set rForm = lForm
GoTo IsFormOpened_Exit
End If
Next
LOL I just realised while re-reading my msg that I can likely trap the error to work out if the form is open or not!
I have quickly written this and it seems to work fine:
Public Function IsFormLoaded(ByRef FormToTest As Form, _
Optional ByRef bIsVisable As Boolean = False) As Boolean
Dim lErrorNum As Long
bIsVisable = False
On Error Resume Next
bIsVisable = NewFormClone.Visible
lErrorNum = Err.Number
On Error GoTo 0
If (lErrorNum = 0) Then
IsFormLoaded = True
Else
IsFormLoaded = False
End If
End Function
Guess it doesn't really mater who answers the question as long as it is answered and the next guy/gal can use it! :)
I will leave this open for a bit and if nobody finds a better answer I will mark this as it...
I like your answer. As for the loop/wait idea? A better way is to always include a reference in each form. I useally declare form module variable called frmPrevious.
Create instance of form
Instance.frmPrevious = me
So now we have the form "call" some code when the form is closed in place of some "visible" + looping code setting.
So in the close code of the form we have:
frmPrevious.FunctionCodeToRun
The above solves a good many issues, but one is you don't need dialog (which as you note cannot use) And you also dump the need for writing "loop + wait" code from the calling code.
This does however mean that your code continues in a new function in the calling form. I thus usually place that function right below the calling code in the calling form. I also tend to use a standard name for that function. I find this trade off worth it as opposed to loop/wait and continuing in the same code routine (I do agree this "continue" in code is often preferable, but then again having to write looping and wait code is not really that clean).
Try this also
Function IsLoaded(strFrmName As String) As Boolean
' Determines if a form is loaded.
Const conFormDesign = 0
Dim intX As Integer
IsLoaded = False
For intX = 0 To Forms.Count - 1
If Forms(intX).FormName = strFrmName Then
If Forms(intX).CurrentView <> conFormDesign Then
IsLoaded = True
Exit Function ' Quit function once form has been found.
End If
End If
Next
End Function
You can call the above function it in your project like so
If Not isLoaded("MyForm") Then
MsgBox "MyForm is Not Loaded"
End If

How do I chain forms in Access? (pass values between them)

I'm using Access 2007 and have a data model like this...
Passenger - Bookings - Destinations
So 1 Passenger can have Many Bookings, each for 1 Destinations.
My problem...
I can create a form to allow the entry of Passenger details,
but I then want to add a NEXT button to take me to a form to enter the details of the Booking (i.e. just a simple drop list of the Destinations).
I've added the NEXT button and it has the events of
RunCommand SaveRecord
OpenForm Destination_form
BUT, I cant work out how to pass accross to the new form the primary key of the passenger that was just entered (PassengerID).
I'd really like to have just one form, and that allow the entry of the Passenger details and the selection of a Destination, that then creates the entries in the 2 Tables (Passenger & Bookings), but I can't get that to work either.
Can anyone help me out please?
Thanks
Jeff Porter
Actually the best suggestion I can give here is to not actually pass parameters. Simple in your form's on-open event, or even better is to use the later on-load event is to simply to pick up a reference in your code to the PREVIOUS calling form. The beauty of this approach is that if overtime you go from one parameter to 10 parameters then you don't have to modify the parameter parsing code, and you don't even have to modify the calling code. In fact there's no code to modify AT ALL if you decide to examine previous values from the calling form.
So, keep in mind using open args is only a one way affair. You can not use it to return values to the calling form. Furthermore all of the open args params will have to be strings. So, you lose any ability of dating typing such as real integers or even date and time formatting which can be quite problematic to parse out. And as you can see the example here the code to parse out strings can get a little bit messy anyway.
The simple solution is that in each form where you want values from the PREVIOUS from, simply declare a module level variable as follows
Dim frmPrevous as form.
Then in your forms on load an event, simply pick up the name of the previous form as follows:
Set frmPrevious = screen.ActiveForm
That is it. We are done!
We only written one line of code here. (ok two if you include the declaration statement). At this point on words ANY place in your form's current code you can reference the events properties and any field or value in the previous form by doing the following
Msgbox "PK id of previous form = " & frmPrevious.ID
And let's say for some reason that you want the previous form to re-load records as in a continues form. Then we can go:
frmPrevious.Requery
Or, force a record save:
frmPrevious.Dirty = false
So, the above becomes almost as natural and handy as using "ME" in your current code. I find this so simple and easy I think this should have been part of access in the first place.
And as mentioned the uses are endless, I can inspect ANY column or value from the calling form. You can even declare variables and functions as public, and then they can be used.
And, note that this works BOTH WAYS. I can stuff and change values in the calling form. So I can update or change the value of any value/column/control from the calling form.
And there is absolutely no parsing required. Furthermore the above code step even works if the same existing form is called by different forms. In all cases the forms calling ID can be picked up without modifying your code.
And even in the case of that I have many different forms launching and calling this particular form, you can still pull out the ID column. And, in the case that columns could be different from different forms, you can simply declare public variables or public functions in the calling form of the SAME name. So, if I wanted to call a form that needs the DateCreate, but each form did NOT have a consistent column name of DateCreate (maybe invoiceDateCreate and inventory Date Create), then you simply declare a public function in the calling forms with a constant name. We then can go:
Msgbox "Date created of calling form record = " & frmPrevious.DateCreated
So, Date created can be a public variable or public function in the previous form that could be any conceivable column from the database.
So don't pass values between forms, simply pass a reference to the calling form, not only is this spectacularly more flexible than the other methods showing here, it's also an object oriented approach in which you're not limited to passing values.
You can ALSO return values in the calling form by simply setting the value of any control you want (frmPrevous.SomeContorlName).
And as mentioned, you not limited to just passing values, but have complete use of code, properties such as dirty and any other thing that exists in the calling form.
I have as a standard coding practice adopted the above for almost every form. I simply declare and set up the form previous reference. This results in a handy previous form reference as useful as "ME" reference when writing code.
With this coding standard I can also cut and paste the code between different forms with different names and as a general rule my code will continue to run without modification.
And as another example all of my forms have a public function called MyDelete which of course deletes the record in the form, therefore if I want to delete the record in the previous calling form for some reason, then it's a simple matter to do the following
frmPrevious.MyDelete
So I can save data in the previous form. I can requery the previous form, I can run code in the previous form, I can return values to the previous form, I can examine values and ALL columns all for the price of just ONE measly line of code that sets a reference to the calling form.
I do this by defining properties in the form with Property Let and Property Get and passing values to those properties after opening the form, like this:
in the destination form:
Dim strCallingForm As String
Dim lngKey As Long
Public Property Get callingform() As String
callingform = strCallingForm
End Property
Public Property Let callingform(NewValue As String)
strCallingForm = NewValue
End Property
Public Property Let PrimaryKey(NewValue As Long)
lngKey = NewValue
End Property
in the calling form:
Sub btnGo_Click()
Const cform As String = "frmDestinationForm"
DoCmd.OpenForm cform
Forms(cform).callingform = Me.Name
Forms(cform).PrimaryKey = Me.PrimaryKey
Me.Visible = False
End Sub
(end)
I would use the openargs method of the form. That way you can pass one piece of data to the new form from any other form. You can also expand of this by sending a delimited string of arguments and then splitting them out. For example I have a form for editing agent activity that is passed the date, the agents name, agents ID and team in the open args
DoCmd.OpenForm "frmEdit_agent_activity", , , , , acDialog, Item & "|" & Me.txtDate & "|" & Item.ListSubItems(1) & "|" & Item.ListSubItems(2)
The form then uses this to pre populate the controls
Private Sub Form_Load()
If IsMissing(Me.OpenArgs) = True Or IsNull(Me.OpenArgs) = True Then
'no args, exit the form
DoCmd.Close acForm, "frmEdit_agent_activity", acSaveNo
Else
'this form has 4 open args
'1 Staff ID
'2 Date
'3 Team_ID
'4 Staff Name
Me.txtStaff_ID = GetDelimitedField(1, Me.OpenArgs, "|")
Me.txtDate = GetDelimitedField(2, Me.OpenArgs, "|")
Me.txtTeam_ID = GetDelimitedField(3, Me.OpenArgs, "|")
Me.txtStaff_name = GetDelimitedField(4, Me.OpenArgs, "|")
End If
End Sub
Ohh and here is the GetDelimitedField function
Function GetDelimitedField(FieldNum As Integer, DelimitedString As String, Delimiter As String) As String
Dim NewPos As Integer
Dim FieldCounter As Integer
Dim FieldData As String
Dim RightLength As Integer
Dim NextDelimiter As Integer
If (DelimitedString = "") Or (Delimiter = "") Or (FieldNum = 0) Then
GetDelimitedField = ""
Exit Function
End If
NewPos = 1
FieldCounter = 1
While (FieldCounter < FieldNum) And (NewPos <> 0)
NewPos = InStr(NewPos, DelimitedString, Delimiter, vbTextCompare)
If NewPos <> 0 Then
FieldCounter = FieldCounter + 1
NewPos = NewPos + 1
End If
Wend
RightLength = Len(DelimitedString) - NewPos + 1
FieldData = Right$(DelimitedString, RightLength)
NextDelimiter = InStr(1, FieldData, Delimiter, vbTextCompare)
If NextDelimiter <> 0 Then
FieldData = Left$(FieldData, NextDelimiter - 1)
End If
GetDelimitedField = FieldData
End Function
Have you considered subforms? There are ideal for one to many relationships. The Link Child Field(s) will be automatically completed from the Link Master Field(s).
If you need an example of subforms in actions, the Northwind database ships with all versions of Access, or you can download which ever is relevant.
2007
http://office.microsoft.com/en-us/templates/TC012289971033.aspx?CategoryID=CT102115771033
2000
http://www.microsoft.com/downloads/details.aspx?familyid=c6661372-8dbe-422b-8676-c632d66c529c&displaylang=en
You can use OpenArgs.
But I would also suggest that you also consider using a tab control.
That allows you to have different sets of controls on the same "screen real estate", using one single recordset, or using sub forms to show child recordsets.
In that case, you could use the "Next" button to just switch to the next page of your tab control.

Login Screen in Access with forms, macros and queriesms-

I have an embarrassing question regarding Access. I can build relational databases in my sleep but I have always used PHP, mySQL and HTML to build my applications.
I've hated Access with a passion for various reasons ever since I can remember but now I'm stuck using it for a particular project.
I have a number of tables, one of which is customer, which among other things has a username and password field.
When I start the database, I want a login form to appear, which I can do using the AutoExec macro, I've made the form with a username and password field. After that, I get stuck with the logic of querying for the username/password and then showing a new form if correct or an error if not.
Could anyone help me out with making the macro and query work together?
Clarification: I am trying to do this without coding whole Visual Basic macros, if at all possible, I want to be able to do it using the macro builder thingumy.
Thanks
Given a form frmLogin, with 2 text boxes, txtUserName and txtPassword, and a command button, you can try the following
Private Sub Command0_Click()
Dim rec As Recordset2
Set rec = CurrentDb.OpenRecordset("SELECT * FROM Customer WHERE username = """ & txtUserName.Value & """ AND password = """ & txtPassword.Value & """")
If (rec.RecordCount > 0) Then
DoCmd.OpenForm "frmMain"
DoCmd.Close acForm, "frmLogin"
End If
End Sub
Malphas -
It is actually possible to do this without using VBA, but I am wondering whether the reason why you don't want to use VBA is because of the Trust issue. In which case, this won't be possible, because the macro actions Close and Quit are disallowed if the database is not trusted.
Whilst you can to run actions in the AutoExec macro beyond the point where you use the OpenForm command, I think it is neater to continue the next actions on the form itself. First because you can't really do branching in a macro; secondly because it is more modular to keep actions to do with the form actually on the form.
In the example below, my sample login form is called LoginForm, and the username text box is txtUserName, and the password text box is called txtPassword.
The first thing to do is to protect the dialogue from the simple act of letting the user close the dialogue and escape into the database design screen. The best way to do this is to set a flag called ValidLogin when the form loads. You will set this flag during the login process. When the form is closed, check whether the flag is true. If ValidLogin is false, then close the database.
On the OnLoad event of the Login form, click on the ellipsis button, and choose Macros Builder. In the Macro screen, use the following actions (note that the Condition column is hidden by default - but you'll only need for the next two macros):
Line Condition Action/Arguments
1 SetTempVar, Name = ValidLogin, Expression = False
On the OnUnload event of the Login form, do the same as above, and add:
Line Condition Action/Arguments
1 Not [TempVars]![ValidLogin]
Quit, Options = Exit.
If you run this now, as soon as you close the form, the database will close. To be useful, you need to add the following macro actions to the OnClick event of your Login button:
Line Condition Action/Arguments
1 SetTempVar, Name = Valid Login, Expression = DCount("*","Customer","[Username]=[Forms]![LoginForm]![txtUserName] And [Password]=[Forms]![LoginForm]![txtPassword]")>0
2 Not [TempVars]![ValidLogin]
MsgBox, Message = Invalid Login
3 ... StopMacro
4 OpenForm, Form Name = MainForm
5 Close, Object Type = Form, Object Name = LoginForm, Save = No
Note that in all these examples, I have used embedded macros, not named macros, so you can keep them together with the form. Also note the ellipsis (...) in the last macro, which represents the value of the last condition.
A slight tweak to the above as the code above would be open to SQL injection attacks (yes I know it is only access but it never hurts)
Public Function CheckUserPassword(strUserName As String, strPassword As String) As Boolean
Dim rst As DAO.Recordset
Set rst = DBEngine(0)(0).OpenRecordset("tblUsers", dbOpenTable)
With rst
.Index = "UserName"
.Seek "=", strUserName
If .NoMatch = False Then
If !Password = strPassword Then
CheckUserPassword = True
Else
CheckUserPassword = False
End If
Else
CheckUserPassword = False
End If
End With
rst.Close
Set rst = Nothing
End Function