Characters appended to a string all appear as question marks in VBA - ms-access

I have been using the same code to get the fullname from the current user for a while now and it suddenly started behaving weird.
I have a string variable named CurrentUser which I populate with my function GetFullNameOfLoggedUser().
This return in the immediate window (msgbox also works fine) "Lastname, Firstname (F)" which it is supposed to.
When I want to give the variable to another string variable called 'sql1', it starts to behave weird all of a sudden:
CurrentUser = GetFullNameOfLoggedUser()
Dim sql1 As String: sql1 = "UPDATE Tbl_Records SET Tbl_Records.[Added by] = """ & CurrentUser & """ WHERE Tbl_Records.[Added by] IS NULL;"
The value of sql1 suddenly becomes:
UPDATE Tbl_Records SET Tbl_Records.[Added by] = "Lastname, Firstname (F)? ????? ?????????????????? ??? ?? ?????
Does anybody have a clue where all the question marks come from?
Disclaimer Lastname and Firstname are obviously regular values, they are omitted for the sake of privacy.
Extra info:
To get the full network name, I am using this fine piece of code from Dev Ashish which makes use of windows API:
http://access.mvps.org/access/api/api0066.htm
The function has been dimmed as string. I have added an "" at the end of the function to ensure the value is a string type:
Function GetFullNameOfLoggedUser(Optional strUserName As String) As String
...
GetFullNameOfLoggedUser = StrFromPtrW(pTmp.EUI_full_name) & ""
End Function
As seen in the locals window, it truly is a string. (This snap has been taken right before the end of the funcion, so no further changes will happen to the variable.
CurrentUser has also explicitly been defined as a string variable. Option Explicit is also active on every page.

There does seem to be something a bit strange about the string that fGetFullNameOfLoggedUser() returns. When I run the code
Dim s As String
s = fGetFullNameOfLoggedUser()
Debug.Print Len(s)
Debug.Print s
I get
13
GORD THOMPSON
which looks correct. However, if I change the last Debug.Print to
Debug.Print "|" & s & "|"
I get
|GORD THOMPSON?
with the final character being a question mark ? instead of a pipe |. However, if I Trim() the string
Debug.Print "|" & Trim(s) & "|"
then I get
|GORD THOMPSON|

Related

Why is is my MS Access control not accepting my "on not in list" event code

I've been using the Access "On Not In List" event for a long time. It allows you to limit the items in your combo box to a particular list, but allows the user to add an item to combo's record source on the fly if they want to enter something that isn't already there. You set the "Limit To List" property of the combo box to Yes, but then you put some code behind the "On Not In List" event. Intermittently, I get a situation in Access 2016 where this doesn't seem to work. I get the standard "The item is not in the list." error when trying to enter a new item, without my code-behind logic being seen and called. What's up?
After banging my head against the wall for a long time, I believe this is bug in Access 2016 and I think I stumbled on a fix. I set the form's RecordSetType property to Snapshot, closed and saved form, reopened the form in design view, set the RecordSetType property back to Dynaset. This seems to have cleared up the problem. I have no idea why.
But since I'm here. . . some additional details: In the code-behind for each control I use code like this:
Private Sub VendorID_NotInList(NewData As String, Response As Integer)
Response = RU_NotInList("Lookup Vendor", "Description", NewData, gc_strMsgCap)
End Sub
This type of subroutine gets created automatically behind the "On Not In List" event, when you click on the 'code builder' option. I have mine call a utility function that I wrote a long time ago. ("RU" refers a code library.)
The function returns an intrinsic Access integer constant that gets passed straight back to Access to handle.
The inside of that routine looks like this:
Function RU_NotInList(TableName As String, FieldName As String, newdata As String, Optional pstrTile As String) As Integer
Dim rs As DAO.Recordset, db As DAO.Database, n1 As Integer
RU_NotInList = DATA_ERRCONTINUE
On Error GoTo RU_NotInList_Error
If Len(Trim(newdata)) = 0 Then Exit Function
n1 = MsgBox(newdata & " is not in the list. Do you wish to add it?", MB_ICONQUESTION + MB_YESNO, pstrTile)
If n1 = IDNO Then Exit Function
Dim strSQL As String
strSQL = "INSERT INTO [" & TableName & "] ([" & FieldName & "]) VALUES (""" & newdata & """)"
WarningsHour True 'Turns hourglass cursor on, warning messages off.
DoCmd.RunSQL strSQL
WarningsHour False 'Undoes the above.
RU_NotInList = DATA_ERRADDED
Exit Function
RU_NotInList_Error:
RUError "RU_NotInList", Err 'generic error-handling routine in the RU library
Exit Function
End Function
All the all-caps items in the code above are Access intrinsic constants.

VBA loop until function is true, changing passed value each time

I have a form that adds new users. I'm trying to auto generate the username, which needs to be unique. I have a function checkUsername that takes the string passed and checks the db table to see if that username exists. It returns false if the username exists and true if not.
What I'm trying to do is if the username already exists then add a number on to the end and check again. I want to keep looping until a unqiue username is found. Heres my attempt at a loop below, however its returning usernames that already exist. Also I prefer it to increment the number rather than just adding it to the end. Currently the pattern ends up being username1, username12, username123.
'Generate username
Dim Username As String
Username = generateUsername(LCase(Left(FirstName, 1) & LastName))
Function generateUsername(Username As String) As String
Dim i As Integer
i = 1
Do While checkUsername(Username)
If checkUsername(Username) Then
Exit Do
Else
Username = Username & i
i = i + 1
End If
Loop
generateUsername = Username
End Function
What I would do is, when the username exists already (see Andre's answer for the code that will accomplish this a different way):
check what the previous "i" number was, as a string, to see how many characters I need to cut off the end of username
set username equal to a substring of username, which will cut off the last X characters (X being the number of characters the previous "i" was)
add the new "i" to the end username
That will solve your naming issue.
For your issue of looking for unique names, no one will be able to help with that until we can see what the code looks like for the "checkUsername" function. Make sure that that function is returning the value you are expecting, first. I suspect the problem is in there.
EDIT:
I also don't understand why you have an "If/Else" statement in your loop. You already determined that it was true. See below for simplified version (again, assuming I got the code right for VB...I usually do it in C#):
Do While Not checkUsername(NewName)
NewName = Username & i
i = i + 1
Loop
I assume checkUsername() returns true if the username exists?
Then your If condition is wrong - you want to exit if it doesn't exist.
Your loop gets clearer if you use two variables:
Function generateUsername(origUsername As String) As String
Dim i As Integer
Dim Username As String
i = 1
Username = origUsername
Do While checkUsername(Username)
' If Not checkUsername(Username) Then
' Exit Do
' Else
Username = origUsername & i
i = i + 1
' End If
Loop
generateUsername = Username
End Function
Edit: as pointed out by Joel, the additional checkUsername() in the loop isn't needed. Since it probably involves a DLookup or similar, it is actually harmful to performance.
I commented it out above.
Sorry, but this is the corrected version!
Change your code to this instead:
Dim Username As String
Username = generateUsername(LCase(Left(FirstName, 1) & LastName))
Function generateUsername(Username As String) As String
Dim NewName as String
Dim i As Integer
i = 1
NewName = Username
Do While checkUsername(NewName)
If checkUsername(NewName) Then
Exit Do
Else
NewName = Username & i
i = i + 1
End If
Loop
generateUsername = NewName
End Function

Create query in QueryEditor with VBA function call for specifying the WHERE IN clause

I have written a couple of VBA functions which in the end return a Collection of Integers:
Public Function ValidIDs() As Collection
Now I want to run create a query in the QueryEditor with the following condition: WHERE TableID IN ValidIDs(). That does not work since access for some reason does not even find my function as long as it returns a Collection. Therefore I wrote a wrapper around it, which joins the Collection:
Public Function joinCollectionForIn(Coll As Collection) As String
Now a third function which calls ValidIDs(), passes the result to joinCollectionForIn and returns that result. Lets call it GetIDCollectionAsString().
As a result I can now change my query to WHERE TableID IN (GetIDCollectionAsString()). Note the added parenthesis since the IN needs them in any case, they can not just be at the end and the beginning of the String returned by GetID....
Running that query however results in
Data type mismatch in criteria expression.
I guess that results from the fact that I return a String, therefore access automatically wraps that string in ' for the SQL and the IN-clause no longer works because I would check if a number is IN a collection of 1 string.
Therefore my question is:
Is there a way to prevent access from wrapping the returned string for the SQL
or (would be a whole lot better):
Is there an already existing way to pass a collection or array to the WHERE IN-clause?
P.S.: I am currently using a workaround by writing a placeholder in the parenthesis following the IN (e.g. IN (1,2,3,4,5)) and replacing that placeholder in Form_Load with the result of GetIDCollectionAsString() - that works but it is not pretty...
Edit: The final query should look like SELECT * FROM TestTable t WHERE t.ID IN (1,2,3,4,5,6,7). That actually works using above method, but not in a nice way.
Well this required more work than it seems.... i couldn't find a straight solution so here is a workaround
Public Function ListforIn(inputString As String) As String
Dim qdf As QueryDef
Dim valCriteria As String
Dim strsql As String
Dim splitcounter As Byte
Dim valcounter As Byte
Set qdf = CurrentDb.QueryDefs(**TheNameOfQueryYouWantToModify**)
strsql = qdf.sql
strsql = Replace(strsql, ";", "") 'To get rid of ";"
splitcounter = Len(inputString) - Len(Replace(inputString, ",", ""))
For valcounter = 0 To splitcounter
valCriteria = valCriteria & ValParseText(inputString, valcounter, ",")
Next
strsql = strsql & " WHERE TableId IN (" & Left(valCriteria, Len(valCriteria) - 1) & ")"
qdf.sql = strsql
End Function
Public Function ValParseText(TextIn As String, X As Byte, Optional MyDelim As String) As Variant
On Error Resume Next
If Len(MyDelim) > 0 Then
ValParseText = "Val(" & (Split(TextIn, MyDelim)(X)) & "),"
Else
ValParseText = Split(TextIn, " ")(X)
End If
End Function

Replace Function - Preserve Slashes and Other Special Characters

I have a Microsoft Access 2013 database that I created to track time. The database has a FINDREPLACE table that I use to store shortcuts for certain often-used time entry text. The table contains two fields, myFind and myReplace. For example, one value in myFind is "telconf" and the corresponding entry in myReplace is "telephone conference with". There is a button on the time entry form that calls a sub that loops through my FINDREPLACE table and replaces all of the shortcut "myFind" text in the time description with the corresponding "myReplace" text. It works well and saves me from having to repeatedly type out the same lengthy phrases or names I can never remember how to spell.
Here is the sub:
Private Sub myFindReplace(myTime As Integer)
Dim dbs As DAO.Database
Dim rs, rs2 As DAO.Recordset
Dim myMsg, mySQL, myTimeString As String
If Me.Dirty Then
myMsg = MsgBox("You must save your record before running FindReplace", vbOKOnly)
Exit Sub
End If
Set dbs = CurrentDb
mySQL = "SELECT * From TABLEFINDREPLACE"
Set rs = dbs.OpenRecordset(mySQL, dbOpenSnapshot)
myTimeString = DLookup("myDESCRIP", "TABLETIME", "ID = " & myTime)
With rs
Do Until .EOF
myTimeString = Replace(myTimeString, !myFind, !myReplace)
.MoveNext
Loop
End With
rs.Close
myTimeString = UCase(Left(myTimeString, 1)) & Mid(myTimeString, 2)
mySQL = "SELECT * FROM TABLETIME WHERE ID = " & myTime
Set rs2 = dbs.OpenRecordset(mySQL, dbOpenDynaset)
With rs2
.Edit
!myDESCRIP = myTimeString
.Update
End With
rs2.Close
dbs.Close
Me.txtMyDESCRIP.Requery
End Sub
The sub that the button calls uses the VBA Replace function, and it works well in most instances. The problem arises when I want to includes slashes or other special characters in my replace text. For example, one of my "myFind" values is "emailtofrom", and the corresponding "myReplace" value is "e-mail correspondence to/from". But, when I run the sub, the "emailtofrom" text is replaced with "e-mail correspondence tofrom", WITHOUT the slash.
I understand that the VBA Replace function will remove slashes and other special characters. Is there anything that I can do preserve the slashes when the Replace function runs? Escaping the slashes somehow in my FINDREPLACE table (I'm the only one using this database so I can do that if necessary)? Using code other than VBA Replace?
"I understand that the VBA Replace function will remove slashes"
That is not what I see with VBA Replace(). Here are examples from the Immediate window using forward and back slashes.
? Replace("foo emailtofrom bar", "emailtofrom", _
"e-mail correspondence to/from")
foo e-mail correspondence to/from bar
? Replace("foo emailtofrom bar", "emailtofrom", _
"e-mail correspondence to\from")
foo e-mail correspondence to\from bar
I think something else is going on, but I can't spot the issue in your code sample. Set a break point, run your code, and then step through it one line at a time with the F8 key and examine the text values at each step.

(Error 3071) Access

I'm trying to search for the Course ID by checking the the faculty ID and Course ID in my Course table. Its a simple query but when launched I receive a
(This expression is typed incorrectly, or it is too complex to be evaluated. For example, a numeric expression may contain too many complicated elements. Try simplifying the expression by assigning parts of the expression to variables. (Error 3071)
The VB script I'm using looks like this.
Private Sub Course_ID_DblClick(Cancel As Integer)
Dim x As Integer
Dim y As String
Dim z As Integer
x = Me.Faculty_ID.Value
Me.Course_ID.RowSource = "SELECT Course.[Course ID] FROM Course WHERE Course.[Course Name]=['Course']AND Course.[Faculty ID]='" & x & "'"
End Sub
I think you are getting this message because you are using wrong delimiters and confusing terminology in object names (where field content 'course' and table name [course] are the same.
I guess your datasource is an ms-access database. You should then use the " (double quote) as value separator (you'll have to double the sign inside the expression) for text, and nothing for numbers. your select instruction could then look like:
"SELECT Course.[Course ID] FROM Course WHERE Course.[CourseName]=[""Course""] AND Course[FacultyID]=" & x
Add a 'debug.print Me.Course_ID.RowSource' to check the final string that should look like:
SELECT Course.[Course ID] FROM Course WHERE Course.[CourseName]=["Course"] AND Course[FacultyID]= 3
There should be no brackets around your string literals:
Me.Course_ID.RowSource = "SELECT Course.[Course ID] FROM Course WHERE Course.[Course Name]='xyz' AND ...
Also, you can use either single or double quotes as string delimiters in an access query.