How to Declare/Define and simultaneously Select multiple cells/ranges in VBA? - vba

Below is the problematic part of my code. The code takes in the ProductCode and the Quantity through the InputBox, however, once the ProductcCode is entered, I also want it to define store the values, "Cost" and "Discount" that are beside it so they can be displayed in my final Input Box. Any help would be greatly Appreciated.
'Obtaining VLookup Value
ProductCode = InputBox("Enter the ProductCode's code.")
'Error checking
Do Until ErrorCheck = False
If ProductCode = "" Then
ErrorCheck = True
MsgBox ("Not a valid entry.")
ProductCode = InputBox("Enter the ProductCode's code.")
Cost = ActiveCell.Offset(0, 1).Select
MinQty = ActiveCell.Offset(0, 2).Value
Discount = ActiveCell.Offset(0, 3).Value
ElseIf IsError(Application.VLookup(ProductCode, myRange, 3, False)) Then
ErrorCheck = True
MsgBox ("The value entered was not found.")
ProductCode = InputBox("Enter the ProductCode's code.")
Else
ErrorCheck = False
End If
Loop

your narrative isn't very clear and also conflicts with part of your code, but I think O got the whole idea
so you may try and adapt this (commented) code
Option Explicit
Sub Product()
Dim ProductCode As String
Dim ErrorCheck As Boolean
Dim Cost As Double, MinQty As Double, Discount As Double
Dim MyRange As Range
Dim found As Variant
Set MyRange = Range("A1:A10") '<-- change it to your actual "MyRange" setting
Do '"main" outer loop
Do '"Product code input" inner loop
ProductCode = Application.InputBox("Enter the ProductCode's code.", Type:=2) '<--| force string input
Loop While ProductCode = ""
found = Application.Match(ProductCode, MyRange.Columns(1), 0) '<-- try getting ow index of prodcut code in 1st column of "MyRange" range
If IsError(found) Then '<--| if no match found...
MsgBox "The value entered was not found!" & vbCrLf & vbCrLf & "Please, try again", vbCritical + vbOKOnly '<-- inform the user and loop again
Else '<--| otherwise
With MyRange(found, 1) '<-- reference the matching cell
Cost = .Offset(0, 1).Value '<--| store "Cos"t from cell 1 column to the right of the referenced one
MinQty = .Offset(0, 2).Value '<--| store "MinQty" from cell 2 columns to the right of the referenced one
Discount = .Offset(0, 3).Value '<--| store "Discount" from cell 3 columns to the right of the referenced one
End With
End If
Loop While IsError(found)
End Sub

Related

Looping through column based on comparing dates, if less than today then lookup previous columns

I understand that a match function is needed to look up values to the left rather than a right (VLOOKUP).
My want to click the macro button to display the items of the previous two columns, if the cell (is past its due date), and build an array of items which are past its due date.
Sub ItemRegister()
Application.Workbooks("Current.xlsm").Worksheets("Sheet1").Activate
On Error GoTo MyErrorHandler:
Dim Today As Date
Dim InspectionDate As Range
Dim ItemRow As Long
Dim ItemCol As Long
Dim Check As Variant
Today = Date
Set InspectionDate = [G4:G500]
Set TableC = [A4:A500]
Set TableS = [B4:B500]
Set DateArray = [G4:G500]
ItemRow = [G4].Row
ItemCol = [G4].Column
For Each Cell In InspectionDate
Check = Application.Match(Cell, DateArray, 0) 'need to fix match up
If Cell = "" Then
Item = ""
Serial = ""
If Cell <= Today Then
Item = Application.WorksheetFunction.Index(TableC, Check)
Serial = Application.WorksheetFunction.Index(TableS, Check)
Else
Item = ""
Serial = ""
End If
ItemRow = ItemRow + 1
End If
Next Cell
Exit Sub
MyErrorHandler:
If Err.Number = 1004 Then
MsgBox "An error has occured - please ensure that cells have not been altered in anyway - Something is wrong with code, Debug It" 'Remove this, when process is completed
Else
MsgBox "The item(s) that need inspection is/are: " & vbNewLine & vbNewLine & Item & "-" & Serial
End If
End Sub
Thanks in advance.
you could adopt an AutoFilter() approach;
Option Explicit
Sub main()
Dim area As Range
Dim iCell As Long
With Application.Workbooks("Current.xlsm").Worksheets("Sheet1") '<--| reference relevant worksheeu
With .Range("G3", .Cells(.Rows.COUNT, "G").End(xlUp).Offset(1)) '<-- reference its column "G" cell from row 3 down to last not empty cell
.AutoFilter Field:=1, Criteria1:="<=" & CDbl(Date) '<--| filter referenced column on dates preceeding or equal today's date
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then '<--| if any cell filtered other than header (which is in row 3)
With .SpecialCells(xlCellTypeVisible) '<--| reference columnn "G" filtered cells
ReDim Item(1 To .COUNT) '<--| size Item array to the number of referenced (i.e. filtered) cells
ReDim Serial(1 To .COUNT) '<--| size Serial array to the number of referenced (i.e. filtered) cells
For Each cell In .Cells '<--| loop through referenced (i.e. filtered) cells
iCell = iCell + 1 '<--| update cell counter
Item(iCell) = cell.Offset(, -6).Value '<--| retrieve value in column "A" cell at current filtered cell row
Serial(iCell) = cell.Offset(, -5).Value '<--| retrieve value in column "G" cell at current filtered cell row
Next cell
End With
End If
End With
.AutoFilterMode = False '<--| show all rows back
End With
End Sub

Inserting the whole VBA Code, which includes multiple Do Loops in a For Loop

My code currently asks for the product code and the quantity of the product, and displays the total cost and the discount the consumer gets. However, I now need to use a For Loop that asks the "different types of products bought," and thus, place my current code in this For LOOP. That is, each time through the loop you should get and display information about a particular product purchased. I have spent hours trying to insert my code in a For Loop to no avail. Any help would be immensely useful since I am rather inexperienced and teaching myself! thanks!
Sub Product()
Dim ProductCode As String
Dim ErrorCheck As Boolean
Dim Cost As Double, MinQty As Double, Discount As Double
Dim MyRange As Range
Dim found As Variant
Dim QtyBought As Integer
Dim TotalCost As Double
Set MyRange = Worksheets("Data").Cells '<-- the range containing the data provided
Do '"main" outer loop
Do '"Product code input" inner loop
ProductCode = Application.InputBox("Enter the Product's code.", Type:=2) '<--| force string input
Loop While ProductCode = ""
found = Application.Match(ProductCode, MyRange.Columns(1), 0) '<-- try getting ow index of prodcut code in 1st column of "MyRange" range
If IsError(found) Then '<--| if no match found...
MsgBox "The value entered was not found!" & vbCrLf & vbCrLf & "Please, try again", vbCritical + vbOKOnly '<-- inform the user and loop again
Else '<--| otherwise
With MyRange(found, 1) '<-- reference the matching cell
Cost = .Offset(0, 1).Value '<--| store "Cost from cell 1 column to the right of the referenced one
MinQty = .Offset(0, 2).Value '<--| store "MinQty" from cell 2 columns to the right of the referenced one
Discount = .Offset(0, 3).Value '<--| store "Discount" from cell 3 columns to the right of the referenced one
End With
End If
Loop While IsError(found)
'Obtaining QtyBought Value
QtyBought = InputBox("Enter the QtyBought ordered.")
'Error checking
Do Until ErrorCheck = False
If IsNumeric(QtyBought) = False Then
ErrorCheck = True
MsgBox ("Not a valid entry.")
QtyBought = InputBox("Enter the QtyBought ordered.")
Else
ErrorCheck = False
End If
Loop
'finding out the cost of the prodcut ordered.
TotalCost = Selection.Value * QtyBought
Discount = Selection.Value * Discount
'Obtaining discount rate
If QtyBought > MinQty Then
MsgBox ("You purchased " & QtyBought & "units of product " & ProductCode & ".The total cost is " & Format(TotalCost, "$#,##0") & "Because you purchased at least " & MinQty & "units, you get a discount of " & Discount & "on each unit")
Else
MsgBox ("Sorry, You don't qualify for any discount")
End If
End Sub
I'd have an "outer" Sub calling an "inner" Sub while in a ProductName input loop, like follows:
Option Explicit
Sub Products()
Dim ProductCode As String
Do '"main" outer loop
Do '"Product code input" inner loop
ProductCode = Application.InputBox("Enter the ProductCode's code [input space to end]", Type:=2) '<--| force string input
Loop While ProductCode = ""
If ProductCode <> " " Then Product ProductCode
Loop While ProductCode <> " "
End Sub
Sub Product(ProductCode As String)
Dim Cost As Double, MinQty As Double, Discount As Double
Dim MyRange As Range
Dim found As Variant
Dim QtyBought As Integer
Dim TotalCost As Double
Set MyRange = Worksheets("Data").UsedRange '<-- the range containing the data provided
found = Application.Match(ProductCode, MyRange.Columns(1), 0) '<-- try getting ow index of prodcut code in 1st column of "MyRange" range
If IsError(found) Then '<--| if no match found...
MsgBox "The value entered was not found!" & vbCrLf & vbCrLf & "Please, try again", vbCritical + vbOKOnly '<-- inform the user and loop again
Exit Sub '<--| exit sub to get another product code
End If
With MyRange(found, 1) '<-- reference the matching cell
Cost = .Offset(0, 1).Value '<--| store "Cost from cell 1 column to the right of the referenced one
MinQty = .Offset(0, 2).Value '<--| store "MinQty" from cell 2 columns to the right of the referenced one
Discount = .Offset(0, 3).Value '<--| store "Discount" from cell 3 columns to the right of the referenced one
End With
'Obtaining QtyBought Value
QtyBought = Application.InputBox("Enter the QtyBought ordered.", Type:=1) '<--| force numeric input
'finding out the cost of the product ordered.
TotalCost = Selection.Value * QtyBought '<--shouldn't this be: TotalCost = Cost * QtyBought
Discount = Selection.Value * Discount '<--shouldn't this be: Discount = TotalCost * Discount
'Obtaining discount rate
If QtyBought > MinQty Then
MsgBox ("You purchased " & QtyBought & "units of product " & ProductCode & ".The total cost is " & Format(TotalCost, "$#,##0") & "Because you purchased at least " & MinQty & "units, you get a discount of " & Discount & "on each unit")
Else
MsgBox ("Sorry, You don't qualify for any discount")
End If
End Sub

Find user input value and copy/paste another value in an empty field

I need to locate a date entered by a user in a specific column. If the date is found, the macro should check the third field to the right:
if it's blank, it should copy and paste a specific value from another sheet;
if it's not blank, just pop out a message box.
The current code does not do a copy-paste and somehow it is not running through a list of dates though it has been already working before.
Sub EnterRecord()
Dim rcdDate As Date
Dim r As Range
Set ws1 = Sheets("Manual")
Set ws2 = Sheets("Data")
ws1.Activate
rcdDate = InputBox("Please enter date dd/mm/yyyy")
With ws1.Range("K:K")
Set r = .Cells.Find(What:=rcdDate)
If Not r Is Nothing Then
r.Select
ActiveCell.Offset(0, 3).Activate
If ActiveCell.Value = "" Then
ws2.Range("b1").Copy
ws1.ActiveCell.Value.Paste Paste:=xlPasteValues
End If
MsgBox "Date is incorrect or the record is already entered"
End If
End With
End Sub
If you want to search for the date in column K of ws1 and you already are using the With then replace:
Set r = Cells.Find(What:=rcdDate)
with
Set r = .Cells.Find(What:=rcdDate)
There may be other problems.
EDIT#1
Once you have executed:
ActiveCell.Offset(0, 3).Activate
you have "moved" the ActiveCell, so replace:
If ActiveCell.Offset(0, 3).Value = "" Then
with
If ActiveCell.Value = "" Then

Application.match with multiple control inputs - Does it work?

I'm new to VBA and I've spent probably 2 days trying to fix this code to make it work. Its actually a very simple credit entry to a statement of accounts database that I'm trying to compile.
The userform looks something like this:
http://imgur.com/gallery/DCHd23M/new
Where the top textboxes are named ClientTextBox and DebitTextBox respectively
and the bottom 10 + 10 textboxes are named Inv1, Inv2... Inv20. The invoice textboxes are to reference which invoices that the cheques we have received will be paying. E.g. We received a cheque with "$100" from Client "ABC" to pay for invoice "001" and "002". Hence 001 and 002 are entered into the invoice textboxes 1 and 2 with the remaining left blank.
Msgboxes were added along the way for error checking. I used the "Like" function to differentiate the top textboxes with the invoice textboxes to differentiate the input, as seen here:
For Each Ctr In Me.Controls
If TypeName(Ctr) = "TextBox" And Ctr.Name Like "Inv#*" Then
This worked fine. But the area of particular concern was in this section of code:
If Ctr.value <> "" Then
MsgBox ("Found a value!" & vbNewLine & Ctr.value)
If Application.WorksheetFunction.CountIf(.Worksheets("SOA").Range("A:A"), Ctr.value) Then
V = Application.WorksheetFunction.Match(Ctr.value, .Worksheets("SOA").Range("A:A"), 0)
If .Worksheets("SOA").Range(V, 7).value = "Unpaid" Then
.Worksheets("SOA").Range(V, 7).value = "Paid"
The Application.worksheetfunction.match is virtually unable to match any variable (e.g. Y = Ctr.value) with an error 1004 but works when I change Ctr.value to 1, like this:
V = Application.WorksheetFunction.Match(1, .Worksheets("SOA").Range("A:A"), 0)
I am new to userforms so I don't quite get some of its limitations. Any suggestions would be most welcome! I just learnt VBA coding about a week ago so I'm sure that I've got a long way to go. Here is the full code:
Private Sub OkButton_Click()
Dim SOA As ListObject
Dim Ctr As Control
Dim pPage As msforms.Page
Dim credit As ListRow
Dim V As Variant
With ThisWorkbook
Set SOA = .Worksheets("SOA").ListObjects(1) 'table name
Set credit = SOA.ListRows.Add(1) 'the new row, always add to the top
credit.Range(1, 2).value = Format(Now(), "mm/dd") 'Date
credit.Range(1, 3).value = ClientTextBox.value 'Client name
credit.Range(1, 6).value = DebitTextBox.value 'Credit Amt
For Each Ctr In Me.Controls
If TypeName(Ctr) = "TextBox" And Ctr.Name Like "Inv#*" Then
MsgBox (Ctr.Name)
If Ctr.value <> "" Then
MsgBox ("Found a value!" & vbNewLine & Ctr.value)
If Application.WorksheetFunction.CountIf(.Worksheets("SOA").Range("A:A"), Ctr.value) Then
V = Application.WorksheetFunction.Match(Ctr.value, .Worksheets("SOA").Range("A:A"), 0)
If .Worksheets("SOA").Range(V, 7).value = "Unpaid" Then
.Worksheets("SOA").Range(V, 7).value = "Paid"
Else: MsgBox ("Invoice #" & Ctr.value & " has already been paid!")
End If
Else
MsgBox ("Didn't find the number")
End If
End If
End If
Next
End With
Unload Me
End Sub
Your Ctr.Value is a String value. E.g. "1".
The Application.WorksheetFunction.CountIf(.Worksheets("SOA").Range("A:A"), Ctr.value) will count even if the values in the Range are numbers. This is because the second parameter of CountIf is expected as a String since it could also be ">1" or "<1" or "<>1".
But Application.WorksheetFunction.Match(Ctr.value, .Worksheets("SOA").Range("A:A"), 0) will not match if the Ctr.Value is "1" but the values in the range are numbers.
So if the values in the range are numbers, you must convert Ctr.Value to a number value before using in Match.
Example:
Application.WorksheetFunction.Match(CLng(Ctr.value), .Worksheets("SOA").Range("A:A"), 0)

Select first Empty Cell from a selected cell (or range), then add a Value (date), then offset and inputext

I have been looking around to find good examples, but can't find what I need.
Here is the context: The code is for a sales tracker worksheet with around 50 vendors (each of them can add value and most of them didn't know anything about Excel).
I want to select the first empty cell (where the first they can enter a value is B5, not higher, because the top of the sheet includes some instructions). In fact, from this cell (Date value is in Column B, and begin in Row 5) the second date value is in B6
Add the Date (date or now) as activecell.value
Then 2 cells to the right activecell.offset(0,2)
And insert the value of the textbox (their ID).
For now, I can add the date and the Textbox ID.
Here what I have so far:
Sub CommandButton1_click()
Dim Input_ID As String, Date_in As String
Date_in = Format(Now, "DD-MMM")
ActiveCell.Value = Date_in
Input_ID = InputBox("SVP entré votre ID ", "Data Entry Form")
ActiveCell.Offset(0, 2) = Input_ID
End Sub
But is it possible to make that command/button only available for column "B?" Because I don't what them add a date and their ID to another Column.
PS: I More or less begin in VBA, I learn from a bit of everywhere, So if you could add some explanation in your code, i appreciate it. Thanks
Edit1: Post from comment
Sub Date_insert_click()
Dim Input_ID As String, Date_in As String
Dim ws As Worksheet
Set ws = ActiveSheet 'change to your actual worksheet
'Dim Date_in As Date
Date_in = Format(Now, "DD-MMM")
With ws.Range("B" & ws.Rows.Count).End(xlUp)
If .Row >= 4 Then .Offset(1, 0).Value = Date_in Else Exit Sub
Input_ID = InputBox("SVP entré votre ID ", "Data Entry Form")
If Input_ID <> "" Then .Offset(1, 1).Value = Input_ID Else .Offset(1, 0).Value = ""
End With
End Sub
But I found a weakness. If I select a cell anywhere down like K378,
I Still can add the value (date_In or value of the inputbox) but can't see it because the cell isn't active.
Try this as commented:
Sub CommandButton1_click()
Dim Input_ID As String, Date_in As String
Dim ws As Worksheet
Set ws = Thisworkbook.Sheets("Sheet1") 'change to your actual worksheet
Date_in = Format(Now, "DD-MMM")
With ws.Range("B" & ws.Rows.Count).End(xlUp)
If .Row >= 4 Then .Offset(1, 0).Value = Date_in Else Exit Sub
Input_ID = InputBox("SVP entré votre ID ", "Data Entry Form")
If Input_ID <> "" Then .Offset(1, 2).Value = Input_ID Else .Offset(1, 0).Value = ""
End With
End Sub
Edit1: Explanation as requested
Q: Why pass Worksheet object to a variable?
A: HERE is some explantion for this question. Also it makes your code a lot more readable and easy to debug.
Explaining the code:
'This line simply finds the last cell in Column B
With ws.Range("B" & ws.Rows.Count).End(xlUp)
'other code here
End With
Why use With? I used with because all the coding focuses on Column B and other data input is also reference to it. You can also see explanation on the advantages of using it in the link I provided above.
With ws.Range("B" & ws.Rows.Count).End(xlUp)
'Since we used With, you can directly access the Range properties
'The following line uses the Row and Offset property
'Row returns the row number of the range you're workning on
'Offset literally offets the range you are currently working on
If .Row >= 4 Then .Offset(1, 0).Value = Date_in Else Exit Sub
'This next line is already known to you, no need to explain
Input_ID = InputBox("SVP entré votre ID ", "Data Entry Form")
'Next line checks if Input_ID is supplied
'If yes, we use offset to get to the 2nd column from the current range
'If no, we delete the Date_In value
If Input_ID <> "" Then .Offset(1, 2).Value = Input_ID Else .Offset(1, 0).Value = ""
End With
I hope I explained it enough.
But if ever you still need more explanation, just comment it out.
If you encounter dificulties somewhere just post another question.