Excel vba error with dates on sheet and textbox - vba

I live in Australia and we use the d/mm/yyyy date format. I am trying to make a userform with VBA in excel that will read the a cell (A1), display it in a textbox. The user can then enter a date in another textbox and set that date back to cell(A1). The problem I have is that when I am reading the date from Cell(A1) is changes from d/mm/yyyy to m/dd/yyyy in the textbox. I have used the
" = Format(DateCurrent_Tbx.Value, "d/mmm/yy")" but it makes no difference. I am using Excel 2010 32bit, on a Windows 7 SP1 64Bit Computer, and have the computer set to English (Australian) time format. Cell(A1) is set to custom formatting "d/mm/yyyy"
As the VBA code for the file I'm using is very long, I've replicated the error in a smaller userform (attached excel file).
Sample of Excel file with Error
Private Sub LockInput_Cmd_Click()
SetDate_Cmd.Caption = InputDate_Tbx.Value
SetDate_Cmd.Caption = Format(SetDate_Cmd.Caption, "d/mmm/yy")
End Sub
Private Sub SetDate_Cmd_Click()
ThisWorkbook.Sheets(1).Range("A1").Value = SetDate_Cmd.Caption
DateCurrent_Tbx.Value = ThisWorkbook.Sheets(1).Range("A1").Value
DateCurrent_Tbx.Value = Format(DateCurrent_Tbx.Value, "d/mmm/yy")
End Sub
Private Sub UserForm_Initialize()
DateCurrent_Tbx.Value = ThisWorkbook.Sheets(1).Range("A1").Value
DateCurrent_Tbx.Value = Format(DateCurrent_Tbx.Value, "d/mmm/yy")
End Sub
I have been racking my head and searching the web for days to no avail. I hope I have explained things clearly enough.

There is some confusion with strings that look like dates and actual dates and the EN-US-centric VBA default MDY locale is doing nothing to alleviate that.
First off, the date in A1 is a number between 0 and 42,225 (Aug 9, 2015 is 42,225); nothing more or less. You can dress it up as d/mm/yyyy regardless of what locale your computer system is running under. Mine runs under EN-US and I have no trouble displaying a date as 9/08/2015 if I use a number format mask of d/mm/yyyy. To get the displayed date from A1 back into the textbox AS A STRING use the Range.Text property. This is the displayed text that is in a cell. It is read-only so you cannot stuff a "9/08/2015" string back into it.
Private Sub UserForm_Initialize()
DateCurrent_Tbx.Value = ThisWorkbook.Sheets(1).Range("A1").TEXT
End Sub
As far as user input of a 'string-that-looks-like-a-date' is concerned, you may have to trust your users a bit to do the right thing. If they can be relied upon to input in a DMY format, you can split the pieces and put them together properly using the DateSerial function.
Private Sub LockInput_Cmd_Click()
dim dt as date, vDT as variant
vDT = split(InputDate_Tbx.Value, chr(47)) 'split the string-date on the forward slash
dt = DateSerial(vDT(2), vDT(1), vDT(0)) 'create a real date
SetDate_Cmd.Caption = Format(dt, "d/mmm/yy") 'use any format mask you want to create another string-date
End Sub
Private Sub SetDate_Cmd_Click()
dim dt as date, vDT as variant
vDT = split(SetDate_Cmd.Caption, chr(47)) 'split the string-date on the forward slash
dt = DateSerial(vDT(2), vDT(1), vDT(0)) 'create a real date
ThisWorkbook.Sheets(1).Range("A1").Value = dt 'put the actual date back into A1
End Sub
As far as the CDate function is concerned, I would avoid its use. If you stuff "19/08/2015" into CDate (try it in the VBE's Immediate window as ?CDate("19/08/2015")) it will correctly interpret it as 19-Aug-2015 but that is only because IT HAS NO OTHER CHOICE! If you try the same with ?CDate("9/08/2015") you end up with 08-Sep-2015 because whenever there is a choice, CDate opts for the EN-US locale of MDY. Best not to rely on this one. I believe the DateValue function is at least as unreliable.

Related

Detect if combobox has been modified in the last Xs [duplicate]

Good morning!
I have a "fancy" search function in Microsoft Access where the list of possible options shrinks as you type in the search field. Unfortunately the computer and server can't keep up with these rapid requeries of the data.
Currently the command to requery with the field in the 'onchange' function of the search box. I'd like to add a delay so it only runs the requery when the search box has not changed for a second. Thus if someone types in a 8 letter word, it isn't running 8 requeries.
The current idea I have for it, which I know there must be something better, is..
"On change, set search box value to X and wait 1 second. After 1 second, if X = search box value, run the requery. An issue is that it would be rapidly rewriting the X value and have a 'wait' command floating for each letter.
Hopefully there's a way to write an event trigger of "When field X has changed, but not changed for the past second."
Thank you!
As requested, here is my current code
'Create a string (text) variable
Dim vSearchString As String
'Populate the string variable with the text entered in the Text Box SearchFor
vSearchString = SearchFor.Text
'Pass the value contained in the string variable to the hidden text box SrchText,
'that is used as the sear4ch criteria for the Query QRY_SearchAll
SrchText = vSearchString
'Requery the List Box to show the latest results for the text entered in Text Box SearchFor
Me.SearchResults.Requery
Me.SearchResults2.Requery
'Tests for a trailing space and exits the sub routine at this point
'so as to preserve the trailing space, which would be lost if focus was shifted from Text Box SearchFor
If Len(Me.SrchText) <> 0 And InStr(Len(SrchText), SrchText, " ", vbTextCompare) Then
'Set the focus on the first item in the list box
Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor,
'and restores trailing space lost when focus is shifted to the list box
Me.SearchFor = vSearchString
Me.SearchFor.SetFocus
Me.SearchFor.SelStart = Me.SearchFor.SelLength
Exit Sub
End If
'Set the focus on the first item in the list box
' Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor
Me.SearchFor.SetFocus
If Not IsNull(Len(Me.SearchFor)) Then
Me.SearchFor.SelStart = Len(Me.SearchFor)
End If
Obviously this is not MY code, it's from somewhere on the interweb. It works fantastic for databases stored locally, but everything is moving to our Sharepoint server which is running on a 386 in a moldy basement powered by a narcoleptic gerbil.
You can simply use the Timer of the current form. No need for a separate form or anything.
Private Sub DoSearch()
' Your current code
' but you should look into removing as many "Requery" from there as possible!
End Sub
Private Sub SearchFor_Change()
' Wait for x Milliseconds until the search is started.
' Each new change restarts the timer interval.
' Use 1000 (1 s) for slow typists or a really slow server
' 200 ms feels right for a normal typist
Me.TimerInterval = 200
End Sub
Private Sub Form_Timer()
' Disable timer (will be enabled by the next SearchFor_Change)
Me.TimerInterval = 0
' Now run the search
DoSearch
End Sub
Note: you may need to move some of the cursor-handling code from DoSearch() to SearchFor_Change(), specifically:
Me.SearchFor.SelStart = Len(Me.SearchFor)
Assign a shortcut key like (Ctrl+ J) to the logic in on change event and call it on demand once you have finished typing search keyword.
Remove on change event.
Create other procedure which has the logic of on change event and assign a shortcut key
Press shortcut to get search suggestion
Other approach
Add below validation to Change event which will check for length of string and will trigger only if length of string is >=8
Private Sub txtSearch_Change()
If Len(Nz(txtSearch.Text, 0)) >= 8 Then
End If
End Sub
I'm going a little outside my comfort area, since I hardly use MS Access forms, but why are you bothering the Server/Database so much? In my experience, each query costs the same amount of time, whether it returns 1 record or 100,000 records.
So even before the user types anything, why don't you just do a single query to return a sorted list. After that, it takes almost no time to use VBA to process the results and find everything in the list that starts with whatever the user types in (it's sorted after all).
Except for the initial load, users who are local to the database or on the other side of the world will experience the same snappy response from your interface.
----------
Like I said, I haven't messed with Access Forms a lot, so this is more of a strict VBA solution. Maybe there is a better way to do it without going outside the Access Forms box that someone could enlighten us with.
You should basically just call LoadItemList when you load the form, or whenever you need to.
Public dbConn As ADODB.Connection
Private ItemList As Variant
Private RecordCount As Long
Sub LoadItemList()
Dim SQL As String
Dim RS As New ADODB.Recordset
SQL = "SELECT T.Name FROM Table T"
Set RS = dbConn.Execute(SQL)
If Not RS.EOF Then
ItemList = RS.GetRows
RecordCount = UBound(ItemList, 2) - LBound(ItemList, 2) + 1
End If
End Sub
Then replace DoCmd.Requery with AddItemtoCombobox SearchResults, SearchFor.Text
Sub AddItemtoCombobox(Control As ComboBox, Filter As String)
Dim Index As Long
Control.Clear
If Not IsEmpty(ItemList) Then
For Index = 0 To RecordCount - 1
If ItemList(Index) Like Filter Then Control.AddItem ItemList(Index)
Next
End If
End Sub
Again, maybe there is a better way that is built into Access...
The technical term that you're looking for is debounce.
What you can do is on your on change event, keep track of the current search string
in terms of pseudocode.
sub onChange()
Form.timerinterval = 0
setSearchString
form.timerinterval = delay
So in terms of the explanation, if your on change is called, disable the timer. Update your search string, then reset the timer to fire after a certain amount of time. The form should be a hidden form that contains the code that you want to execute

VB.Net and handling dates within Grid Controls - better way?

While I have my app running, I question the methodology, and wondering if there’s a “better way”…
Overall design is to allow editing 200-300 records from a gridview (phase1) using VB.Net. The database itself is on SQL Server. There are a number of columns a user will enter into an “application”, and there are several columns that will be edited/maintained by “office users”, if you will. There are several dates involved in this ongoing maintenance, and that’s where the first of my questions revolves.
I have found “solutions” on the internet that got the code working, but am questioning them…
Problem #1 I ran into – dates are NULL in the database, and in trying to read them in using a SqlDataReader led to errors (cannot assign NULL to a Date object). Ok, that led into using a ternary operator to use “IsDBNull”, and either assign the value read from the DB, or to assign DateTime.MinValue. Problem “solved”…
Problem #2 – using the above method now shows dates that are the minimum VB date value – showing actual dates in the fields the user is to edit – definitely not “user friendly”, nor what I want. The only solution to this issue was:
Convert dates from Date or DateTime objects into String objects. This would then allow me to be able to assign an empty string to the gridview in the case where the date was originally NULL in the DB, which had to be transformed into DateTime.MinValue (which could be tested), and then another ternary operator to assign either “ToString” conversion, or an empty string to the gridview field.
Ok – editing is now accomplished. I added some “ScriptManager.RegisterStartupScript” commands to allow testing the validity of the dates the user enters – all is well.
Problem #3 (or 4) – I now need to update the database with the data the user entered – PRESERVING THE EMPTY DATE STRINGS – and update the database (using parameters…) with NULLs back in those date columns. However, again – the date is a string, and is empty, so I had to assign to a “MinValue”, first, then another ternary operator to test each date against “MinValue”, and either assign the date, or a DBNull.Value…
Yes, I guess I could have come up with a number of different update strings (including dates in some, excluding in others), depending on whether or not a string/date was empty or not... But that will only lead to future bugs, so, I guess I’ll be keeping a series of ternary operators.
So, the code for beginning the edit process looks something like:
While sdr.Read
Dim _date1 As Date = If(IsDBNull(sdr("date1")), DateTime.MinValue, sdr("date1"))
.
.
.
‘ Now add them to a List of my Class:
appsList.Add(New AppClass(… _
If(_date1 = DateTime.MinValue, " ", _date1.ToString("MM/dd/yyyy")), _
… )
Now to get the data back from the gridview to update the database:
Dim _date1 As Date
' see if we can convert the various dates...
Try
' see if empty…
If ((CType((row.Cells(19).Controls(0)), TextBox)).Text).Length < 2 Then
_date1 = DateTime.MinValue
Else
_date1 = DateTime.Parse((CType((row.Cells(19).Controls(0)), TextBox)).Text)
End If
Catch ex As Exception
ErrFlag = True
ScriptManager.RegisterStartupScript(Me, Page.GetType, "Script", "alert(‘Date1 Date is not valid - enter as MM/DD/YYYY');", True)
End Try
.
.
.
Dim sql As String = "UPDATE [foo_bar].[dbo].[bar_foo] set date1=#Date1, …….)
cmd.Parameters.AddWithValue("#Date1", If(_date1 = DateTime.MinValue, DBNull.Value, _date1))
Honestly, all this conversion back and forth seems like it’s going to lead to bugs or errors at some point.
So – is this the “best” method for handling this? There isn’t a cleaner way?
If your using winforms then you can handle the Format and parse of the databindings. I haven't tried it on a Gridviewtextbox but worst case you can use a custom cell template and a textbox with format and parse handlers. The code would be something like this:
mybinding = New Binding("text", DataSet1, "table1.datefield")
Me.DataGridTextBoxColumn1.TextBox.DataBindings.Add(mybinding)
AddHandler mybinding.Parse, AddressOf StringToDateTime
AddHandler mybinding.Format, AddressOf formatdate
Private Sub StringToDateTime(ByVal sender As Object, ByVal cevent As ConvertEventArgs)
If cevent.Value.GetType().Equals(GetType(String)) And _
cevent.DesiredType Is GetType(DateTime) Then
If cevent.Value <> "" Then
' Make sure matches format in format funtion
cevent.Value = DateTime.Parse(String.Format(cevent.Value, "MMM d, yy"))
Else
cevent.Value = DBNull.Value
End If
'cevent.Value = DateTime.Parse(String.Format(cevent.Value, "MMM d yyyy")
End If
End Sub
Public Sub formatdate(ByVal sender As Object, ByVal e As ConvertEventArgs)
If e.Value.GetType().Equals(GetType(DateTime)) And e.DesiredType Is GetType(String) Then
' Hard-coded or user-specified
' Make sure matches format in parse funtion
e.Value = Format(e.Value, "d")
End If
End Sub

Start with empty dtPicker Value, then change format after pressing sumbit button

I've really struggled with this one, I've browsed through Google but not found a working solution for me. This is the case (using US Office 2016 and added the mscomct2.ocx);
Loading the Excel also loads UserForm1.
Within this, I have a 'DateFrom' and 'DateTo' using the DateTimePicker.
At startup I would like both to show empty (using checkbox is not an option). After choosing the appropriate 'DateFrom' and 'DateTo' the presented format in the UserForm is .dtpLongDate (set from the dtPicker properties box).
When pressing the submit button, I need the DateTimePicker format to change from .dtpLongDate to Custom format "yyyy-MM-dd" so I can put the DateFrom.Value into a string which needs this specific format.
I've tried everything I've found, but as I'm quite novice on VB, nothing I've tried seem to work.
This is the code I have in the start of the command button:
Private Sub CommandButton1_Click()
Dim i As Integer
Dim TerminalIDRange As Range
Dim ii As Long
Set FormlerSheet = Worksheets("Formler")
Set DataSheet = Worksheets("Data")
Set TerminalIDRange = DataSheet.Range("M2:M2000")
DateFrom.Format = DateTimePickerFormat.Custom
DateTo.Format = DateTimePickerFormat.Custom
DateFrom.CustomFormat = "yyyy-MM-dd"
DateTo.CustomFormat = "yyyy-MM-dd"
MsgBox DateFrom.Value
Still the DateFrom.Value show me date as dd.mm.yyyy for some reason.
Also; to set code for the DateFrom and DateTo to be blank after loading the UserForm, where do I place this code??
Hope somebody can help me on this...
When I understand you right, you just need the date in the form "yyyy-MM-dd".
Then you could also just read DateFrom.Value and convert it with Format$, like this:
Dim s as string
s = Format$(DateFrom.Value, "yyyy-MM-dd")
MsgBox s

Access 2013 - Set a field value based on value of another field

I have a combo box (Status) which includes the following:
Shortage
Allocated
Actioned
Acknowledged
Complete
I also have 5 other date fields which are as follows:
Shortage_date
Allocated_date
Actioned_date
Acknowledged_date
Complete_date
However I want this status to be populated automatically based on what data has been entered in my previous fields.
For example, once shortage_date has been populated with a valid date (00/00/0000) I want the "status" to change to "shortage".
Once allocated_date has been populated with a valid date (00/00/0000) I want the "status" to change to "allocated".
I saw this bit of code online but I'm totally confused:
Private Sub Textbox1_AfterUpdate()
If Textbox1.Value = "1" Then
Textbox2.Value = "10"
End If
End Sub
I believe mine should look something like this but I dont know what I need to make sure it validates the date.
Private Sub shortage_date_AfterUpdate()
If shortage_date.Value = "(I want to valididate the date here)" Then
Status.Value = "Status"
End If
End Sub
Hope I make sense!
Firstly, I would set up an Input Mask on the field itself. You can do that by putting the form into design view, then select the field, then go the Property Sheet which isAlt+Enter if it isn't open, then select the Data tab and set up a Input Mask. That will handle your validation part so you don't have to in code.
Then you should be able to just use the code:
Private Sub shortage_date_AfterUpdate()
If Nz(shortage_date.Value, "") <> "" Then
Status.Value = "Status"
End If
End Sub
The If statement is just to make sure that it doesn't reset the value back to the original every time the date is changed. Also here is link where you can read about Input Masks: https://msdn.microsoft.com/en-us/library/office/ff821336.aspx
Update: Changed to Input Mask instead of Validation Rule

Microsoft Access VBA pop up alert date approaching

I am trying to write some VBA in Microsof Access (if VBA is the way to go?). What I need is a pop up message alerting someone that a deployment is happening within the next week.
My table is called Tasks_List and there is a field called Deployment_Date.
What I think I need is to put together an OnLoad for the initial form. It would check today's date and check through Deployoment_Date and show a pop up if any deployments are happening within the next week. The pop up should show what deployments are happening e.g. Initiating_System, Deployment_Date and Description.
Thank you in advance, I've hit a brick wall on this. I'll post what I've tried but I have no VBA knowledge and it is pretty bad.
What I tried:
Private Sub Report_Open(Cancel As Integer)
Dim varX As Variant
varX = DLookup(Tasks_List.[Deployment_Date]< Now - 20)
If varX > 0 Then GoTo line2
line1: msgbox "Deployment approacing for: "
line2:
End Sub
EDIT: After help below I have created a query and form for this. Using Dcount:
Private Sub Detail_OnLoad()
Deploy = DCount("*", "Tasks_List_Popup_Query")
If Deploy <> 0 Then
DoCmd.OpenForm "Tasks_List_Popup_Query_Form"
DoCmd.GoToRecord , , acNewRec
End If
End Sub
You should not need any VBA. Create a query that selects the relevant records and create a form based on the query. You can use DCount to ensure that there are records before you launch the form, which would take a little VBA.
SELECT * FROM Tasks_List WHERE [Deployment_Date]< (Date - 20)
For the DCount:
Deploy = DCount("*","TheQuery")