I have an Access 2002 database/application where my clients can enter multiple information about their own clients, including a code which follow some rules.
However, when they view these information after they have been entered, I need to hide every characters in this code except for the 4 last characters. However, the agent needs to be able to edit this code if it needs to be modified.
So basically, I have 3 phases possible:
First time information are filled, empty data. The field must show the characters entered.
At a later date, the code must be hidden in some way to show only the last 4 characters. It can be with * or simply the last 4 characters, but the user must not be able to see what is before these.
The agent edit the code, the code must then be properly modified in the database. The characters must be shown.
I tried to show only the 4 last characters, however my database gets modified... So the code gets cut in the database.
I wrote the following function to obscure sensitive data. Its primary use is to discourage shoulder surfing. I'm not sure if it will meet your particular needs, but it is simple, straightforward and may help others who stumble upon this question.
'Use to hide data in sensitive fields (e.g., BirthDate, PhoneNum, SSN)
'Usage: Ctl OnEnter property: =ObscureInfo(False, Form.ActiveControl)
' Ctl OnExit property: =ObscureInfo(True, Form.ActiveControl)
' Form Open property: =ObscureInfo(True, [BirthDate], [HomePhone], [SSN])
Function ObscureInfo(HideIt As Boolean, ParamArray Ctls() As Variant)
Dim Ctl As Variant
For Each Ctl In Ctls
If HideIt Then
If IsNull(Ctl.Value) Then
Ctl.BackColor = vbWhite
Else
Ctl.BackColor = Ctl.ForeColor
End If
Else
Ctl.BackColor = vbWhite
End If
Next Ctl
End Function
Wow - I'm shocked that this hasn't been answered sufficiently. The best answer is to use an unbound text box in your form instead of a bound one. First you'll need to make your unbound text box populate the actual field. You'll do that in the AfterUpdate event.
Private Sub UnboundTextBox_AfterUpdate()
[MyField] = Me.UnboundTextBox
End Sub
Then you'll need to set an OnCurrent event to populate your unbound text box with the protected view whenever the agents view the record:
Private Sub Form_Current()
Me.UnboundTextBox = String(Len([MyField])-4, "*") & Right([MyField], 4)
End Sub
However, you also want to let your agents edit or view the full code later, if necessary. The best way to do this would be to set the OnEnter event for your unbound text box to pull the whole field value, so the agent can see and edit it - effectively the reverse of your OnUpdate event.
Private Sub UnboundTextBox_Enter()
Me.UnboundTextBox = Nz([Field1]) 'The Nz deals with Null errors
End Sub
I've used this with a field displaying SSN's and it works like a charm.
Related
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
My management wants an access database that can have a way to control which users can see specific fields on a form. For example, if a manager logs in, a form will display the performance rating field. For any other user, the performance rating field will not be visible.
So far the below are some of my options:
1) Use VBA to detect the User Name of the access application and if its Manager's name, then the textbox is visible.
2) Use a username reference table that requires users to login. Users with special access will have the textbox visible.
3) Have a special little button on the form that, if someone clicks, will load a small password dialog and then set the text visible.
Which option would be the most difficult to implement?
The idea of using a Login form for user specific information is something that is available all over the internet. I would suggest a combination of all three of your ideas would be just the perfection solution.
For either of the two methods you have (1 & 2) you first need a Table that will hold the information. The table does not need to be complex (at least not right away). The table should be of the following structure,
tbl_Employees
-------------
EmpID - Auto Number - PK
EmpNam - Text
EmpUserName - Text
EmpPassword - Text - Input Mask (If required)
IsManager - Yes/No - Default - No
Then you will have to create a Form, a basic form that will have three controls, two Text Boxes and one Button. The first text box is where you will enter the Employee User name and the second for Password. Finally a button, behind which the magic happens. The code (simplified) behind the button would be something along the lines of.
Private Sub LogInBtn_Click()
On Error GoTo errOccured
Dim eQryStr As String, empRS As DAO.Recordset
eQryStr = "SELECT EmpID, EmpUserName, EmpPassword, IsManager FROM tbl_Employees WHERE EmpUserName = '" & Me.UserNameTxt & "'"
Set empRS = CurrentDb.OpenRecordset(eQryStr)
If empRS.RecordCount <> 0 Then
If Me.PwdTxt = empRS!EmpPassword Then
If empRS!IsManager Then
DoCmd.OpenForm "ManagerForm"
Else
DoCmd.OpenForm "EmployeeForm"
End If
Me.Visible = False
Else
wrongEntry "Password"
End If
Else
wrongEntry "User Name"
End If
exitOnError:
Set empRS = Nothing
Exit Sub
errOccured:
wrongEntry "User Name/Password"
Resume exitOnError
End Sub
Private Sub wrongEntry(entityStr As String)
MsgBox entityStr & " is incorrect (OR) omitted, please check again.", vbCritical
End Sub
As you can see, I have used
(a) A Recordset object than a simple DLookup. I prefer recordset object, you can use a DLookup, but you have to make sure you handle Null (if the criteria is not met).
(b) A separate Form for Managers and Employees. I imagined there would be a lot more on a managers form that is not available on a Employee form. If you do not wish to go this way, but use one form you can, but you need to inform the opening form of who is logging in - Using the OpenArgs property of the OpenForm method.
Just if you feel to simply avoid all the hassle and use an Password box, to get the access of the TextBox. Simply follow the instructions on this thread - Password Box - A variant of Input Box. Then create a button on the form you currently have, then on click of the button, you simply code the following.
Private Sub AllowAccessButton_Click()
If Call_Password_Box = "yourPassword" Then
Me.yourHiddenTextBox.Visible = True
Else
MsgBox "Sorry you are not authorised to vies this information (OR) Incorrect Password", vbCritical
End If
End Sub
PS: The Hidden text control should be set to invisible, preferable in the Form current event.
I hope this helps.
I've implemented option nr 1 and 2, they we're quite easy to build.
option nr 3 seems equally difficult.
The question within my work environment would be which option would be low in (account)maintenance and which would demand little effort from the users.
From that view option nr.1 has been more successfull.
(And i would rather build different forms in stead of turning the view setting of field ON/OFF)
I am building a form In MS Access for users to input data but there are too many possible fields. Most of the time only about half the fields will be used.
I thus would like to have certain fields appear only depending on what the user inputted on a prior given field.
Ex: user enters project number, title, then he checks a "yes/no" engineering. since he checked it this means engineering is impacted so a dozen fields that the user will have to fill out appear.
Is this possible:
1)without VBA
2)with VBA
Probably not possible without VBA.
With VBA for example:
Ensure your form is in Design view
Right click on your Combo Box, Build Event, Code Builder
This opens the code behind your form. It drops you into the default code for the BeforeUpdate event. We want the Change event instead, so at the top right change the drop down from BeforeUpdate to Change. This will give you a bit of code like this:
Private Sub Field1_Change()
End Sub
Inside here, you want to check the value of the combo box and hide fields as required:
Assuming the name of your combo box is Field1 (yours of course will be different), you add some code so it looks like this to hide Field2:
Private Sub Field1_Change()
If Field1.Value = "Yes" Then
Me.Field2.Visible = False
End If
End Sub
Note you need to know the names of all your fields - this is in the Other tab, Name field in the properties box (F4). You should give all of your fields sensible names so you can understand what is going on in the code.
For a check box, follow exactly the same procedure, but you probably need to use the Click event. Just experiment.
Sample check box code:
Private Sub Check5_Click()
' Note: vbTrue = -1
If Me.Check5 = vbTrue Then
MsgBox ("Ticked")
Else
MsgBox ("Not Ticked")
End If
End Sub
I have a form that will show certain fields after a list box value is selected. I use the AfterUpdate function. It has worked so far. My code is below. ProjectName and ProjectNumber are fields you only want displayed if Engineering is selected. OtherName and OtherNumber are fields you only want to show if it is a "NotEngineering" project.
Insert this code by clicking on the Field that selects the project type, go to the Event tab on the property sheet, and click "After Update" and choose code builder and paste in VBA.
Private Sub ProjectType_AfterUpdate()
If ProjectType.Value = "Engineering" Then
Me.ProjectName.Visible = True
Me.ProjectNumber.Visible = True
Else
Me.ProjectName.Visible = False
Me.ProjectNumber.Visible = False
End If
If ProjectType.Value = "NotEngineering" Then
Me.OtherName.Visible = True
Me.OtherNumber.Visible = True
Else
Me.OtherName.Visible = False
Me.OtherNumber.Visible = False
End If
End Sub
There is a way to do not-quite-this without VBA. I'd definitely recommend VBA though, because you can do a lot more with it.
Rather than hiding, try disabling the unnecessary fields with conditional formatting by expression.
right-click on the control you want disabled.
go down and click on 'Conditional Formatting'
Add a new rule
Select 'Expression is'
example:
[fld1]="yes"
hit the disabled box
click ok
click ok
now the control you've selected will disable if field 1 has "yes" selected.
I have the same problem and I did the following:
Private Sub Field1_Click()
If Field1 = "Yes" Then
Me.Field2.Visible = True
Else: Me.Field2.Visible = False
End If
End Sub
but now I have other problem, when I change record, the field that I choosen to be visible in the last record is now visible on the current record, although I have not choosen any option.
Thank you,
I have a report which is like an hourly schedule for a whole week. Each cell in the schedule is 1 day for 1 machine. Each column is one of 7 machines. Thus, a row consists of 7 day cells. So far so good, but each day needs 18 rows of text (a Memo field) and - each row needs to be colorized. I've created 18 labels numbered Label33 and up, for each of the 7 columns.
I'm setting their BackColor with a pretty large case statement of 7 cases and 18 x Labelnn.Backcolor=aColor in each case.
However, I'd like to replace this with something that forms the control_name="Label" & nn, and do something like Me.control_name.BackColor=aColor.
Possible? How? (No, considering how flexible VBA-Access is I haven't tried the above yet.)
Cody Gray is correct but this is not helpful if you do not know what a control array is.
The following is Excel VBA but I have done the same with Access VBA but not recently. I think the syntax is the same but I do not guarantee it.
I created a workbook, inserted a form, pulled a selection of controls onto it and then ran the following code:
Option Explicit
Sub TestControls()
Dim InxC As Long
Load UserForm1
With UserForm1
For InxC = 0 To .Controls.Count - 1
Debug.Print .Controls(InxC).Name
Next
End With
End Sub
The output to the immediate window was:
Label1
CommandButton1
ComboBox1
CommandButton2
OptionButton1
You can see that Label1.xxx is exactly the same as .Controls(0).xxx. I name my controls systematically so I can run code like:
With UserForm1
For InxC = 0 To .Controls.Count - 1
If Mid(.Controls(InxC).Name,1,5) = "lblXx" Then
' Code to set properties of all lblXx controls
End If
Next
End With
I use this functionality most when I do not know how many of a particularly type of control I need. I create 10, say, of them which is more than I will ever need and make them all invisible.
At run time, I make those I need visible and set their top and left properties as necessary.
I agree with Cody Gray. Use the Control Arrays.
Here is a link which will help you
http://www.siddharthrout.com/index.php/2018/01/15/vba-control-arrays/
Though Tony has given you a relatively easy way to do it but there are instances where you might add or delete a control or simply mess up on the sequence. Also there are instances when you simply cannot leave the label as Label1. You might be adding some text to it so that it signifies something...
In such a scenario use TypeOf instead of Name
For example
Option Compare Database
Private Sub Command1_Click()
Dim ctl As Control
For Each ctl In Me.Controls
If TypeOf ctl Is Label Then
Debug.Print ctl.Name
End If
Next ctl
End Sub
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.