copy data into next available blank cell excel vba - vba

the code below works fine apart from one thing, the names are copied to the second sheet in the same position as they are in in the first sheet so I end up with this.
As you can see there are loads of blanks, what I need it to end up like is,
This
There are three parts to the code as you can see
1 gather names and status
2 test the availability of the person and write their name to the second sheet if they are available
3 clear out the blanks
Is there any way I can amend the line;
Activecell.offset to place the name in the next available cell in each column as it cycles through?
I can’t use the “clear the blanks” as it screws up all the buttons positions in the second sheet
Code
Option Explicit
Sub Copy_all_available_names_to_sorted_sidesmen_50()
'record all the names and availability into a single array
Dim AllData() As Variant
Dim Name As Long, Status As Long
Dim Storedname As String
Dim Storedstatus As String
Dim nameindex As Long
Sheets("Everyones Availability").Select
Name = Range("A3", Range("A3").End(xlDown)).Count - 1
Status = Range("a3", Range("a3").End(xlToRight)).Count - 1
ReDim AllData(0 To Name, 0 To Status)
For Name = LBound(AllData, 1) To UBound(AllData, 1)
For Status = LBound(AllData, 2) To UBound(AllData, 2)
AllData(Name, Status) = Range("A3").Offset(Name, Status).Value
Next Status
Next Name
Sheets("Sorted sidesmen").Select
Range("A3").Select
For Name = LBound(AllData, 1) To UBound(AllData, 1)
For Status = LBound(AllData, 2) To UBound(AllData, 2)
Storedname = AllData(Name, 0)
Storedstatus = AllData(Name, Status)
If Storedstatus = "Available" Then
ActiveCell.Offset(1, 0)(Name, Status).Value = Storedname
End If
Next Status
Next Name
Dim rng As Range
On Error GoTo NoBlanksFound
Set rng = Range("a3:z46").SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
rng.Rows.Delete shift:=xlShiftUp
NoBlanksFound:
MsgBox "All Blanks have been removed"
End Sub
Thank you for looking and help you may be able to give

This should work
Option Explicit
Public Sub CopyAllAvailableNamesToSortedSidesmen50()
Dim wsEA As Worksheet: Set wsEA = ThisWorkbook.Worksheets("Everyones Availability")
Dim wsSS As Worksheet: Set wsSS = ThisWorkbook.Worksheets("Sorted sidesmen")
Dim topEAcel As Range: Set topEAcel = wsEA.Cells(3, "A")
Dim topSScel As Range: Set topSScel = wsSS.Cells(3, "A")
Dim lrEA As Long: lrEA = wsEA.Cells(wsEA.Rows.Count, "A").End(xlUp).Row
Dim lcEA As Long: lcEA = wsEA.Cells(1, wsEA.Columns.Count).End(xlToLeft).Column
wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA)).ClearContents 'clear Sorted sidesmen
Dim arrEA As Variant: arrEA = wsEA.Range(topEAcel, wsEA.Cells(lrEA, lcEA))
Dim arrSS As Variant: arrSS = wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA))
Dim rEA As Long, cEA As Long, rSS As Long
For cEA = 2 To lcEA 'by columns
rSS = 1
For rEA = 1 To lrEA - 2 'by rows
If arrEA(rEA, cEA) = "Available" Then
arrSS(rSS, cEA) = arrEA(rEA, 1) 'copy available names
rSS = rSS + 1
End If
Next
Next
wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA)).Value2 = arrSS 'paste in wsSS
End Sub
Sheet1 ("Everyones Availability")
Sheet2 ("Sorted sidesmen")
Key items in code:
Last Row on "Everyones Availability": lrEA
Last Col on "Everyones Availability": lcEA
lrEA = wsEA.Cells(wsEA.Rows.Count, "A").End(xlUp).Row
lcEA = wsEA.Cells(1, wsEA.Columns.Count).End(xlToLeft).Column
Note: initial methods (xlDown, and xlToRight) were causing issues with empty cells
- All data on "Everyones Availability": arrEA = Variant Array (copy from)
- All data on "Sorted Sidesmen": arrSS = Variant Array (copy to; empty before copy)
arrEA = wsEA.Range(topEAcel, wsEA.Cells(lrEA, lcEA))
arrSS = wsSS.Range(topSScel, wsSS.Cells(lrEA, lcEA)) 'Same size as arrEA
If arrEA(rEA, cEA) = "Available" Then
arrSS(rSS, cEA) = arrEA(rEA, 1) 'copy names
rSS = rSS + 1 'separate row counter for "Sorted sidesmen", increment only if "Available"
End If

Could you simply sort the output in the final sheet?
Option Explicit
Public Sub Ordering()
Dim col As Range, lastRow As Long
With ThisWorkbook.Worksheets("Sheet1") 'change as appropriate
lastRow = .UsedRange.SpecialCells(xlLastCell).Row
For Each col In Intersect(Range("A:D"), .UsedRange).Columns
.Range(.Cells(3, col.Column), .Cells(lastRow, col.Column)).Sort Key1:=.Range(.Cells(3, col.Column), .Cells(lastRow, col.Column)), Order1:=xlAscending, Header:=xlNo ' 'Sort to ensure in order
Next col
End With
End Sub
Before:
After:

This code should do what you need:
Assuming your source sheet is called "Everyones Availability" and new sheet "Sorted sidesmen"
Sub copy_to_newsheet()
Dim i, j, lr, lc, newlr, newlc As Long
Sheets("Sorted sidesmen").Cells.ClearContents
lr = Sheets("Everyones Availability").Range("A10000").End(xlUp).Row '' your last row
lc = Sheets("Everyones Availability").Range("A1").End(xlToRight).Column '' your last column
Sheets("Everyones Availability").Range(Cells(1, 1), Cells(2, lc)).Copy
Sheets("Sorted sidesmen").Cells(1, 1).PasteSpecial Paste:=xlPasteValues
For j = 1 To lc
For i = 3 To lr
Sheets("Sorted sidesmen").Select
Cells(1, j).Select
newlr = Selection.End(xlDown).Row '' your new last row
newlc = Selection.End(xlToRight).Column '' your new last column
If Sheets("Everyones Availability").Cells(i, j).Value = "" Then GoTo thenexti
Sheets("Everyones Availability").Cells(i, j).Copy
Sheets("Sorted sidesmen").Cells(newlr + 1, j).PasteSpecial Paste:=xlPasteValues
thenexti:
Next
Next
End Sub

Related

Reverse a selection of data from a specific column if value in a cell meets a criteria

I have a form where users enter the name of a project and the type of transaction.
I have written a macro that returns a selection of data from a table based on the name of the project the user entered, and it works perfectly.
Now I need to add in a function that reverses the order of that same list if the user enters a specific transaction type, it reverses the order of the same list of data.
For example, if type A returns:
Bob
Jerry
Andrew
Jeff
Then type B would reverse that order and return:
Jeff
Andrew
Jerry
Bob
The VBA I wrote for the first portion, to return the list based on project name is:
Sub finddata()
Dim projectName As String
Dim transactionType As String
Dim finalRow As Integer
Dim i As Integer
Sheets("Template_Test").Range("G10:I38").ClearContents
projectName = Sheets("Template_Test").Range("E10").Value
finalRow = Sheets("Project_Structure").Range("A20000").End(xlUp).Row
transactionType = Sheets("Template_Test").Range("E14").Value
For i = 2 To finalRow
Sheets("Project_Structure").Activate
If Cells(i, 1) = projectName Then
Sheets("Project_Structure").Range(Cells(i, 2), Cells(i, 4)).Copy
Sheets("Template_Test").Activate
Sheets("Template_Test").Range("G100").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
Sheets("Template_Test").Range("E10").Select
End Sub
I can get the selection to reverse order using the built in vba function strReverse and a specific range, but my data is not a consistent length of cells - sometimes it's 6 names and sometimes it's 15 - and I can't figure out how to get it to adjust the length it needs to reverse without including blank cells underneath the range.
Here is a method using the .Reverse method of ArrayList object
Option Explicit
Public Sub ReverseAList()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set aList = CreateObject("System.Collections.ArrayList")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
For i = LBound(arr, 1) To UBound(arr, 1)
aList.Add arr(i, 1)
Next i
aList.Reverse
For i = 0 To aList.Count - 1
arr(i + 1, 1) = aList(i)
Next
.Cells(2, 2).Resize(aList.Count, 1) = arr
End With
End Sub
Data and output
Same thing re-writing a sub by Ryan Wells as a function:
Public Sub ReverseAList2()
Dim ws As Worksheet, arr(), i As Long, aList As Object, lastRow As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
With ws
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row 'whichever column is required to determine last row. Assumes there are headers in row1
If lastRow = 2 Then arr(0) = .Range("A2").Value
arr = .Range("A2:A" & lastRow).Value
.Cells(2, 2).Resize(UBound(arr), 1) = ReverseArray(arr)
End With
End Sub
Public Function ReverseArray(vArray As Variant) As Variant
Dim vTemp As Variant, i As Long, iUpper As Long, iMidPt As Long
iUpper = UBound(vArray, 1)
iMidPt = (UBound(vArray, 1) - LBound(vArray, 1)) \ 2 + LBound(vArray)
For i = LBound(vArray) To iMidPt
vTemp = vArray(iUpper, 1)
vArray(iUpper, 1) = vArray(i, 1)
vArray(i, 1) = vTemp
iUpper = iUpper - 1
Next i
ReverseArray = vArray
End Function

how to match 2 criteria in macro

I currently have the following codes that look up the column for Columbus. But how do I specify that I only want to look up the column for Columbus in Ohio by also referring to row 4 (State)?
Amount = WorksheetFunction.Match("Columbus", Rows("5:5"), 0)
Try Looping thru all the records -
Dim Amount As Variant
Dim lngRow as long
lngRow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To lngRow 'Considering row 1 has headers
If ActiveSheet.Cells(i, 5) = "Columbus" And ActiveSheet.Cells(i, 4) = "Ohio" Then
Amount = i
Exit For
End If
Next i
Thanks
Use Variant Arrays and cycle through that it will be quicker:
With Worksheets("Sheet1") 'Change to your sheet
Dim rngArr() As Variant
rngArr = .Range(.Cells(4, 1), .Cells(5, .Columns.Count).End(xlToLeft)).Value
Dim i As Long
For i = 1 To UBound(rngArr, 2)
If rngArr(1, i) = "Ohio" And rngArr(2, i) = "Columbus" Then Exit For
Next i
If i <= UBound(rngArr, 2) Then
Dim Amount As Long
Amount = i
Else
MsgBox "Not Found"
End If
End With

Excel VBA cell upper/lower case depending other cell

I'm writing a code to loop through an excel sheet and changing the text (in column B) to uppercase/lowercase, depending on the value of cell in column N on the same row.
Macros purpose:
loop through cells in column B starting at row 2 and changing the string from upper to lowercase or vice versa, depending on the value of the cell in column N (lowercase if value = 5, other cases text should be uppercase)
Code I've got so far:
Sub CAPS()
'
' CAPS Macro
'
Dim Rang As Integer
Dim j As Integer
j = 2
For Each N In Source.Range("N2:N10000") ' Do 10000 rows
Rang = Cells(j, 14)
If Rang = 5 Then
Cells(j, 2).Range("A1").Select
ActiveCell.Value = LCase$(ActiveCell.Text)
Else
ActiveCell.Value = UCase$(ActiveCell.Text)
j = j + 1
End If
Next N
End Sub
I'm a little bit stuck in the looping part, not really a clue how to fix the error(s) in the current code.
Thanks in advance :)
Sub CAPS()
'
' CAPS Macro
'
Dim N as long 'use long here as integer is limite to a 32b character
For N Is 2 to 10000 ' Do 10000 rows
If Cells(N, 14) = 5 Then
Cells(N, 2) = LCase(Cells(N,2)
Else
Cells(N, 2) = UCase(Cells(N,2)
EndIf
Next N
End Sub
This should do the trick, untested though.
You currently have a fixed number of rows you want to test. To optimize your code you could first check how many rows are filled with data. To do so you can use:
DIM lastrow as long
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
And then make the loop with For N Is 2 to lastrow
Also it is good practice to explicitly reference your worksheets, as this prevents undesired results. For example you click on another worksheet whilst the code is running it will continue formatting on that sheet. To do so declare a variable as your worksheet:
DIM ws as worksheet
And set a value to your variable, in this case Sheet1.
Set ws as ThisWorkbook.Worksheets("Sheet1")
Now every time you reference a Cells(), you explicitly say on what sheet that has to be by adding ws. in front of it like such: ws.Cells()
To summarize all that into your code:
Sub CAPS()
'
' CAPS Macro
'
Dim N as long 'use long here as integer is limite to a 32b character
Dim lastrow as long
Dim ws as worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1") 'Set the code to run on Sheet 1 of your current workbook.
lastrow = ws.Cells(Rows.Count, "B").End(xlUp).Row
For N Is 2 to lastrow ' Do all rows that have data in column B
If ws.Cells(N, 14) = 5 Then
ws.Cells(N, 2) = LCase(ws.Cells(N,2)
Else
ws.Cells(N, 2) = UCase(ws.Cells(N,2)
EndIf
Next N
End Sub
Try processing in an array,
Sub CAPS()
'
' CAPS Macro
'
Dim arr As variant, j As Integer
with worksheets("sheet1")
arr = .range(.cells(2, "B"), .cells(.rows.count, "B").end(xlup).offset(0, 12)).value2
for j= lbound(arr, 1) to ubound(arr, 1)
if arr(j, 13) = 5 then
arr(j, 1) = lcase(arr(j, 1))
else
arr(j, 1) = ucase(arr(j, 1))
end if
next j
redim preserve arr(lbound(arr, 1) to ubound(arr, 1), 1 to 1)
.cells(2, "B").resize(ubound(arr, 1), ubound(arr, 2)) = arr
end with
End Sub
You may try something like this...
Sub CAPS()
Dim ws As Worksheet
Dim lr As Long, i As Long
Application.ScreenUpdating = False
Set ws = Sheets("Sheet1") 'Sheet where you have to change the letter case
lr = ws.Cells(Rows.Count, "B").End(xlUp).Row
For i = 2 To lr
Select Case ws.Cells(i, "N")
Case 5
ws.Cells(i, "B") = LCase(ws.Cells(i, "B"))
Case Else
ws.Cells(i, "B") = UCase(ws.Cells(i, "B"))
End Select
Next i
Application.ScreenUpdating = True
End Sub
Another approach using for each loop with Range:
Sub UCaseLCase()
Dim rng, cell As Range
Dim Test As Integer
Test = 5
Set rng = Range(Cells(2, 14), Cells(10000, 14))
For Each cell In rng.Cells
If cell.Value = Test Then
cell.Offset(0, -12) = LCase(cell.Offset(0, -12))
Else
cell.Offset(0, -12) = UCase(cell.Offset(0, -12))
End If
Next cell
End Sub
I know you said in your question starting at row 2 but it's easier just going from last row until row 2.
Hope this can help or at least, learn something new about Loops :)
Sub CAPS()
Dim j As Integer
For j = Range("B2").End(xlDown).Row To 2 Step -1
If Range("N" & j).Value = 5 Then
'uppercase
Range("B" & j).Value = UCase(Range("B" & j).Value)
Else
'lowercase
Range("B" & j).Value = LCase(Range("B" & j).Value)
End If
Next j
End Sub

Copy a range into a single column - values only

Hello I am trying to copy a range into a single column. The range is a mix of blank cells and cells with values.I only want to copy and paste the cells with values and I would it to find the first blank cell and want it to walk itself down the column from there.
The code I have right now (besides taking forever) pastes in the first row.
Dim i As Integer
i = 1
ThisWorkbook.Worksheets("amount date").Select
For Row = 51 To 100
For col = 2 To 1000
If Cells(Row, col).Value <> "" Then
Cells(Row, col).Copy
Worksheets("sheet 2").Range("G" & i).PasteSpecial xlPasteValues
End If
Next
Next
Do While Worksheets("sheet 2").Range("G" & i).Value <> ""
i = i + 1
Loop
End Sub
This will work:
Sub qwerty()
Dim i As Long, r As Long, c As Long
i = 1
ThisWorkbook.Worksheets("amount date").Select
For r = 51 To 100
For c = 2 To 1000
If Cells(r, c).Value <> "" Then
Cells(r, c).Copy
Worksheets("sheet 2").Range("G" & i).PasteSpecial xlPasteValues
i = i + 1
End If
Next
Next
End Sub
Perhaps this will be a little faster (even though it seems to have been slow arriving).
Sub CopyRangeToSingleColumn()
' 20 Oct 2017
Dim LastRow As Long
Dim LastClm As Long
Dim Rng As Range, Cell As Range
Dim CellVal As Variant
Dim Spike(), i As Long
With ThisWorkbook.Worksheets("amount date")
With .UsedRange.Cells(.UsedRange.Cells.Count)
LastRow = Application.Max(Application.Min(.Row, 100), 51)
LastClm = .Column
End With
Set Rng = .Range(.Cells(51, "A"), .Cells(LastRow, LastClm))
End With
ReDim Spike(Rng.Cells.Count)
For Each Cell In Rng
CellVal = Trim(Cell.Value) ' try to access the sheet less often
If CellVal <> "" Then
Spike(i) = CellVal
i = i + 1
End If
Next Cell
If i Then
ReDim Preserve Spike(i)
With Worksheets("sheet 2")
LastRow = Application.Max(.Cells(.Rows.Count, "G").End(xlUp).Row, 2)
.Cells(LastRow, "G").Resize(UBound(Spike)).Value = Application.Transpose(Spike)
End With
End If
End Sub
The above code was modified to append the result to column G instead of over-writing existing cell values.
Do you need copy the whole row into one cell, row by row? For each loop shall be faster. I guess, this should work
Sub RowToCell()
Dim rng As Range
Dim rRow As Range
Dim rRowNB As Range
Dim cl As Range
Dim sVal As String
Set rng = Worksheets("Sheet3").Range("$B$51:$ALN$100") 'check this range
For Each rRow In rng.Rows
On Error Resume Next
Set rRowNB = rRow.SpecialCells(xlCellTypeConstants)
Set rRowNB = Union(rRow.SpecialCells(xlCellTypeFormulas), rRow)
On Error GoTo 0
For Each cl In rRowNB.Cells
sVal = sVal & cl.Value
Next cl
Worksheets("sheet4").Range("G" & rRow.Row - 50).Value = sVal
sVal = ""
Next rRow
End Sub
its quick for this range.

how to insert a row before pasting an array

I currently have an array which I populate and paste in a sheet named "T1" using a macro. My current macro uses the rowcount function to determine the used rows and pastes the array from the next available row.
The problem I am having is that when I paste this array multiple times, the arrays need to be spaced by a row so that i can differentiate different submissions. This is what I have so far, and I was hoping someone could help me with this:
Sub CopyData()
Dim Truearray() As String
Dim cell As Excel.Range
Dim RowCount1 As Integer
Dim i As Integer
Dim ii As Integer
Dim col As Range
Dim col2 As Range
i = 0
ii = 2
RowCount1 = DHRSheet.UsedRange.Rows.Count
Set col = DHRSheet.Range("I1:I" & RowCount1)
For Each cell In col
If cell.Value = "True" Then
Dim ValueCell As Range
Set ValueCell = Cells(cell.Row, 3)
ReDim Preserve Truearray(i)
Truearray(i) = ValueCell.Value
Dim siblingCell As Range
Set siblingCell = Cells(cell.Row, 2)
Dim Siblingarray() As String
ReDim Preserve Siblingarray(i)
Siblingarray(i) = DHRSheet.Name & "$" & siblingCell.Value
i = i + 1
End If
Next
Dim RowCount2 As Integer
RowCount2 = DataSheet.UsedRange.Rows.Count + 1
For ii = 2 To UBound(Truearray)
DataSheet.Cells(RowCount2 + ii, 2).Value = Truearray(ii)
Next
For ii = 2 To UBound(Siblingarray)
DataSheet.Cells(RowCount2 + ii, 1).Value = Siblingarray(ii)
Next
DataSheet.Columns("A:B").AutoFit
MsgBox ("Data entered has been successfully validated & logged")
End Sub
If you Offset two rows from the bottom cell, you will leave a blank row of separation. You should also consider filling the whole array as base 1 and writing it to DataSheet in one shot.
Sub CopyData2()
Dim rCell As Range
Dim aTrues() As Variant
Dim rRng As Range
Dim lCnt As Long
'Define the range to search
With DHRSheet
Set rRng = .Range(.Cells(1, 9), .Cells(.Rows.Count, 9).End(xlUp))
End With
'resize array to hold all the 'trues'
ReDim aTrues(1 To Application.WorksheetFunction.CountIf(rRng, "True"), 1 To 2)
For Each rCell In rRng.Cells
If rCell.Value = "True" Then
lCnt = lCnt + 1
'store the string from column 2
aTrues(lCnt, 1) = DHRSheet.Name & "$" & rCell.Offset(0, -7).Value
'store the value from column 3
aTrues(lCnt, 2) = rCell.Offset(0, -6).Value
End If
Next rCell
'offset 2 from the bottom row to leave a row of separation
With DataSheet.Cells(DataSheet.Rows.Count, 1).End(xlUp).Offset(2, 0)
'write the stored information at one time
.Resize(UBound(aTrues, 1), UBound(aTrues, 2)).Value = aTrues
End With
End Sub