VBA Code to Alert Against Expired Material Assistance - vba

I am trying to create a popup window as soon as I open my excel file.
The excel workbook that I have has multiple sheets.
One of the sheets is titled "Inventory".
As shown in the image below, there is a column in the Inventory tab that is titled "Days Until Expiration".
I want to have my excel file display a pop up when opening the workbook. This popup will check the "Days Until Expiration" column in the "Inventory" tab and say something like "____ material" (from the 'Type' Column) "has ____" days until expiration.
This will only happen if the "Days Until Expiration" value is in between 0 and 14 days.
If the number is negative in the "Days Until Expiration" column, I want the message pop up to say "___ material has expired".
Shown below is what I have so far. I have created a workbook_open() event and this code is in my "ThisWorkbook" code tab.
I am also getting an error when I run what I have below, specifically saying:
Run-time error '13': Type mismatch
Private Sub Workbook_Open()
Application.WindowState = xlMaximized
Dim wb As Workbook
Dim ws As Worksheet
Dim rngUsed As Range, rngExpirationColumn As Range, rngCell As Range
Dim strExpirationMessage As String
Set wb = ThisWorkbook
Set ws = wb.Sheets("Inventory")
Set rngUsed = ws.UsedRange
Set rngExpirationColumn = Intersect(ws.Columns(4), rngUsed)
For Each rngCell In rngExpirationColumn.Cells
If Date - CDate(rngCell.Value2) >= 14 Then
If Len(strExpirationMessage) = 0 Then
strExpirationMessage = rngCell.Offset(0, -3).Value2 & " material has " & (Date - CDate(rngCell.Value2)) & " days left before expiration"
Else
strExpirationMessage = strExpirationMessage & Chr(13) & rngCell.Offset(0, -3).Value2 & " material has " & (Date - CDate(rngCell.Value2)) & " days left before expiration"
End If
End If
Next
MsgBox strExpirationMessage
End Sub

I'm posting this answer based on your request and with some assumptions, as follows:
You want to check the data for column "Days Until Expiration" (as per your request)
You want to grab the data from column "Type" to add on the popup (as per your request)
You want one message if the Expiration Days is between 0 and 14
You want another message if the Expiration Days is less than 0
The value on column "Days Until Expiration" is actually a number (this is an assumption, since no data was provided)
I'm assuming "Days Until Expiration" is merged into two rows (per screenshot)
I'm assuming your data is directly below the rows from your screenshot, so probably your actual data starts on Row 4
Here is the code (tested based on assumptions above, due to lack of actual data to reproduce the scenario):
Private Sub Workbook_Open()
Application.WindowState = xlMaximized
Dim ws As Worksheet
Dim strExpirationMessage As String
Dim rngExpirationCell As Range, rngTypeCell As Range
Dim lngRow As Long, lngExpirationCol As Long, lngTypeCol As Long
Set ws = ThisWorkbook.Worksheets("Inventory")
Set rngExpirationCell = ws.UsedRange.Find(What:="Days Until Expiration", LookAt:=xlWhole, MatchCase:=False, SearchFormat:=False)
Set rngTypeCell = ws.UsedRange.Find(What:="Type", LookAt:=xlWhole, MatchCase:=False, SearchFormat:=False)
If Not rngExpirationCell Is Nothing And Not rngTypeCell Is Nothing Then
lngExpirationCol = rngExpirationCell.Column
lngTypeCol = rngTypeCell.Column
For lngRow = rngExpirationCell.row + 2 To ws.UsedRange.Rows.Count
With ws.Cells(lngRow, lngExpirationCol)
If 0 <= .Value And .Value <= 14 Then
strExpirationMessage = strExpirationMessage & ws.Cells(lngRow, lngTypeCol).Value & " material has " & .Value & " days left before expiration" & vbCrLf
ElseIf .Value < 0 Then
strExpirationMessage = strExpirationMessage & ws.Cells(lngRow, lngTypeCol).Value & " material has expired" & vbCrLf
End If
End With
Next
strExpirationMessage = Left(strExpirationMessage, Len(strExpirationMessage) - 2) 'to remove trailing vbCrLf
MsgBox strExpirationMessage
End If
End Sub
Important Notes:
I've modified part of your logic, by eliminating some variables and using others instead.
I'm not working with range objects, but rather with specified cells
I'm performing a dynamic search for the desired Columns "Days Until Expiration" and "Type", instead of working with fixed column and with offsets, to allow you to change the columns position in future without changing the code.
I'm assuming "Days Until Expiration" is merged into two rows (per screenshot) and this is why I'm using rngExpirationCell.row + 2 in the For loop. If you have anything different than that, the code might need changes.
I hope this suits your needs. Let me know of any issues or concerns.
If this post answers your question, please Accept it by clicking on the Check mark on the left of it.
Update:
Based on the issues you found, below are two alternate solutions for the For loop that you could use. Everything else should be exactly the same. Just replace the For loop with either of the logic below and you should be good to go
Assuming you want to leave the logic once you found an Empty cell:
For lngRow = rngExpirationCell.row + 2 To ws.UsedRange.Rows.Count
With ws.Cells(lngRow, lngExpirationCol)
If IsEmpty(.Value) Then Exit For 'Will leave the For loop once an Empty cell is found
If 0 <= .Value And .Value <= 14 Then
strExpirationMessage = strExpirationMessage & ws.Cells(lngRow, lngTypeCol).Value & " material has " & .Value & " days left before expiration" & vbCrLf
ElseIf .Value < 0 Then
strExpirationMessage = strExpirationMessage & ws.Cells(lngRow, lngTypeCol).Value & " material has expired" & vbCrLf
End If
End With
Next
Assuming you want to skip the evaluation for the Empty cells, but continue checking the cells until the end of the Used Range:
For lngRow = rngExpirationCell.row + 2 To ws.UsedRange.Rows.Count
With ws.Cells(lngRow, lngExpirationCol)
If Not IsEmpty(.Value) Then 'Will skip empty cells but continuing validation until the end of the Used Range
If 0 <= .Value And .Value <= 14 Then
strExpirationMessage = strExpirationMessage & ws.Cells(lngRow, lngTypeCol).Value & " material has " & .Value & " days left before expiration" & vbCrLf
ElseIf .Value < 0 Then
strExpirationMessage = strExpirationMessage & ws.Cells(lngRow, lngTypeCol).Value & " material has expired" & vbCrLf
End If
End If
End With
Next

Related

Why does Application.Match behave inconsistently when run multiple times on the same data?

The background:
I have a workbook, Outline.xlsm, with a five-level hierarchy. In the first worksheet (WS1), the first three levels are described the first two columns, while the next two levels each have their own set of two columns:
In the second worksheet (WS2), there is no level 3, but everything else is the same. All cells are formatted as text.
I have some code that splits out each first-level section ("General thing") into its own workbook to allow users to make changes to the descriptions (and some other fields off to the right). The code in question then goes out and gets those new descriptions from each file and matches them to the ID number. Here is a sanitized version:
Option Explicit
Sub GatherData()
'Set up for speed
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
'Get files to be processed
Dim DataFolder As String
Dim DataFile As String
DataFolder = "\\SomeNetworkLocation"
DataFile = Dir(DataFolder & "\GeneralThing*.xlsx")
'Define ranges to search
Dim WS1_L1Rng As Range
Dim L2rng As Range
Dim L3rng As Range
Set WS1_L1Rng = Worksheets("WS1").Range("A2", "A" & Range("N2").End(xlDown).Row)
Set L2rng = Worksheets("WS1").Range("C2", "C" & Range("N2").End(xlDown).Row)
Set L3rng = Worksheets("WS1").Range("E2", "E" & Range("N2").End(xlDown).Row)
Dim WS2_L1Rng As Range
Dim WS2_L2Rng As Range
Set WS2_L1Rng = Worksheets("WS2").Range("A2", "A" & Range("K2").End(xlDown).Row)
Set WS2_L2Rng = Worksheets("WS2").Range("C2", "C" & Range("K2").End(xlDown).Row)
Dim MatchPos As Variant
Dim WS1_SearchRng As Range
Dim WS2_SearchRng As Range
Dim Cell As Range
'Find and copy data
Do While DataFile <> ""
Workbooks.Open Filename:=DataFolder & "\" & DataFile
With Workbooks(DataFile).Worksheets("WS1")
Set WS1_SearchRng = .Range("A2:" & "A" & .Range("A" & .Rows.Count).End(xlUp).Row & ",C2:" & "C" & .Range("C" & .Rows.Count).End(xlUp).Row & ",E2:" & "E" & .Range("E" & .Rows.Count).End(xlUp).Row)
End With
For Each Cell In WS1_SearchRng
If IsNumeric(Left(Cell.Value2, 2)) Then
Select Case Cell.Rows.OutlineLevel
Case Is < 4
MatchPos = Application.Match(Cell.Value2, WS1_L1Rng, 0)
Case 4
MatchPos = Application.Match(Cell.Value2, L2rng, 0)
Case 5
MatchPos = Application.Match(Cell.Value2, L3rng, 0)
End Select
If IsError(MatchPos) Then
Debug.Print "WS1 " & Cell.Value2
Else
MatchPos = MatchPos + 1
Workbooks(DataFile).Worksheets("WS1").Range("A" & Cell.Row, "L" & Cell.Row).Copy Destination:=Workbooks("Outline.xlsm").Worksheets("WS1").Range("A" & MatchPos, "L" & MatchPos)
End If
End If
DoEvents
Next Cell
If Workbooks(DataFile).Worksheets.Count > 1 Then
With Workbooks(DataFile).Worksheets("WS2")
Set WS2_SearchRng = .Range("A2:" & "A" & .Range("A" & .Rows.Count).End(xlUp).Row & ",C2:" & "C" & .Range("C" & .Rows.Count).End(xlUp).Row)
End With
For Each Cell In WS2_SearchRng
If IsNumeric(Left(Cell.Value2, 2)) Then
Select Case Cell.Rows.OutlineLevel
Case Is < 4
MatchPos = Application.Match(Cell.Value2, WS2_L1Rng, 0)
Case 4
MatchPos = Application.Match(Cell.Value2, WS2_L2Rng, 0)
End Select
If IsError(MatchPos) Then
Debug.Print "WS2 " & Cell.Value2
Else
MatchPos = MatchPos + 1
Workbooks(DataFile).Worksheets("WS2").Range("A" & Cell.Row, "I" & Cell.Row).Copy Destination:=Workbooks("Outline.xlsm").Worksheets("WS2").Range("A" & MatchPos, "I" & MatchPos)
End If
End If
DoEvents
Next Cell
End If
With Workbooks(DataFile)
.Save
.Close
End With
DataFile = Dir
Loop
'Return to regular configuration
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
End Sub
The problem:
Often, when I go to run this code, Application.Match throws an error when it tries to match to anything in WS2. It usually works fine if I just kill the execution and start over on the same data (sometimes it takes a few tries). Very rarely, it can't find anything in WS1 either; again, if I simply restart the execution it usually works just fine. Sometimes everything works great on the first try. Why does it not behave consistently?
Watch for implicit references to the active workbook/worksheet; what workbook/worksheet these instructions are referring to at run-time will depend on whatever workbook/worksheet is active at that time, and this is often responsible for such errors.
You can use Rubberduck (an open-source VBIDE add-in project I manage) to easily locate them for you (and other potential code issues).
Range("N2") in Worksheets("WS1").Range("A2", "A" & Range("N2").End(xlDown).Row) would be one. Worksheets used unqualified with a Workbook object would be another.
The solution is to explicitly qualify them with a Workbook or Worksheet object reference.

VBA code to protect all worksheets contingent on response

To delete a name and correlating data in the same row on each sheet of my workbook, users need to highlight a name and click a button on the first worksheet. A confirmation window then pops up asking if they are sure. If they click NO, everything remains protected and works fine. If they click YES, all worksheets are unprotected, and a second confirmation window appears - If YES is clicked the second time, the data is deleted from each worksheet and everything is protected after deletion. HOWEVER, if NO is clicked the second time, I can't get my code to then protect everything prior to exiting the sub.
Any help is appreciated, as well as suggestions for resources to help be become more proficient on my own. :)
Here is the code:
Sub DeleteRow()
'this macro deletes the row for a selected patient from worksheet of selected month and all months after that
'variables
Dim PatientName As String, PatientRow As Long, w As Long
Dim pRow As Long, lRow As Long, LookUpRng As Range, answer As Long
Dim rArray() As Variant, sArray As Variant
ReDim rArray(0)
ReDim sArray(0)
With ActiveSheet
ActiveSheet.Unprotect "arafluid"
PatientName = .Range("d" & ActiveCell.Row)
PatientRow = ActiveCell.Row
.Rows(PatientRow).Interior.ColorIndex = 4
'check that user want has selected correct patient
answer = MsgBox("Do you want to permanently remove patient " & vbCr & vbCr & _
PatientName & " from ALL months in this workbook?", vbYesNo, "Confirmation")
.Rows(PatientRow).Interior.ColorIndex = -4142
If answer = vbNo Then ActiveSheet.Protect "arafluid"
If answer <> vbYes Then Exit Sub
'check that it is safe to delete rows in future sheets
For w = Worksheets.Count To ActiveSheet.Index Step -1
With Sheets(w)
Sheets(w).Unprotect "arafluid"
pRow = 0
lRow = .Range("d10").CurrentRegion.Rows.Count + 9
Set LookUpRng = .Range("d10" & ":d" & lRow)
On Error Resume Next
pRow = Application.WorksheetFunction.Match(PatientName, LookUpRng, 0) + 9
If Err.Number <> 0 Then
Trail = Trail & vbCr & " " & .Name & " Not Found!"
Else
Trail = Trail & vbCr & " " & .Name & " ok"
' add value on the end of the arrays
ReDim Preserve rArray(UBound(rArray) + 1) As Variant
ReDim Preserve sArray(UBound(sArray) + 1) As Variant
rArray(UBound(rArray)) = pRow
sArray(UBound(sArray)) = w
End If
On Error GoTo 0
End With
Next w
'check that user still wants to delete
answer = MsgBox("Once deleted, the information cannot be recovered. Click YES to permanently remove: " & vbCr & vbCr & _
PatientName & vbCr, vbYesNo, "Are you sure?")
If answer <> vbYes Then Exit Sub
If answer <> vbNo Then
For a = Worksheets.Count To ActiveSheet.Index Step -1
Sheets(a).Protect "arafluid"
Next a
End If
'delete rows for selected patient
For d = 1 To UBound(sArray)
Sheets(sArray(d)).Rows(rArray(d)).EntireRow.Delete
Next d
End With
'loop through all sheets in the workbook.
For w = 1 To Sheets.Count
Sheets(w).Protect "arafluid"
Next w
End Sub
You are exiting the Sub if the user said "no". Change these lines after the second MessageBox:
answer = MsgBox("Once deleted, the information cannot be recovered. Click YES to permanently remove: " & vbCr & vbCr & _
PatientName & vbCr, vbYesNo, "Are you sure?")
If answer = vbNo Then 'This will test if user said "No" and will protect the sheets
For a = Worksheets.Count To ActiveSheet.Index Step -1
Sheets(a).Protect "arafluid"
Next a
Exit Sub
End If
As a note, after the first MsgBox you have the same situation with two If statements for the same thing, you could simplify them as:
If answer = vbNo Then
ActiveSheet.Protect "arafluid"
Exit Sub
End If

Excel VBA: message box to show list of errors at the end

I am new to VBA and I would really appreciate your help with the following. The code below searchers for blanks in Column A, highlights them, then it shows a message every time a cell has blanks with the cell location (e.g."No Value, in $A$1"). I copy these cells locations in another tab called "Results".
I need help with the following. I want to somehow for the message to show once with the list of cells that have blanks and their location. I do not want the message to pop out every time it finds a blank cell (I could have hundreds of blank cells in a file and clicking Ok for each one is not practical). Then that list of values will be copied in my "Results" sheet.
Here is the code I currently have:
Sub CeldasinInfo()
Dim i As Long, r As Range, coltoSearch As String
coltoSearch = "A"
For i = 1 To Range(coltoSearch & Rows.Count).End(xlUp).Row
Set r = Range(coltoSearch & i)
If Len(r.Value) = 0 Then
r.Interior.ColorIndex = 3 ' Red
r.Select
MsgBox "No Value, in " & r.Address
Sheets("Results").Range("A" & Sheets("Results").Range("A" & Rows.Count).End(xlUp).Row).Offset(1, 0).Formula = r.Address
End If
Next
End Sub
Thank so much in advance. I would really appreciate your help with this.
Something like this should work for you:
Sub CeldasinInfo()
Dim i As Long, r As Range, coltoSearch As String
Dim Result as String
Dim ErrCount as integer
ErrCount = 0
coltoSearch = "A"
coltoSearch = Range("1:1").find(What:="Hours", LookIn:=xlValues, LookAt:=xlWhole).Column
Result = "No Value in:" & vbcrlf
For i = 1 To Range(coltoSearch & Rows.Count).End(xlUp).Row
Set r = Range(coltoSearch & i)
If Len(r.Value) = 0 Then
r.Interior.ColorIndex = 3 ' Red
r.Select
' MsgBox "No Value, in " & r.Address
Result = Result & r.Address & vbcrlf
ErrCount = ErrCount + 1
if ErrCount Mod 10 = 0 then 'change to 15 or 20 or whatever works well
MsgBox Result
Result = "No Value in:" & vbcrlf
End If
Sheets("Results").Range("A" & Sheets("Results").Range("A" & Rows.Count).End(xlUp).Row).Offset(1, 0).Formula = r.Address
End If
Next
If ErrCount > 0 then
MsgBox "There were " & ErrCount & " errors detected." & vbcrlf & result
else
MsgBox "No errors detected"
End If
End Sub
This will give you each address on a separate line in the MsgBox. If there are hundreds of errors likely, this will result in a very long MsgBox output, and I'm not sure how it will handle that. You may need to add in a counter and display the message every 10, 15 or 20 errors for a better looking output.
If your cells are truly blank you can avoid a range loop and use SpecialCells
Sub CeldasinInfo()
Dim rng1 As Range
Dim coltoSearch As String
coltoSearch = "A"
On Error Resume Next
Set rng1 = Columns(coltoSearch).SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If rng1 Is Nothing Then Exit Sub
rng1.Interior.ColorIndex = 3 ' Red
MsgBox "No Value, in " & rng1.Address
End Sub

Creating excel macro to take info from form and append it onto bottom of list

I am currently working on a spreadsheet to help track individuals who attend a weekly meeting conducted by my department. I am trying to automate the process of tracking by using a macro to copy values from a list/form that a member of my department will enter the attendees email and the date. The email and date will then be added together (=a&b) to generate a value and that value will be used to mark whether the individual is present or not at that particular meeting. View Image of form/table
A report is generated after the meeting to tell which individuals have attended and how long they were on the call for. Before I was taking this report and pasting it onto the bottom of the original list but this has become inefficient as the columns and table length have changed. What I would like to do is take the emails, dates, and value on spreadsheet from the calculate tab and have those values append onto the bottom of the list on the reports tab without altering any of the previous information. View Image of reports tab
After the values have been appended to the bottom of the report, I have another tab called meeting dates. This contains a formula that will determine whether the individual was present or not by marking it with either ā€œYā€ or ā€œNā€. Forgot to mention that every week it is the same 17 individuals that are attending these meetings. Eventually I would like to have it so that if the date entered on the calculate tab is not present on the meeting dates tab, add the date to the meeting dates tab.
I am still very new to Excel VB and macros however do have some programming experience. Just not in excel. If somebody could help me, that would be awesome!
This answer is an attempt to get your started.
If you search the internet for "Excel VBA Tutorial" you will get many hits. Try a few because they are all different and pick the one you like best. Work through that tutorial to get a general feel for Excel. I do not believe you will be successful finding bits of relevant code without that general feel.
Do not try to describe your entire problem because I doubt anyone will respond. Instead try to break your problem down into little steps and seek help with those steps.
For example, you will need to determine the number of rows in the post-meeting report so you can access that data. You then want to add that data to the bottom of the previous list. In both cases you need to determine the last used row in a worksheet. "Excel VBA: How to find last row of worksheet?" is a simple question and you will be able to find multiple answers. I give my response to that question below.
I assume the post-meeting report and the list you are creating are in different workbooks. Your macro could be in the same workbook as the list or it could be in a different workbook. Macros can access their own workbooks, any other workbook that happens to be open or they can open as many other workbooks as required. Again "Excel VBA: How do I work with several workbooks?" should result in plenty of hits.
I have not tried either of my questions. I find "Excel VBA:" helps but you may require several attempts before you find the just the right question to get the answer you seek. But if your question is small and precise you should always be able to find an answer.
Let's return to the first question. An irritating feature of Excel VBA is that they are almost always several ways of achieving a similar effect. Create a new workbook, create a module and copy the code below to it. Run the macro FindFinal().
This macro demonstrates several methods of finding the last row and column. Every method has its problems and I have tried to show how how each method can fail. There is a lot of worksheet access within this macro which I believe will repay study. It should help you decide which method is appropriate for each of your requirements.
Option Explicit
Sub FindFinal()
Dim Col As Long
Dim Rng As Range
Dim Row As Long
' Try the various techniques on an empty worksheet
Debug.Print "***** Empty worksheet"
Debug.Print ""
With Worksheets("Sheet1")
.Cells.EntireRow.Delete
Set Rng = .UsedRange
If Rng Is Nothing Then
Debug.Print "Used range is Nothing"
Else
Debug.Print "Top row of used range is: " & Rng.Row
Debug.Print "Left column row of used range is: " & Rng.Column
Debug.Print "Number of rows in used range is: " & Rng.Rows.Count
Debug.Print "Number of columns in used range is: " & Rng.Columns.Count
Debug.Print "!!! Notice that the worksheet is empty but the user range is not."
End If
Debug.Print ""
Set Rng = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByRows, xlPrevious)
If Rng Is Nothing Then
Debug.Print "According to Find the worksheet is empty"
Else
Debug.Print "According to Find the last row containing a value is: " & Rng.Row
End If
Debug.Print ""
Set Rng = .Cells.SpecialCells(xlCellTypeLastCell)
If Rng Is Nothing Then
Debug.Print "According to SpecialCells the worksheet is empty"
Else
Debug.Print "According to SpecialCells the last row is: " & Rng.Row
Debug.Print "According to SpecialCells the last column is: " & Rng.Column
End If
Debug.Print ""
Row = .Cells(1, 1).End(xlDown).Row
Debug.Print "Down from A1 goes to: A" & Row
Row = .Cells(Rows.Count, 1).End(xlUp).Row
Debug.Print "up from A" & Rows.Count & " goes to: A" & Row
Col = .Cells(1, 1).End(xlToRight).Column
Debug.Print "Right from A1 goes to: " & ColNumToCode(Col) & "1"
Col = .Cells(1, Columns.Count).End(xlToLeft).Column
Debug.Print "Left from " & Columns.Count & _
"1 goes to: " & ColNumToCode(Col) & "1"
' Add some values and formatting to worksheet
.Range("A1").Value = "A1"
.Range("A2").Value = "A2"
For Row = 5 To 7
.Cells(Row, "A").Value = "A" & Row
Next
For Row = 12 To 15
.Cells(Row, 1).Value = "A" & Row
Next
.Range("B1").Value = "B1"
.Range("C2").Value = "C2"
.Range("B16").Value = "B6"
.Range("C17").Value = "C17"
.Columns("F").ColumnWidth = 5
.Cells(18, 4).Interior.Color = RGB(128, 128, 255)
.Rows(19).RowHeight = 5
Debug.Print ""
Debug.Print "***** Non-empty worksheet"
Debug.Print ""
Set Rng = .UsedRange
If Rng Is Nothing Then
Debug.Print "Used range is Nothing"
Else
Debug.Print "Top row of used range is: " & Rng.Row
Debug.Print "Left column row of used range is: " & Rng.Column
Debug.Print "Number of rows in used range is: " & Rng.Rows.Count
Debug.Print "Number of columns in used range is: " & Rng.Columns.Count
Debug.Print "!!! Notice that row 19 which is empty but has had its height changed is ""used""."
Debug.Print "!!! Notice that column 5 which is empty but has had its width changed is not ""used""."
Debug.Print "!!! Notice that column 4 which is empty but contains a coloured cell is ""used""."
End If
Debug.Print ""
Set Rng = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByRows, xlPrevious)
If Rng Is Nothing Then
Debug.Print "According to Find the worksheet is empty"
Else
Debug.Print "According to Find the last row containing a formula is: " & Rng.Row
End If
' *** Note: search by columns not search by rows ***
Set Rng = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByColumns, xlPrevious)
If Rng Is Nothing Then
Debug.Print "According to Find the worksheet is empty"
Else
Debug.Print "According to Find the last column containing a formula is: " & Rng.Column
End If
' *** Note: Find returns a single cell and the nature of the search
' affects what it find. Compare SpecialCells below.
Debug.Print ""
Set Rng = .Cells.SpecialCells(xlCellTypeLastCell)
If Rng Is Nothing Then
Debug.Print "According to SpecialCells the worksheet is empty"
Else
Debug.Print "According to SpecialCells the last row is: " & Rng.Row
Debug.Print "According to SpecialCells the last column is: " & Rng.Column
End If
Debug.Print ""
Row = 1
Do While True
Debug.Print "Down from A" & Row & " goes to: ";
Row = .Cells(Row, 1).End(xlDown).Row
Debug.Print "A" & Row
If Row = Rows.Count Then Exit Do
Loop
End With
With Worksheets("Sheet2")
.Cells.EntireRow.Delete
.Range("B2").Value = "B2"
.Range("C3").Value = "C3"
.Range("B7").Value = "B7"
.Range("B7:B8").Merge
.Range("F3").Value = "F3"
.Range("F3:G3").Merge
Debug.Print ""
Debug.Print "***** Try with merged cells"
Set Rng = .UsedRange
If Rng Is Nothing Then
Debug.Print "Used range is Nothing"
Else
Debug.Print "Used range is: " & Replace(Rng.Address, "$", "")
End If
Debug.Print ""
Set Rng = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByRows, xlPrevious)
If Rng Is Nothing Then
Debug.Print "According to Find the worksheet is empty"
Else
Debug.Print "According to Find the last cell by row is: " & Replace(Rng.Address, "$", "")
End If
Set Rng = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByColumns, xlPrevious)
If Rng Is Nothing Then
Debug.Print "According to Find the worksheet is empty"
Else
Debug.Print "According to Find the last cell by column is: " & Replace(Rng.Address, "$", "")
End If
Debug.Print "!!! Notice that Find can ""see"" B7 but not F3."
Debug.Print ""
Set Rng = .Cells.SpecialCells(xlCellTypeLastCell)
If Rng Is Nothing Then
Debug.Print "According to SpecialCells the worksheet is empty"
Else
Debug.Print "According to SpecialCells the last row is: " & Rng.Row
Debug.Print "According to SpecialCells the last column is: " & Rng.Column
End If
End With
End Sub
Function ColNumToCode(ByVal ColNum As Long) As String
Dim Code As String
Dim PartNum As Long
' Last updated 3 Feb 12. Adapted to handle three character codes.
If ColNum = 0 Then
ColNumToCode = "0"
Else
Code = ""
Do While ColNum > 0
PartNum = (ColNum - 1) Mod 26
Code = Chr(65 + PartNum) & Code
ColNum = (ColNum - PartNum - 1) \ 26
Loop
End If
End Function
In the code above, I access worksheet cells directly with statements such as .Range("B2").Value = "B2". This can be slow particularly when you are moving data from one worksheet to another. An alternative approach is to use arrays.
Dim Rng As Range
Dim ShtValues as Variant
With Worksheets("Xxxx")
Set Rng = .Range(.Cells(Row1, Col1), .Cells(Row2, Col2))
End With
ShtValues = Rng.Value
A Variant is a variable that can hold anything including an array. ShtValues = Rng.Value converts ShtValues to a two-dimensional array hold all the values within Rng. Processing values within an array is much faster that accessing them in the worksheet.
.Range(.Cells(Row1, Col1), .Cells(Row2, Col2)) is perhaps the easiest way of creating a range specifying the worksheet area with Cells(Row1, Col1) as the top left cell and Cells(Row2, Col2) as the bottom right.
If I understand correctly, you want to move data from the post-meeting report to the list but the sequence of columns in the report and list are not the same. This suggests you need to move the data as columns. Using .Range(.Cells(Row1, Col1), .Cells(Row2, Col2)) and with Col1 = Col2, you can define a range that is a column.
Rng1.Copy Destination := Cell2
The above statement will copy the contents of Rng1 to the range starting at Cell2. A statement like this for each column of data in the report may be the easiest way of copying the data.
I hope the above gives you a start.

VBA - Set Range Between Two Dates Using Search Function

I'm trying to get my VBA code to search through a column for a user-inputted value (on a form) and set a range based on the values.
I need the code to scan DOWN through the column until it finds the value (which is a date) and then scan UP through the column to get the second part of the range. I need it to be like this because there might be multiple instances of the same date and they all need to be accounted for.
I've tried this:
StartRange = ws.Cells.Find(What:=StartDate, SearchOrder:=xlRows, _
SearchDirection:=xlNext, LookIn:=xlValues)
EndRange = ws.Cells.Find(What:=EndDate, SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues)
But it's not working the way I had expected and is erroring. (Edit: the WS has been defined, so I know that's not the issue). I don't even know if I'm going about this the right way
I'm feeling defeated :(
Any help would be appreciated, Thanks in advance!
Edit:
I've yet to try any of the suggestions as I am away from my project at the moment, but I feel I need to clarify a few things.
The dates will always be in chronological order, I have a script that organises them on sheet activation
I need to be able to error handle dates that do not appear in the database, I also need the script to be able to "skip over" dates that don't exist. Ie, 1st 1st 1st, 3rd, 3rd, 5th. If my start and end dates were the 1st and 5th, the entire example would be the range.
Thanks for your help so far guys though, I appreciate it!
EDIT2:
I've tried a few answers and have added this in to my code, but it is now failing on a Range_Global fail.
Dim startrange, endrange, searchrange As Range
LookUpColumn = 2
With ws.Columns(LookUpColumn)
Set startrange = .Find(What:=Me.R_Start.Value, _
After:=ws.Cells(.Rows.count, LookUpColumn), _
SearchOrder:=xlRows, _
SearchDirection:=xlNext, LookIn:=xlValues)
Set endrange = .Find(What:=Me.R_End.Value, _
After:=ws.Cells(5, LookUpColumn), _
SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues)
searchrange = Range(startrange, endrange)
MsgBox searchrange.Address
End With
Any suggestions?
Using Find is the right way to do this type of thing, you just need to get a few details right.
Use Set to assign range references. Eg Set StartRange = ... (and make sure to Dim StartRange as Range). Ditto EndRange and SearchRange
Specify a After cell. Note that by default this is the Top Left cell of the search range, and the search begins after this cell. If your StartDate happens to be in cell A1 (and another cell) then leaving as default will return the wrong result
Limit the search range to the column of interest.
Dim all your variables. Each variaqble needs its own As (and use Option Explicit)
End result
Dim startrange As Range, endrange As Range, searchrange As Range
Dim LookUpColumn as Long
LookUpColumn = 2
With ws.Columns(LookupColumn)
' Make sure lookup column data is type Date
Set searchrange = .SpecialCells(xlCellTypeConstants)
searchrange.Value = searchrange .Value
Set searchrange = Nothing
Set StartRange = .Find(What:=CDate(StartDate), _
After:=.Cells(.Rows.Count, LookupColumn), _
SearchOrder:=xlRows, _
SearchDirection:=xlNext, LookIn:=xlValues)
Set EndRange = .Find(What:=CDate(EndDate), _
After:=.Cells(1, LookupColumn), _
SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues)
End With
Set searchrange = Range(startrange, endrange)
MsgBox searchrange.Address
Let's start with this and see what needs to be fine tuned.
This code will look for a date (based on input) and find the position of that date in a column. Same with the "EndDate" and then creates a range on that column between the 2 positions.
Sub ARange()
Dim Sh As Worksheet: Set Sh = Sheets("Sheet1")
Dim i, j As Integer
LookupColumn = "A" 'Define the LookupColum / If you find using column index to be simpler then you need to switch the search from (range) to (cells)
StartDate_Value = Sh.Range("B2").Value 'Use whatever you need to define the input values
EndDate_Value = Sh.Range("C2").Value 'Use whatever you need to define the input values
For i = 1 To 30000
If Sh.Range(LookupColumn & i).Value = EndDate_Value Then EndDate_Row = i
Next i
For j = EndDate_Row To 1 Step -1
If Sh.Range(LookupColumn & j).Value = StartDate_Value Then StartDate_Row = j
Next j
Dim MyDateRange As Range: Set MyDateRange = Sh.Range(LookupColumn & StartDate_Row & ":" & LookupColumn & EndDate_Row)
MsgBox "MyDateRange = " & LookupColumn & StartDate_Row & ":" & LookupColumn & EndDate_Row
End Sub
Another approach should imply looking for the EndDate from bottom upwards (as in Excel's column values) and for the StartDate from top to bottom. like this:
For i = 30000 to 1 step -1
For j = 1 To 30000
And the 3rd (the charm):for the EndDate from top to bottom and for the StartDate from top to bottom. like this:
For i = 1 to 30000
For j = 1 To 30000
And the 4th (The One):
For i = 1 to 30000
For j = 30000 to 1 Step -1
On my home laptop the search on the 30.000 cells is instant (under 1s).
Give it a try and based on the feedback we can fine tune it.
On the Other hand, I might read your question as for looking To select not all values between the top / bottom position, but any cells with values of dates between the 2 input values neverminind the arrangement of the values within the list (column cells). i.e. If StartDate = 1.Jan.2013 and EndDate = 3.Jan.2013. The code should pick up 1,2 and 3 from the 30.000 list neverminind the position of these 3 dates (which in fact may be found thousands of times). If This is true, the solution may be simpler than the one above.
I don't like the concept of this date search for a couple of reasons..
It makes the assumption that the dates will always be in order
It makes the assumption that both the dates will exist in the list
Whilst these may be valid assumptions in this case, I'm sure there may be instances where this may not be the case...
I don't know the best way to do this but one alternative to consider is using the auto-filter
Something like:
Sub FindDateRange()
Dim sht As Worksheet
Dim column As Long
Set sht = Sheet1
Dim rng As Range, inclusiveRange As Range
Dim startDate As Long, endDate As Long
column = 2
On Error GoTo Err
startDate = DateValue("02/10/2012")
endDate = DateValue("05/10/2012")
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
sht.Cells(1, column).AutoFilter Field:=column, Criteria1:=">=" & startDate, Operator:=xlAnd _
, Criteria2:="<=" & endDate
Set rng = sht.Range(sht.Cells(2, column), sht.Cells(sht.Cells(sht.Rows.Count, column).End(xlUp).Row, column)).SpecialCells(xlCellTypeVisible)
sht.AutoFilterMode = False
If rng.Address = sht.Cells(1, column).Address Then
MsgBox Format(startDate, "dd-mmm-yyyy") & " - " & Format(endDate, "dd-mmm-yyyy") _
& vbCrLf & vbCrLf & "No instances of the date range exist"
Else
Set inclusiveRange = sht.Range(rng.Cells(1, 1), rng.Cells(rng.Count, 1))
MsgBox Format(startDate, "dd-mmm-yyyy") & " - " & Format(endDate, "dd-mmm-yyyy") _
& vbCrLf & vbCrLf & "the range is " & rng.Address & vbCrLf & vbCrLf & _
"inclusive range is " & inclusiveRange.Address
End If
continue:
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub
Err:
MsgBox Err.Description
GoTo continue
End Sub