Get monday of current week based on userform input - vba

I am very new to VBA, so please bear with me. I have a userform that is going to eventually create a 2 or 6 week schedule look ahead. The userform has a textbox which automatically populates the Monday of the current week as the lookahead start date. If the user inputs a date, it will use that date as the lookahead start date instead.
The part I can't seem to figure out, is the formula so that when the user inputs a date, it calculates the Monday of that week.
The code:
Private Sub UserForm_Initialize()
'Inserts date of Monday this week into "Start Date" field in UserForm
LookAheadDate1.Value = Date - Weekday(Date) + 2
End Sub
Private Sub Generate_Click()
Dim StartDate As Date
Dim EndDate As Date
Dim ws As Worksheet
Dim addme As Long
Set ws = Worksheets("Projects")
addme = ws.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
' Clears Look Ahead sheet - Row 5 and below
With Sheets("Look Ahead")
.Rows(5 & ":" & .Rows.Count).Delete
End With
'Force user to select either option button
If ((Me.OptionButton1.Value = 0) * (Me.OptionButton2.Value = 0)) Then
MsgBox "Please select 2 or 6 Week Look Ahead"
End If
'Force user to select either option button
If ((Me.OptionButton1.Value)) Then
ThisWorkbook.Worksheets("Look Ahead").Range("E6").Value = "2 Week Look Ahead"
End If
If ((Me.OptionButton2.Value)) Then
ThisWorkbook.Worksheets("Look Ahead").Range("E6").Value = "6 Week Look Ahead"
End If
' Set StartDate Variable - If Start Date field value is blank, use this weeks start date, otherwise use start date field value
If IsNull(Me.LookAheadDate1.Value) Or Me.LookAheadDate1.Value = "" Then
StartDate = Date
Else
StartDate = LookAheadDate1.Value
End If
' Option buttons / Code to create end date for 2 or 6 week look ahead
Dim res As Date
If OptionButton1.Value Then
EndDate = StartDate - Weekday(Date) + 16
ElseIf OptionButton2.Value Then
EndDate = StartDate - Weekday(Date) + 44
End If
'Write Look Ahead date range in cell "E7"
ThisWorkbook.Worksheets("Look Ahead").Range("E7").Value = StartDate - Weekday(Date) + 2 & " to " & EndDate
'Clear all fields in UserForm
Dim oneControl As Object
For Each oneControl In ProjectData.Controls
Select Case TypeName(oneControl)
Case "TextBox"
oneControl.Text = vbNullString
Case "CheckBox"
oneControl.Value = False
End Select
Next oneControl
'Close UserForm.
Unload Me
End Sub
Private Sub ToggleButton1_Click()
End Sub
Private Sub UserForm_Click()
End Sub
Private Sub LookAheadDate1_Change()
End Sub
Private Sub Cancel_Click()
'Close UserForm if "Cancel" Button is pressed
Unload Me
End Sub
'Checks for entry of valid date
Private Sub LookAheadDate1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If LookAheadDate1 = vbNullString Then Exit Sub
If IsDate(LookAheadDate1) Then
LookAheadDate1 = Format(LookAheadDate1, "Short Date")
Else
MsgBox "Please Enter Valid Date"
Cancel = True
End If
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'If the user clicks the "X" to close the UserForm, then cancel
If CloseMode = 0 Then Cancel_Click
End Sub

If I understand your question about the date correctly, you want to edit this block of your code:
If IsNull(Me.LookAheadDate1.Value) Or Me.LookAheadDate1.Value = "" Then
StartDate = Date ' <--- this is where you want Monday, right?
Else
StartDate = LookAheadDate1.Value
End If
If so, replace the marked line with
StartDate = Date - Application.WorksheetFunction.Weekday(Date,3)
to get the date of Monday of this week as the StartDate.
Date returns the current date (similar to Now, but without the time part)
The use of Weekday (=WEEKDAY()) to get Monday is from this answer by Paul
Edit
In the Else branch:
Dim enteredDate as Date
enteredDate = CDate(LookAheadDate1.Value)
StartDate = enteredDate - Application.WorksheetFunction.Weekday(enteredDate,3)
CDate converts from text to date according to the current locale. You may need to parse the date text manually if you need a more sophisticated conversion.
I believe LookAheadDate1 is a text box because I see you are giving it a value using Format(..., "Short Date"). If it is a date/time picker, you may not need the CDate call.

I got it working. I added a calendar date picker to input the date into the textbox. Thanks for the help!

Related

when I click the user form in vba It always show the path/file error and I can not see the object of the userform where I am doing wrong?

Private cancel As Boolean
Public Function ShowDatesDialog(startDate As Date, endDate As Date, timeInterval As String) As Boolean
Call Initialize
Me.Show
If Not cancel Then
startDate = calStartDate.Value
endDate = calEndDate.Value
Select Case True
Case optMonth: timeInterval = "m"
Case optWeek: timeInterval = "w"
Case optDay: timeInterval = "d"
End Select
End If
ShowDatesDialog = Not cancel
Unload Me
End Function
Private Sub Initialize()
calStartDate.Value = DateSerial(Year(Date) - 2, Month(Date), Day(Date))
calEndDate.Value = Date
optMonth.Value = True
End Sub
Private Function Valid() As Boolean
Dim startDate As Date, endDate As Date
Valid = True
startDate = calStartDate.Value
endDate = calEndDate.Value
If startDate >= endDate Then
Valid = False
MsgBox "The starting date should be before the ending date.", _
vbInformation, "Invalid dates"
calStartDate.SetFocus
Exit Function
ElseIf startDate < EARLIST_DATE Then
Valid = False
MsgBox " The starting date shouldn't be before 1990.", _
vbInformation, "Start date too early"
calStartDate.SetFocus
Exit Function
ElseIf endDate > Date Then
Valid = False
masgbox " The ending date shouldn't be after today's date.", _
vbInformation, "end date too late"
calEndDate.SetFocus
Exit Function
End If
End Function
Private Sub btnOK_Click()
If Valid Then Me.Hide
cancel = False
End Sub
Private Sub btnCancel_Click()
Me.Hide
cancel = True
End Sub
Private Sub UserForm_QueryClose(cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then btnCancel_Click
End Sub
Lots.
Initialize and btnCancel_Click are event handlers: they should never be called explicitly. Their purpose is to handle events.
You're on the right path though - I see you're trying really hard to avoid using the default instance... yet I suspect your calling code looks like this:
MyAwesomeForm.ShowDatesDialog startDate, endDate, timeInterval
Right?
Well that ShowDatesDialog is running off the form's default instance, which means this:
Me.Show
Displays the default instance.
A form shouldn't be responsible for destroying itself (because it's not creating itself) - you did good with Me.Hide, but Unload Me then defeats all of it, because it makes the object self-destruct, which means the callers can never get a grasp on the object: they display the form, and then it disappears and the object is gone before they can do anything with it.
A form shouldn't be responsible for showing itself either: that's the caller's job too.
Expose the encapsulated cancel field as a read-only property, and rename it to cancelled, so that it doesn't clash with the QuerClose handler's parameter:
Private cancelled As Boolean
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Expose your model as properties too:
Public Property Get StartingDate() As Date
StartingDate = calStartDate.Value
End Property
Public Property Get EndingDate() As Date
EndingDate = calEndDate.Value
End Property
Public Property Get TimeInterval() As String
Select Case True
Case optMonth.Value
TimeInterval = "m"
Case optWeek.Value
TimeInterval = "w"
Case optDay.Value
TimeInterval = "d"
Case Else
TimeInterval = vbNullString
End Select
End Property
And now your callers can do this - basically your ShowDatesDialog function becomes the calling code:
With New MyAwesomeForm
.Show
If Not .IsCancelled Then
Dim startDate As Date
startDate = .StartingDate
Dim endDate As Date
endDate = .EndingDate
Dim interval As String
interval = .TimeInterval
End If
End With
The Valid function should be a verb, e.g. Validate, or start with a verb, e.g. IsValid; because the function has side-effects, I'd be more inclined to name it Validate than IsValid, but YMMV. Its indentation seriously needs some love, though:
If startDate >= endDate Then
Valid = False
MsgBox "The starting date should be before the ending date.", _
vbInformation, "Invalid dates"
calStartDate.SetFocus
Exit Function
ElseIf startDate < EARLIST_DATE Then
Valid = False
MsgBox " The starting date shouldn't be before 1990.", _
vbInformation, "Start date too early"
calStartDate.SetFocus
Exit Function
ElseIf endDate > Date Then
Valid = False
masgbox " The ending date shouldn't be after today's date.", _
vbInformation, "end date too late"
calEndDate.SetFocus
Exit Function
End If
Note, if VBA lets you run this code without complaining at compile-time, then you need to specify Option Explicit at the top of the module (should be at the top of every module), because this can't possibly compile:
masgbox " The ending date shouldn't be after today's date.", _
vbInformation, "end date too late"
Unless you have a masgbox function defined somewhere, of course. Consider reducing redundancies by inverting the Boolean logic (a Boolean variable is False until it's assigned to True), extracting a message variable and standardizing the title, and only calling a MsgBox in one place.
To be clear, here's your form's code-behind as I see it:
Option Explicit
Private Const EARLIEST_DATE As Date = #1990-01-01#
Private cancelled As Boolean
Public Property Get IsCancelled() As Boolean
IsCancelled = cancel
End Property
Public Property Get StartingDate() As Date
StartingDate = calStartDate.Value
End Property
Public Property Get EndingDate() As Date
EndingDate = calEndDate.Value
End Property
Public Property Get TimeInterval() As String
Select Case True
Case optMonth.Value
TimeInterval = "m"
Case optWeek.Value
TimeInterval = "w"
Case optDay.Value
TimeInterval = "d"
Case Else
TimeInterval = vbNullString
End Select
End Property
Private Sub Initialize()
calStartDate.Value = DateSerial(Year(Date) - 2, Month(Date), Day(Date))
calEndDate.Value = Date
optMonth.Value = True
End Sub
Private Sub UserForm_QueryClose(cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
cancelled = True
Me.Hide
End If
End Sub
Private Sub btnCancel_Click()
cancelled = True
Me.Hide
End Sub
Private Sub btnOk_Click()
If Validate Then Me.Hide
End Sub
Private Function Validate() As Boolean
Dim result As Boolean
Dim message As String
If StartingDate >= EndingDate Then
message = "Invalid selection. The starting date must be before the ending date.",
calStartDate.SetFocus
ElseIf StartingDate < EARLIEST_DATE Then
message = "Start date is too early. Cannot be before 1990."
calStartDate.SetFocus
ElseIf EndingDate > Date Then
message = "End date is too late. The ending date cannot be after today's date."
calEndDate.SetFocus
Else
result = True
End If
If Not result Then MsgBox message, "Validation Failed", vbExclamation
Validate = result
End Function
And the calling code:
Public Sub DoSomething()
With New MyAwesomeForm
.Show
If Not .IsCancelled Then
Dim startDate As Date
startDate = .StartingDate
Dim endDate As Date
endDate = .EndingDate
Dim interval As String
interval = .TimeInterval
'...do stuff...
End If
End With
End Sub
Consider handling the calStartDate and calEndDate controls' Changed event to perform earlier validation, and only enable the OK button when the form is valid; that way you can replace the annoying message boxes by a neat little validation/notification label.
That said I've no idea what your path/file error comes from, nothing in your post does anything with any files. But this should help you get your forms straight at least.

VBA Dateadd Format - Need In Total Minutes

I have a userform in Microsoft Excel that I want to use as a stopwatch. However the format of "hh:mm" does not allow it to go above 23:59 as it goes back to 00:00
Private Sub SpinButton2_SpinUp()
If InsertEvent.TextBox1 = vbNullString Then
InsertEvent.TextBox1 = "00:00"
Else
InsertEvent.TextBox1.Value = Format(DateAdd("n", 1, InsertEvent.TextBox1.Value), "hh:mm")
'InsertEvent.TextBox1.Value = TimeValue("mm:ss")
'InsertEvent.TextBox1.Value = Format(InsertEvent.TextBox1.Value, "hh:mm")
End If
End Sub
Is there anyway to format this so that it can work as a clock of total minutes? Ideally I need it to go to about 125 minutes or so (125:00) but it doesn't matter if it is unlimited.
You can't use the built in Date/Time functions for this as you want a representation that is not a Date/Time.
Assuming you want to read the spinner value into the textbox:
Private Sub SpinButton2_SpinUp()
Dim minutes As Integer: minutes = Val(InsertEvent.SpinButton2.Value)
Dim hh As Integer: hh = minutes \ 60
Dim mm As Integer: mm = minutes - (hh * 60)
InsertEvent.TextBox1.Text = Format$(hh, "00") & ":" & Format$(mm, "00")
End Sub
To use a manually entered value from the textbox as the starting up/down point you need to re-parse "hh:mm" back to minutes, for example in the textbox Exit event:
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If (IsNumeric(TextBox1.Text)) Then
'// entering a number only assumes its minutes
SpinButton2.Value = TextBox1.Text
Exit Sub
End If
Dim hhmm() As String: hhmm = Split(TextBox1.Text, ":")
If (UBound(hhmm) = 1) Then
If (IsNumeric(hhmm(0)) And IsNumeric(hhmm(1))) Then
SpinButton2.Value = (hhmm(0) * 60) + hhmm(1)
Exit Sub
End If
End If
SpinButton2.Value = 0
End Sub
(Should add error checking for overflow/exceeding the spinners .Max property)

VB form load event based on date

I'm attempting to display a button on a secondary form in vb based on what the date is (Trying to get a reset button to show only on the last day of the year).
I've tried a few different things with the code below...
I originally put it in the Form Load Event of Form 2, no msgbox displayed, button didn't display.
I cut the code out of my project and pasted it into the Form Load Event of a new project to test it on it's own... Msgbox displayed and button displayed!! :)
This got me thinking maybe I had to put the code into the Form Load Event of the Main Form. I pasted it there and made the modifications to point to form2 (Current version of the code)....
Once again , no msgbox, no button
What am I missing?
Private Sub Main_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim date1 As String = String.Format("{0:MM/dd/yyyy}", DateTime.Now)
Dim todaysdate As String = Format(Now, "Long Date")
Dim dayofweek = todaysdate.Substring(0, todaysdate.IndexOf(","))
Dim year As String = Now.Year
Dim datecheck As String = "12/29/"
Dim datecheck1 As String = "12/30/"
Dim datecheck2 As String = "12/31/"
' Add Current Year to Date to Check variables
datecheck = datecheck + year
datecheck1 = datecheck1 + year
datecheck2 = datecheck2 + year
Dim expenddt As Date = Date.ParseExact(date1, date1, System.Globalization.DateTimeFormatInfo.InvariantInfo)
Dim expenddt1 As Date = Date.ParseExact(datecheck, datecheck,
System.Globalization.DateTimeFormatInfo.InvariantInfo)
Dim expenddt2 As Date = Date.ParseExact(datecheck1, datecheck1,
System.Globalization.DateTimeFormatInfo.InvariantInfo)
Dim expenddt3 As Date = Date.ParseExact(datecheck2, datecheck2,
System.Globalization.DateTimeFormatInfo.InvariantInfo)
' If DEC 29 or 30 Falls Fiday, Display Reset Button
If date1 = datecheck And dayofweek = "Friday" Then
' MsgBox Used Only For Testing
MsgBox("THIS ONE WORKED!")
Form2.Reset.Visible = True
End If
If date1 = datecheck1 And dayofweek = "Friday" Then
' MsgBox Used Only For Testing
MsgBox("THIS ONE WORKED!!")
Form2.Reset.Visible = True
End If
' If it's Dec 31 and it's Not Saturday or Sunday, Display Reset Button
If date1 = datecheck2 and dayofweek <> "Saturday" and dayofweek <> "Sunday" Then
' MsgBox Used Only For Testing
MsgBox("THIS ONE WORKED!!!")
Form2.Reset.Visible = True
End If
End Sub
First things first, have a read through the documentation for the DateTime structure. You can do everything that you're trying to do without using Strings. The DateTime structure has a DayOfWeek property, and Month and Day properties that will help you here.
Secondly, the way you are using the ParseExact method is wrong (not that you should end up using it). The second parameter to the ParseExact method is the format string that you expect the date to be in (something like "MM/dd/yyyy"). Passing in a formatted date will not work, and from my experiments, will simply return the current date without any parsing occurring.
So, with all that in mind (and assuming you want to show the button on the last weekday in the year as your code suggests, and not just the last day in the year as your question stated), try something like this:
Private Sub Main_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Form2.Reset.Visible = ShouldShowResetButton(DateTime.Now)
End Sub
Private Function ShouldShowResetButton(currentDate As DateTime) As Boolean
Return GetLastWeekdayInYear(currentDate.Year) = currentDate.Date
End Function
Private Function GetLastWeekdayInYear(year As Integer) As Date
Dim lastDayInYear As Date
lastDayInYear = New Date(year, 12, 31)
Select Case lastDayInYear.DayOfWeek
Case DayOfWeek.Sunday
Return New Date(year, 12, 29)
Case DayOfWeek.Saturday
Return New Date(year, 12, 30)
Case Else
Return lastDayInYear
End Select
End Function

Monthview Bolding Every Value

I created a Monthview and TimePicker in a form. I want the user to pick the time, and select a month which will bold the value selected each time, then select OK which will insert the value. I have all of this working fine. The issue is that if a user selects a date, then selects another date, or another date, all the dates are getting Bolded. I want the BOLD to only follow each most recent click.. if that makes sense, so that the user knows what value he chose.
Here is my click code:
Private Sub MonthView1_DateClick(ByVal DateClicked As Date)
Dim x As Date
x = MonthView1.value
MonthView1.DayBold(x) = True ' Bold the date
End Sub
What method do I need? Is there some kind of most-recent clicked property?
Try the following code:
Private Sub MonthView1_DateClick(ByVal DateClicked As Date)
Dim x As Date
Dim MaxDate As Date
Dim MinDate As Date
MinDate = DateSerial(Year(DateClicked), Month(DateClicked), 1) 'Get first date of current month based on clicked date
MaxDate = DateSerial(Year(DateClicked), Month(DateClicked) + 1, 0) 'Get last date of current month based on clicked date
x = ActiveCell.Value 'Retreive value of last Bold date
If x >= MinDate And x <= MaxDate Then 'If last Bold date is in the current month then unbold it
MonthView1.DayBold(x) = False
End If
MonthView1.DayBold(DateClicked) = True 'Bold the clicked date
ActiveCell.Value = DateClicked 'Store current date in a sheet
End Sub
The idea is to save the Bold date in a sheet (you may hide it if you wish) and retrieve it when another date is selected. The Bold formatting is removed from the previous date and applied to the current one.
a bit more on bolding in a MonthView
'<code>
Private Sub CommandButton1_Click()
Dim Ss(50) As Boolean
'put in module DaysToBold$ and DatesToBold$ as public variables
DaysToBold = "713" ' sat Sun Tue
' DaysToBold = "" ' for none
DatesToBold = "x,1,2,12,22,30"
' DatesToBold = "x" ' if none
MonthView21_GetDayBold Now, 49, Ss
End Sub
Private Sub MonthView21_GetDayBold(ByVal StartDate As Date, _
ByVal Count As Integer, State() As Boolean)
Dim I As Integer, Mi%, DTB, DI%, DAd%
On Error Resume Next ' seem to be negative indices into State() even if declared ( -10 to 50)
For Mi = 1 To Len(DaysToBold) ' mark of the Days of week to bold
I = Mid(DaysToBold, Mi, 1) ' single digits 1 ..7 excel translate to integer
While I < Count
State(I - MonthView21.StartOfWeek) = True
I = I + 7 ' down the column
Wend
Next Mi
DTB = Split(DatesToBold, ",")
If UBound(DTB) > 0 Then
With MonthView21
For I = 1 To UBound(DTB) ' mark the date numbers to bold
DI = Weekday(DateSerial(Year(.Value), Month(.Value), 1), .StartOfWeek)
If DI = 1 Then DAd = 5 Else DAd = -2 ' 7 days of last month there if Di=1
State(DTB(I) + DI + DAd) = True
Next I
End With
End If
End Sub
'</code>

Validating a date in a textbox

I am trying to valid user input in a textbox which will only takes dates or an empty value (hence the textbox vs a date time picker). Here are the conditions:
Only a date value ("dd-mm-yyyy" or "dd-mm-yy)
Must contain only slashes or numbers
The date has to be on the day it is being typed in
This is what I have so far:
Private Sub tbApp1_TextChanged(sender As System.Object, e As System.EventArgs) Handles tbApp1.TextChanged
If Not Me.tbApp1.Text = String.Empty Then
If Not DateTime.TryParseExact(tbApp1.Text.Trim, formats, New Globalization.CultureInfo("en-US"), Globalization.DateTimeStyles.None, dtTemp) Then
If Not tbApp1.Text.Trim = DateTime.Today.Date Then
ErrorProvider1.SetError(tbApp1, "This is not a valid date; Enter in this format ('M/d/yyyy' or 'M/d/yy')")
End If
Else
ErrorProvider1.Clear()
End If
ElseIf Me.tbApp1.Text.Trim = "" Then
ErrorProvider1.Clear()
End If
End Sub
Masked Textbox use
'Private Sub mtbApp1_TypeValidationCompleted(ByVal sender As Object, ByVal e As TypeValidationEventArgs) Handles mtbApp1.TypeValidationCompleted
If Not Me.mtbApp1.Text = String.Empty Then
If (Not e.IsValidInput) Then
ErrorProvider1.SetError(mtbApp1, "The data you supplied must be a valid date in the format mm/dd/yyyy.")
Else
' Now that the type has passed basic type validation, enforce more specific type rules.
Dim UserDate As DateTime = CDate(e.ReturnValue)
If (UserDate = DateTime.Now) Then
ErrorProvider1.SetError(mtbApp1, "The data you supplied must be today's date")
e.Cancel = True
End If
End If
ErrorProvider1.Clear()
End If
End Sub'
I noticed for a date like 03/18/2014 when it loaded back into the masked textbox it converts to 31/82/014. How could i fix this? The query pulls back the field as
CONVERT(VARCHAR(10),Date,101) AS Date
I set it in vb as :
Dim Approval1 As Date = Nothing
and then
If Not IsDBNull(((WorklistsDS.Tables(0).Rows(0).Item("Approval1")))) Then
Approval1 = ((WorklistsDS.Tables(0).Rows(0).Item("Approval1")))
End If
and then loaded into the masked textbox as:
If Approval1 <> Nothing Then
Me.mtbApp1.Text = Approval1
End If
You could also simplify your validation with
If IsDate(tbApp1.Text) Then
'valid date now just check if date falled within permitted range
Dim CompDate As Date = CDate(tbApp1.Text)
Dim MidDate As Date = *enter your min date here*
Dim MaxDate As Date = *enter your max date here*
If CompDate >= MinDate AndAlso CompDate <= MaxDate Then
'Date is within permitted range
......
Else
'Date outside range
'Display error
End If
Else
'text in tbApp1 is not a date
'Display Error
End If