need a function that returns the first completely empty row (no values, no formulas, no blanks) from a sheet with sparely populated cells. No one single column is required to be filled.
I tried this, but i can even get it to compile:
Public Donations As Worksheet
Set Donations = Sheets("Sheet2")
Function getEmptyRow() As Long
Dim lastCol As Long, lastRow As Long, maxRow As Long
Dim col As Long
Dim r As Variant
lastCol = Donations.Cells(1, Columns.Count).End(xlToLeft).Column
For col = 1 To lastCol Step 1
lastRow = Donations.Cells(Rows.Count, col).End(xlUp).row
maxRow = Application.WorksheetFunction.max(maxRow, lastRow)
Next col
getEmptyRow = maxRow + 1
End Function
Using EntireRow (which is so useful let me tell you) and counting row by row starting in A1 is one very basic way of doing this.
This will tell you in the immediate window:
Sub findemptyrow() '''Psuedo Code
Application.ScreenUpdating = False 'turns off annoying blinky
Range("a1").Activate 'start at beginning
While a <> 1 'keep going
If Application.CountA(ActiveCell.EntireRow) = 0 Then 'is it blank?
Debug.Print "Row " & (ActiveCell.Row) & " is blank." 'it is
a = 1 'stop going
End If
ActiveCell.Offset(1).Activate 'next cell
Wend 'do it all over again
Application.ScreenUpdating = True 'back to normal settings
End Sub
Making ScreenUpdating False will make this faster, even with 10k's of rows.
The Range.Find method is likely the most expedient method. Look for a wildcard (What:=Chr(42)), start in A1 (After:=.Cells(1, 1), search backwards (SearchDirection:=xlPrevious), search row-by-row (SearchOrder:=xlByRows).Row), look at the formulas (LookIn:=xlFormulas) since that will find the first value or formula; looking at xlValues may not be correct if a formula is returning an empty string ("").
Option Explicit
Public Donations As Worksheet
Sub test()
Set Donations = Worksheets("Sheet2")
Debug.Print getNextEmptyRow(Donations)
End Sub
Function getNextEmptyRow(ws As Worksheet)
With ws.Cells
getNextEmptyRow = .Find(What:=Chr(42), After:=.Cells(1, 1), LookIn:=xlFormulas, _
SearchDirection:=xlPrevious, SearchOrder:=xlByRows).Row + 1
End With
End Function
You cannot set Donations in the declarations area of a module code sheet. Declare the public variable in the declarations area (top) of the code sheet but Set the variable in a sub or function.
Don't forget to add 1 to the row returned if you want the 'next empty row'.
Just another alternative using the SpecialCells method of a `Range:
Option Explicit
Sub Test()
Debug.Print "Last row on Sheet1 is: " & FirstCompletelyEmptyRow(Sheet1)
End Sub
Function FirstCompletelyEmptyRow(ByRef wsTarget As Worksheet) As Long
FirstCompletelyEmptyRow = wsTarget.Range("A1").SpecialCells(xlCellTypeLastCell).Row + 1
End Function
Related
This is my first project using Excel-VBA. I've figured out how to use VBA code to do most of what I've wanted. I'm now trying to copy data to a new cell, and then I want to copy a range, but not copy down the data of new cell and leave it blank below. I've only been able to leave the copied data in it's exact place and not move down with the range copies. I don't know if it's possible to do what I want, or if I just don't know how. Any help would be greatly appreciated!
Sub AddHeader()
Range("CA1").Formula = "Stay Date"
End Sub
=====================================
Sub CellCopy()
Range("H2:H4000").Copy Range("CA2")
End Sub
=====================================
Sub CopyData()
Dim xRow As Long
Dim VInSertNum As Variant
xRow = 1
Application.ScreenUpdating = False
Do While (Cells(xRow, "A") <> "")
VInSertNum = Cells(xRow, "P")
If ((VInSertNum > 1) And IsNumeric(VInSertNum)) Then
Range(Cells(xRow, "A"), Cells(xRow, "BZ")).Copy
Range(Cells(xRow + 1, "A"), Cells(xRow + VInSertNum - 1, "BZ")).Select
Selection.Insert Shift:=xlDown
xRow = xRow + VInSertNum - 1
End If
xRow = xRow + 1
Loop
Application.ScreenUpdating = False
End Sub
=====================================
Sub RunAllMacros()
AddHeader
CellCopy
CopyData
End Sub
Your question isn't entirely clear but if I understand correctly, you want to:
Repeat each row in your worksheet n times (where n is read from the worksheet itself and each row has its own n value).
There are certain columns you want to exclude from being repeated.
I would add that:
It might be better to loop in reverse order (so that row insertions do not affect the iterator/variable keeping track of loop progress).
Have you considered copying the entire row (Range.EntireRow) and then using Range.Clear to clear those columns which you didn't want repeated?
It's always good to include an example of input and expected output. Otherwise, it's difficult for the responder to verify their own answer.
The code below:
Option Explicit
Private Sub AddHeader(ByVal someSheet As Worksheet)
someSheet.Range("CA1").Formula = "Stay Date"
End Sub
Private Sub CellCopy(ByVal someSheet As Worksheet)
someSheet.Range("H2:H4000").Copy someSheet.Range("CA2")
End Sub
Private Sub RunAllMacros()
Dim sheetToModify As Worksheet
Set sheetToModify = ActiveSheet ' Better to replace with something like ThisWorkbook.Worksheets("Sheet1")
AddHeader sheetToModify
CellCopy sheetToModify
CopyData sheetToModify
End Sub
Private Sub CopyData(ByVal someSheet As Worksheet)
Dim lastRow As Long
lastRow = someSheet.Cells(someSheet.Rows.Count, "A").End(xlUp).Row
Dim rowIndex As Long
For rowIndex = lastRow To 2 Step -1 ' Presume you want to skip headers?
Dim numberOfTimesToRepeatRow As Variant
numberOfTimesToRepeatRow = someSheet.Cells(rowIndex, "P") ' Will need to -1 as count includes the row being copied.
If IsGreaterThanOne(numberOfTimesToRepeatRow) Then
With someSheet.Range("A" & rowIndex, "CA" & rowIndex)
.Copy
.Offset(1).Resize(numberOfTimesToRepeatRow - 1).Insert Shift:=xlDown
' Have to repeat/re-evaluate (cannot use With or
' object reference since rows have been inserted)
.Offset(1).Resize(numberOfTimesToRepeatRow - 1).Columns("CA").Clear
End With
End If
Next rowIndex
Application.CutCopyMode = False
End Sub
Private Function IsGreaterThanOne(ByVal someValue As Variant)
' Dedicated function to reduce indentation in caller.
' Returns True if value is numeric AND greater than 1 (else
' False).
' Separate IF statements since no short-circuit
' evaluation -- meaning non-numeric values could otherwise
' cause type mismatch error.
If IsNumeric(someValue) Then
If someValue > 1 Then
IsGreaterThanOne = True
End If
End If
End Function
The code above keeps the value in column CA for only the original rows -- and not for the newly inserted rows. In the other words, there are blanks in column CA of the newly inserted rows.
Hope that makes sense and gives you some idea on how to achieve this. If I've misunderstood, you can let me know.
I've tried to figure out the last used column in my excel spreadsheet using VBA to start writing something right after that column. In the image below I've tried to show what I meant and where I wanna start writing from. The desired field is already selected there which is "F2".
However, the problem is the data already available there did not maintain uniformity. How can I figure out the last used column using VBA?
This is my try:
Sub FindLastColumn()
Dim lCol&
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
MsgBox lCol
End Sub
It produces 8 as result which is not correct as the right one should be 5.
The data-ridden sheet looks like below:
If you want to find the last column in your range excluding the header, you could achieve that as below, amend the Sheet name from Sheet1 to the Sheet you are actually using:
Sub foo()
LastRow = Sheet1.UsedRange.Rows.Count
'get the last row with data in your used range
MaxCol = 1
For i = 2 To LastRow 'loop from row 2 to last
If Sheet1.Cells(i, Sheet1.Columns.Count).End(xlToLeft).Column > MaxCol Then
MaxCol = Sheet1.Cells(i, Sheet1.Columns.Count).End(xlToLeft).Column
'get the highest value for the column into variable
End If
Next i
MsgBox MaxCol
End Sub
It appears that you want to find the right-hand most used column in rows 2 to the end of your data. To do that, you'll need to loop through all the rows of data keeping track of which column is Max(LastUsedColumn). Unfortunately, there is no such built in function, but you could write one something like this:
Public Function MaxUsedColumnInRow(ByVal SheetToCheck As Worksheet, ByVal RowToCheck As Long) As Long
MaxUsedColumnInRow = SheetToCheck.Cells(RowToCheck, Columns.count).End(xlToLeft).Column
End Function
Now that you have a nifty function to determine which is the maximum used column in a row, you can call it in a loop, like this:
Public Function MaxUsedColumnInRange(ByVal SheetToCheck As Worksheet, ByVal StartRow As Long, ByVal EndRow As Long) As Long
Dim curRow As Long
For curRow = StartRow To EndRow
Dim CurCol As Long
CurCol = MaxUsedColumnInRow(SheetToCheck, curRow)
Dim maxCol As Long
If CurCol > maxCol Then
maxCol = CurCol
End If
Next
End Function
And, finally, give it a quick test replacing "Sheet1" with the name of the worksheet you're specifically checking:
Public Sub TestIt()
MsgBox "Max Used column on sheet1 = " & CStr(MaxUsedColumnInRange("Sheet1", 2, 50))
End Sub
Of course, you'll want to determine the max used row on your sheet and pass that into the the MaxUsedColumnInRange function - unless you happen to have exactly 50 rows of data, the example test Sub probably won't get you your actual desired result.
As a side benefit, you now have a handy function you can call in the future to determine the max column in a row so you don't have to remember the proper way of doing it. (I usually forget so I have to look it up, or use a nifty helper function to "remember" for me.)
Use a variation of the Find method of finding it, but limit it to ignore row 1:
Sub Test()
Dim rng As Range
Set rng = LastCell(Sheet1)
MsgBox "Last cell containing data is " & rng.Address & vbCr & _
"Selected cell is in example is " & Sheet1.Cells(2, rng.Column + 1).Address
End Sub
Public Function LastCell(wrkSht As Worksheet) As Range
Dim lLastCol As Long, lLastRow As Long
On Error Resume Next
With wrkSht.Rows("2:1048576")
lLastCol = .Cells.Find("*", , , , xlByColumns, xlPrevious).Column
lLastRow = .Cells.Find("*", , , , xlByRows, xlPrevious).Row
If lLastCol = 0 Then lLastCol = 1
If lLastRow = 0 Then lLastRow = 1
Set LastCell = wrkSht.Cells(lLastRow, lLastCol)
End With
On Error GoTo 0
End Function
Easy route would be to use Find like below:
Dim rgLastColumnCell As Range
Set rgLastColumnCell = ActiveSheet.Cells.Find("*", , , , xlByColumns, xlPrevious)
MsgBox "Last Used Column is : " & rgLastColumnCell.Column
Adjust ActiveSheet.Cells portion to suit your need like: Activesheet.Range("B2:XFD1048576") if you want to skip first row from the check.
You cannot get the result you require by using built-in functions, either you can get the column H because it is the last used column or the column B, because it is the last filled column, To get E you have to write your own code, and by the look of it, it seems that you want the end of the colored range. You can check the last column where color is not present in a loop
Sub checkLastColumn()
col_num = 1
Do While Cells(2, col_num).Interior.Pattern <> xlNone
col_num = col_num + 1
Loop
MsgBox col_num
End Sub
It will return column F
EDIT....
As I said earlier you cannot get the cell you require by any built-in function, you have to write some code, and in order to do that you must have a definite logic that should be known and decided between you and the users of the sheet.
For example:
you can color the range as you have already done
You can name the column header, as in your example, it is status.
You can fix the number of data columns and status columns, and there will be no need to use any code
For finding the status column or any other if you decide you can use a loop as below
Sub getStatusColumn()
col_num = 1
Do While Cells(1, col_num) <> "status"
col_num = col_num + 1
Loop
MsgBox col_num
End Sub
OR
Sub getLastItemColumn()
col_num = 1
Do While Left(Cells(1, col_num), 4) = "Item"
col_num = col_num + 1
Loop
MsgBox col_num
End Sub
All of the below methods have failed to reference the last column. What is a viable method?
For example 'Columns("1:" & llc.Address & "").Select through 'Columns("E:" & llc & "").Selectare trying to select sayColumns("E:N")`. But the last column is dynamic. In one instance it's column N, and in another application of the macro it's column AP.
Sub RestorePivtTable()
Set ws = ThisWorkbook.Sheets("sheet1")
llc = ws.Cells(2, ws.Columns.count).End(xlToLeft).Column
'Columns("1:" & llc.Address & "").Select
'Columns(1, llc).Select
'Range(Columns(1), Columns(llc)).Select
'Columns("E:" & Cells(3, llc).Address & "").Select
'Range("1:" & Cells(3, lc).Address & "").Select
'Range(Cells(1, 1).Address, Cells(3, llc).Address).Select
'Columns("E:" & llc & "").Select
Selection.ClearFormats
End Sub
If you are using your above method you will need to find the correct row to use. ie: you will need to know the row in which the data appears in the right-most column. If you want the last column out of anything, try:
ws.usedrange.columns
This just gives the number of columns in the used range of a sheet, which is defined as A1:[The bottom right cell which contains either values or formatting].
Note that this will not work if, say, you have formatting in E10, but you want to get column D, because D is the last column which has a value [ie: you want to exclude consideration of formatted cells].
I generally use this method, although you have to put checks in in case the sheet is empty (you can't return column 0).
Sub FindLastColumn()
Dim wrkSht As Worksheet
Set wrkSht = ThisWorkbook.Worksheets("Sheet1")
MsgBox wrkSht.Cells.Find(What:="*", After:=wrkSht.Cells(1, 1), SearchDirection:=xlPrevious).Column
End Sub
Basic example of how to find the last column in your sheet - I've included an If block in case the sheet is empty, but then I don't know why you would run this code on an empty sheet anyway...
Sub SO()
Dim lastCol As Integer
Dim lastCell As Excel.Range
'// Assuming the variable 'ws' has already been dimensioned and initialised
On Error Resume Next
Set lastCell = ws.Cells.Find(What:="*", After:=ws.Range("A1"), SearchDirection:=xlPrevious)
On Error GoTo 0
If lastCell Is Nothing Then
lastCol = 1
Else
lastCol = lastCell.Column
End If
MsgBox lastCol
End Sub
UsedRange can be unreliable in this instance, because it can still contain cells that were previously used but are now blank - and I'm guessing you're not interested in these cells. Using the Cells.Find() method means that you don't have to know which row will coincide with the last column (which is needed for Columns.Count.End(xlToLeft) method) so this is a bonus too when working with irregular data sets.
Trying to "read between the lines" of your code, I suspect that this is what you are after:
Public Sub RestorePivtTable()
Sheet1.Cells(2, Sheet1.Columns.Count).End(xlToLeft).EntireColumn.ClearFormats
End Sub
This will work as long as there are data in row 2.
Thanks everyone for your help. The below function and macro solved the issue of converting a column number reference into a letter reference:
Function GetColumnLetter(colNum As Long) As String
Dim vArr
vArr = Split(Cells(1, colNum).Address(True, False), "$")
GetColumnLetter = vArr(0)
End Function
Sub RestorePivtTable2()
Dim lc As Long
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("PivtTable")
lc = ws.Cells(5, ws.Columns.count).End(xlToLeft).Column
myCol = GetColumnLetter(lc)
Columns("E:" & myCol & "").Select
Selection.ClearFormats
End Sub
I have an excel file which looks like this:
row1_cell1 row1_cell2 row1_cell3
row2_cell1 row2_cell2 row2_cell3
row3_cell1 row3_cell2 row3_cell3
How can i make three (or any number of) copies of each row that i have in the sheet, which i would like to be added after the row being copied? So, in the end i would like to have this kind of a result:
row1_cell1 row1_cell2 row1_cell3
row1_cell1 row1_cell2 row1_cell3
row1_cell1 row1_cell2 row1_cell3
row2_cell1 row2_cell2 row2_cell3
row2_cell1 row2_cell2 row2_cell3
row2_cell1 row2_cell2 row2_cell3
row3_cell1 row3_cell2 row3_cell3
row3_cell1 row3_cell2 row3_cell3
row3_cell1 row3_cell2 row3_cell3
This is how I would do that for all rows on the sheet:
Option Explicit
Sub MultiplyRows()
Dim RwsCnt As Long, LR As Long, InsRw As Long
RwsCnt = Application.InputBox("How many copies of each row should be inserted?", "Insert Count", 2, Type:=1)
If RwsCnt = 0 Then Exit Sub
LR = Range("A" & Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
For InsRw = LR To 1 Step -1
Rows(InsRw).Copy
Rows(InsRw + 1).Resize(RwsCnt).Insert xlShiftDown
Next InsRw
Application.ScreenUpdating = True
End Sub
There isn't a direct way to paste them interleaved like what you wanted. However, you can create a temporary VBA to do what you want.
For example, you can:-
Create a VBA procedure (like the one below) in your Excel file.
Assign a keyboard shortcut (eg. Ctrl+Q) to it.
To do this, press Alt+F8, then select the macro, then click 'Options'.
Select the cells you want to copy, then press Ctrl+C.
Select the cell you want to paste in, then press Ctrl+Q (or whatever keyboard shortcut you chose).
Enter the number of times you want to copy. (In your example, it would be 3.)
WHAMMO! :D
Now you can delete the VBA procedure. :)
VBA Code:
Sub PasteAsInterleave()
Dim startCell As Range
Dim endCell As Range
Dim firstRow As Range
Dim pasteCount As Long
Dim rowCount As Long
Dim colCount As Long
Dim i As Long
Dim j As Long
Dim inputValue As String
If Application.CutCopyMode = False Then Exit Sub
'Get number of times to copy.
inputValue = InputBox("Enter number of times to paste interleaved:", _
"Paste Interleave", "")
If inputValue = "" Then Exit Sub 'Cancelled by user.
On Error GoTo Error
pasteCount = CInt(inputValue)
If pasteCount <= 0 Then Exit Sub
On Error GoTo 0
'Paste first set.
ActiveSheet.Paste
If pasteCount = 1 Then Exit Sub
'Get pasted data information.
Set startCell = Selection.Cells(1)
Set endCell = Selection.Cells(Selection.Cells.count)
rowCount = endCell.Row - startCell.Row + 1
colCount = endCell.Column - startCell.Column + 1
Set firstRow = Range(startCell, startCell.Offset(0, colCount - 1))
'Paste everything else while rearranging rows.
For i = rowCount To 1 Step -1
firstRow.Offset(i - 1, 0).Copy
For j = 1 To pasteCount
startCell.Offset(pasteCount * i - j, 0).PasteSpecial
Next j
Next i
'Select the pasted cells.
Application.CutCopyMode = False
Range(startCell, startCell.Offset(rowCount * pasteCount - 1, colCount - 1)).Select
Exit Sub
Error:
MsgBox "Invalid number."
End Sub
Old thread, however someone might find this useful:
The below information was copied from here
I needed to do almost the opposite. I needed the formula to increment by 1 every 22 rows, leaving the 21 rows between blank. I used a modification of the formula above and it worked great. Here is what I used:
=IFERROR(INDIRECT("J"&((ROW()-1)*1/22)+1),"")
The information was in column "J".
The "IFERROR" portion handles the error received when the resulting row calculation is not an integer and puts a blank in that cell.
Hope someone finds this useful. I have been looking for this solution for a while, but today I really needed it.
Thanks.
I have a quandary, and I don't know if it will work better using excel VBA or not. Thinking about it I believe VBA will work best, but I don't know how to make it work.
I have two pages in a workbook, one is the form, the other is the database, I want the pulldown menu from the form to populate the rest of the form. It does... what I want then is to be able to change the value of the form press submit, and the new data will overwrite the old data.
Is this possible?
Here is the link to the sheet I'm talking about.
http://dl.dropbox.com/u/3327208/Excel/Change.xlsx
Here is the script I am working with now...it takes the sheet, copies everything to a row takes that row, moves it to the NCMR Data tab and then clears the data on the new row from the original sheet.
This code technically could work, but what I need to do is make it use the same concept, but instead of creating a new row at the end of the sheet find the original line and replace the data from B to U in whatever row it was originally in.
I know it's possible, I just don't know how.
'Copy Ranges Variable
Dim c As Variant
'Paste Ranges Variable
Dim p As Range
'Setting Sheet
Set wsInt = Sheets("Form")
Set wsNDA = Sheets("Data")
Set p = wsInt.Range("A14")
With wsInt
c = Array(.Range("B11"))
End With
For i = LBound(c) To UBound(c)
p(i + 1).Value = c(i).Value
Next
With wsNDA
Dim Lastrow As Long
Lastrow = .Range("B" & Rows.Count).End(xlUp).Row + 1
wsInt.Rows("14").Copy
With .Rows(Lastrow)
.PasteSpecial Paste:=xlPasteFormats
.PasteSpecial Paste:=xlPasteValues
.Interior.Pattern = xlNone
End With
With .Range("A" & Lastrow)
If Lastrow = 3 Then
.Value = 1
Else
.Value = Val(wsNDA.Range("A" & Lastrow - 1).Value) + 1
End If
.NumberFormat = "0#######"
End With
End With
End Sub
I found this code:
Sub CopyTest()
Dim selrow As Range, rngToCopy As Range
With Worksheets("PD DB")
Set selrow = .Range("B:B").Find(.Range("BA1").Value)
'find the cell containing the value
Set rngToCopy = Union(selrow.Offset(0, 9), selrow.Offset(0, 12))
'use offset to define the ranges to be copied
rngToCopy.Copy Destination:=Worksheets("Edit Sheet").Range("B50")
'copy and paste (without Select)
End With
End Sub
As far as I can tell this will do what I want mostly, but I can't seem to figure out where to break it up to add it where I need to to make it work the way I want it to.
What I can tell is this, it will copy and paste, but I want to make sure it will paste the data into row it finds, and not overwrite the number of said row.
Can someone help make that possible with the two scripts I have here?
Not tested, but should get you started. I added a 3rd sheet (shtMap) to hold the mmapping between the cell addresses on your form and the column numbers on the "Data" sheet. Useful to name your sheets directly in the VB editor: select the sheet and set the name in the property grid.
*EDIT:*If you want to trigger the transfer on selecting a record id from a list in Range AG3 then place this code in the code module for that worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Static bProcessing As Boolean
Dim rng As Range
If bProcessing Then Exit Sub
Set rng = Target.Cells(1)
If Not Application.Intersect(rng, Me.Range("AG3")) Is Nothing Then
bProcessing = True
'this is where you call your macro to transfer the record
bProcessing = False
End If
End Sub
You could use something like this for the transfer:
Public Enum XferDirection
ToForm = 1
ToDataSheet = 2
End Enum
Sub FetchRecord()
TransferData XferDirection.ToForm
End Sub
Sub SaveRecord()
TransferData XferDirection.ToDataSheet
End Sub
Sub TransferData(Direction As XferDirection)
Dim rngMap As Range, rw As Range, f As Range, dataCell As Range
Dim formCell As Range, dataCol As Long, dataRow As Long
Dim sId As String
sId = shtForm.Range("AG3").Value
Set f = shtData.Columns(1).Find(sId, LookIn:=xlValues, lookat:=xlWhole)
If Not f Is Nothing Then
dataRow = f.Row
Else
'what do you want to do here?
' record doesn't exist on data sheet
MsgBox "Record '" & sId & "' not found on '" & shtForm.Name & "' !"
Exit Sub
End If
Set rngMap = shtMap.Range("A2:B10")
For Each rw In rngMap.Rows
'the cell on the edit form
Set formCell = shtForm.Range(rw.Cells(1).Value)
'column # on datasheet
Set dataCell = shtData.Cells(dataRow, rw.Cells(2).Value)
If Direction = XferDirection.ToDataSheet Then
dataCell.Value = formCell.Value
Else
formCell.Value = dataCell.Value
End If
Next rw
End Sub
Matt, there are two approaches I would take. The first is use find(), which returns a range object, then append ".row" so that you'll be able to modify the row on Sheet2 (wsNDA, I think). You may want to test that find() doesn't return Nothing.
Dim foundRow as Long
Dim foundRng as Range
set foundRng = wsNDA.find(wsInt.Range("B11").Value, ...)
If Not foundRng is Nothing Then
foundRow = foundRng.row
End If
'method without check: foundRow = wsNDA.find(wsInt.Range("B11").Value, ...).Row
The other is to use a Dictionary object. I'm not sure what you'd want for the key, but the item could be the row on the data sheet. When you make the change to what's on the form, check against the key and grab its item (the corresponding row) to determine where you need to replace the values.