Merge range of cells offset to target - excel

I have a worksheet where Appt Note text is very lengthy. I need to place it in a row of nine merged cells.
I'm trying to check all the cells in column A for the value "Appt Note:" then merge the nine cells to the right of it so all my data shows up in a viewable format.
I checked lots of posts online but can't put my particular code together. I've built it, with the exception of the merge.
Sub MergeTest()
Dim cel As Range
Dim WS As Worksheet
For Each WS In Worksheets
For Each cel In WS.Range("$A1:$A15")
If InStr(1, cel.Value, "Appt Note:") > 0 Then Range(cel.Offset(1, 9)).Merge
Next
Next
End Sub

As per my comment, hereby a sample of Range.Find where in this case I assume "Appt Note:" only exists once per sheet:
Sub Test()
Dim ws As Worksheet
Dim cl As Range
For Each ws In ThisWorkbook.Worksheets
Set cl = ws.Range("A:A").Find(What:="Appt Note:", Lookat:=xlPart)
If Not cl Is Nothing Then
cl.Offset(0, 1).Resize(1, 9).Merge
End If
Next
End Sub
Note: Merged cells are VBA's worst nightmare! Try to stay away from them. Maybe you can let the text just overflow?
Edit: In case your value could exist multiple times, use Range.FindNext:
Sub Test()
Dim ws As Worksheet
Dim cl As Range
Dim rw As Long
For Each ws In ThisWorkbook.Worksheets
Set cl = ws.Range("A:A").Find(What:="Appt Note:", Lookat:=xlPart)
If Not cl Is Nothing Then
rw = cl.Row
Do
cl.Offset(0, 1).Resize(1, 9).Merge
Set cl = ws.Range("A:A").FindNext(cl)
If cl Is Nothing Then
GoTo DoneFinding
End If
Loop While cl.Row <> rw
End If
DoneFinding:
Next
End Sub

Sub MergeTest()
Dim ws As Worksheet, cell As Range
For Each ws In ThisWorkbook.Worksheets
For Each cell In ws.Range("A1:A15")
If cell.Value Like "Appt Note:*" Then cell.Resize(1, 9).Merge
Next
Next
End Sub
ThisWorkbook refers to the workbook where the VBA code is, to avoid issues when a different workbook is active. The Like operator can be used to check if the cell value matches a wildcard pattern.
cell.Resize(1, 9) can be used to get a new range starting from cell and resized to 9 columns.

I found code that will do what I need. See below. I've tested it and it works. It will start at the bottom of my spreadsheet and find the last row with data and work it's way up until it reaches my first row.
Thanks so much for all your help! If you have any suggestions, advice, warnings, etc regarding the code below, please share. As I said, I am completely new to VB and know just enough to be dangerous. So I can use all the help I can get. :)
Sub mergeCellsBasedOnCriteria()
Dim myFirstRow As Long
Dim myLastRow As Long
Dim myCriteriaColumn As Long
Dim myFirstColumn As Long
Dim myLastColumn As Long
Dim myWorksheet As Worksheet
Dim myCriteria As String
Dim iCounter As Long
myFirstRow = 1
myCriteriaColumn = 1
myFirstColumn = 2
myLastColumn = 10
myCriteria = "Appt Note:"
Set myWorksheet = Worksheets("Sample")
With myWorksheet
myLastRow = .Cells.Find(What:="*", LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For iCounter = myLastRow To myFirstRow Step -1
If .Cells(iCounter, myCriteriaColumn).Value = myCriteria Then
.Range(.Cells(iCounter, myFirstColumn), .Cells(iCounter, myLastColumn)).Merge
.Range(.Cells(iCounter, myFirstColumn), .Cells(iCounter, myLastColumn)).WrapText = True
End If
Next iCounter
End With
End Sub

Related

Move a full row of data with formatting and formulas to a designated sheet with one click

I have been an Excel-user for decades by now, VBA has been "out there" but nothing I've spent much time on in the past. Only minor alterations on existing scripts etc.
However I wanted to increase my knowledge and after about a month of tutorials, googling and more googling I feel that I'm getting a slight grip on the case.
I have a large workbook with many products, including specs, pricing and assorted calculation. When a products expires I'd like to move it to a EOL-sheet so I keep a log of old products.
Currently, this script is as far as I have come. It should look at the selected rows, and move the content to sheet "EOL" and delete it from the original sheet, and skip all hidden rows.
It works well if I select one cell, however if I select more cells, it doesn't correctly iterate through the full range.
Sub MoveRows()
Call SpeedUp
Dim SourceSheet As Worksheet
Dim TargetSheet As Worksheet
Dim LastRow As Long
Dim rng As Range
Set rng = Selection
Set SouceSheet = ActiveSheet
Set TargetSheet = ActiveWorkbook.Sheets("EOL")
TargetRow = ActiveCell.row
LastRow = TargetSheet.Cells(TargetSheet.Rows.Count, "D").End(xlUp).row + 1
For Each row In rng.Rows
If row.Rows.Hidden Then
TargetRow = TargetRow + 1
Else
ActiveSheet.Rows(TargetRow).Copy
TargetSheet.Rows(LastRow).PasteSpecial xlPasteFormulasAndNumberFormats
TargetSheet.Rows(LastRow).PasteSpecial xlPasteFormats
Rows(TargetRow).EntireRow.Delete
LastRow = LastRow + 1
End If
Next row
Call SpeedDown
End Sub
*Note: the SpeedUp/SpeedDown function is to turn of screnupdating etc for efficiency. Doesn't affect the script itself. *
As I tested it commenting out the delete function, it copied the first cell repeatedly, obviously since TargetRow didn't change. When I added TargetRow = TargetRow + 1 after the End If it works flawlessly.
However, when I uncomment the delete part, it doesn't work as I would expect.
As TargetRow is deleted, then I would think that the next row would be the new TargetRow, but it seems like this doesn't happen.
I guess my problem is that there is no direct link between TargetRow and the iteration of rng.Rows, but how can I solve this?
Is there a way to store all the moved rows in a list and subsequently delete them through a new iteration ? Or maybe that is a bit too "python-thinking" for VBA .. ?
Appreciate all input on this probably fairly newbie question :)
You're use a For Each, but you hardly ever use row except for when you want to check if it's hidden. Why do you need TargetRow at all? Try:
For Each row In rng.Rows
If Not row.Rows.Hidden Then
row.Copy
TargetSheet.Rows(LastRow).PasteSpecial xlPasteFormulasAndNumberFormats
TargetSheet.Rows(LastRow).PasteSpecial xlPasteFormats
row.EntireRow.Delete
LastRow = LastRow + 1
End If
Next row
Move Visible Rows of the Selection
BTW, if you would have used Option Explicit, it would have warned you about the undeclared variable row and the typo in Set SouceSheet = ActiveSheet.
The Row property usually uses the capital letter R. In your code, there are occurrences of .row because you are using a variable named row. To make the case of the property capital again, declare Dim Row As Range. Then you could use another variable name instead of Row e.g. rrg (Row Range), srrg...
Option Explicit
Sub MoveRows()
If Selection Is Nothing Then Exit Sub ' no visible workbooks open
If Not TypeOf Selection Is Range Then Exit Sub ' not a range
Dim sws As Worksheet: Set sws = Selection.Worksheet
Dim srg As Range: Set srg = Intersect(Selection.EntireRow, sws.UsedRange)
If srg Is Nothing Then Exit Sub ' not in rows of the used range
Dim svrg As Range
On Error Resume Next
Set svrg = srg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If svrg Is Nothing Then Exit Sub ' no visible cells
Dim dws As Worksheet
On Error Resume Next
Set dws = sws.Parent.Sheets("EOL")
On Error GoTo 0
If dws Is Nothing Then Exit Sub ' worksheet 'EOL' doesn't exist
Dim dfcell As Range
With dws.UsedRange
Set dfcell = dws.Cells(.Row + .Rows.Count, "A")
End With
Application.ScreenUpdating = False
svrg.Copy
dfcell.PasteSpecial xlPasteFormulasAndNumberFormats
dfcell.PasteSpecial xlPasteFormats
svrg.Delete xlShiftUp
Application.ScreenUpdating = True
MsgBox "Rows moved.", vbInformation
End Sub
UPDATE *
So after lots of time spent I did finally get to a solution which does the trick.
Sub MoveRows()
Dim SourceSheet As Worksheet
Dim TargetSheet As Worksheet
Dim rng As Range
Dim TargetRow As Integer
Dim StartRow As Integer
Dim EndRow As Integer
Dim LastRow As Long
Set rng = Selection
Set SourceSheet = ActiveSheet
Set TargetSheet = ActiveWorkbook.Sheets("EOL")
LastRow = TargetSheet.Cells(TargetSheet.Rows.Count, "D").End(xlUp).row + 1
StartRow = rng.row
EndRow = rng.Rows.Count + StartRow - 1
For i = EndRow To StartRow Step -1
If Not Rows(i).Hidden Then
ActiveSheet.Rows(i).Copy
TargetSheet.Rows(LastRow).PasteSpecial xlPasteFormulasAndNumberFormats
TargetSheet.Rows(LastRow).PasteSpecial xlPasteFormats
Rows(i).EntireRow.Delete
LastRow = LastRow + 1
End If
Next i
Cells(EndRow, 1).Select
End Sub
Thanks to all for the help!

VBA search for string in a specific column in two sheets, and delete if found

hope someone can help me fix this code. Want to look for a string "StringName" in column F (the string will always be in column F). I have tried to make an array of the two sheets, and then loop through them and find the string i want to delete. If the string is found in one or both of the sheets, then the entire row should be deleted.
I want to do this with 4 more strings, and havent thought on how to do it yet. Would it be better to just find the strings i need to keep which is "hello" and "goodbye", and then say everything that doesn't match those two string, delete? Hope someone can help
Sub test1()
Dim sheetArray As Variant
Dim ws As Variant
Dim targetCell As Range
sheetArray = Array("Sheet1", "Sheet2")
For Each ws In sheetArray
With Worksheets(ws)
For Each targetCell In Range("F:F")
If InStr(targetCell, "StringName") Then
targetCell.EntireRow.delete
End If
Next targetCell
End With
Next ws
End Sub
Use If ... And ... or If ... Or ....
Example below, I went for just keeping 'hello' and 'goodbye' as you suggest because the code's shorter/simpler that way.
Sub answer1()
Dim sheetArray As Variant, ws As Worksheet, a As Long
sheetArray = Array(Worksheets("Sheet1"), Worksheets("Sheet2"))
For Each ws In sheetArray
With ws
For a = .UsedRange.Rows.Count + UsedRange.Row -1 to .UsedRange.Row step -1 'counting backwards/upwards because we're deleting rows, counting forward/down would end up skipping some rows.
If Not .Cells(a, 6) Like "*hello*" _
And Not .Cells(a, 6) Like "*goodbye*" Then 'the 6 refers to column F
.Rows(a).Delete
End If
Next
End With 'ws
Next ws
End Sub
I used autofilter and delete rows the visible rows. if there is no header, no need of offset. I used string doesn't match criteria in sheet1 and string match criteria in Sheet2 in the code
Sub autofilter_Remove()
Dim wb As Workbook
Dim ws1, ws2 As Worksheet
Dim Rng1, Rng2 As Range
Set wb = ThisWorkbook
Set ws1 = wb.Worksheets("Sheet1")
Set ws2 = wb.Worksheets("Sheet2")
Set Rng1 = ws1.Rows(1)
Set Rng2 = ws2.Rows(1)
Stringname = "hello"
'6 for F column
Rng1.AutoFilter Field:=6, Criteria1:="<>*" & Stringname & "*"
ws1.UsedRange.Offset(1, 0).SpecialCells _
(xlCellTypeVisible).EntireRow.Delete
ws1.Cells.AutoFilter
Rng2.AutoFilter Field:=6, Criteria1:="*" & Stringname & "*"
ws2.UsedRange.Offset(1, 0).SpecialCells _
(xlCellTypeVisible).EntireRow.Delete
ws2.Cells.AutoFilter
End Sub

Script to Copy and paste entirerows and mergedrows?

The following code is the one that I'm trying to work with, but I still can't make it work with merge rows. The main idea is to create a loop to check each row from D1:D150 and if the criteria are met then copy the entire row.
This is how my data looks like
Sub attributes()
'--------------------------------------------------------------------
Dim Cel, aCell1, aCell2, aCell3, aCellAsses As Range, ws, ws0 As Worksheet
Dim strAsses1 As Boolean
Dim num As Integer
'------------------------------
Set ws = ActiveWorkbook.Sheets("Contract Attributes")
Set ws0 = ActiveWorkbook.Sheets("ReviewerTab")
ws.Activate
Set aCell1 = ActiveWorkbook.Sheets("Contract Attributes").Range("A1:A150")
'Set aCell1 = ActiveWorkbook.Sheets("Contract Attributes").Range("D1:D150")
'Set aCell2 = ActiveWorkbook.Sheets("Contract Attributes").Range("D:D").Find("Current Modifications", LookIn:=xlValues)
'--------------------------------------------------------------------
strName1 = InputBox("Which contract modification would you like to review?")
num = 5
For Each Cel In aCell1
If InStr(1, Cel, strName1, vbTextCompare) > 0 Or InStr(1, Cel, "x") > 0 Then
Cel.MergeArea.Select
Selection.EntireRow.Copy
ws0.Activate
Rows(num).Insert
ws.Activate
num = num + 1
End If
Next Cel
'--------------------------------------------------------------------
'ws0.Columns(4).Delete
'aCell2.Select
'ActiveCell.EntireRow.Copy
'Sheets("ReviewerTab").Range("A5").Insert
End Sub
TIPS
To begin with, I would recommend that you see How to avoid using Select in Excel VBA. Next you need to identify the range object that you need to copy and then copy them across.
Dim Cel, aCell1, aCell2, aCell3, aCellAsses As Range You need to declare them explicitly else the first four objects are declared as Variant and not Range. For example Dim Cel As Range, aCell1 As Range, aCell2 As Range, aCell3 As Range, aCellAsses As Range
Do not copy the rows in a loop. It will be slow. Identify the rows you want to copy and then copy them in one go. Below is an example
SAMPLE SCENARIO
To demonstrate how this works, I am taking the below sample.
CODE
I have come up with a basic code. I have commented it so you should not have a problem understanding it. But if you do then feel free to ask :).
Option Explicit
Sub Sample()
Dim wsInput As Worksheet
Dim wsOuput As Worksheet
Dim RangeToCopy As Range
Dim lRow As Long, i As Long, num As Long
Dim searchText As Variant
'~~> Row in output sheet where the rows will be copied
num = 5
'~~> Set your input and output sheets
Set wsInput = ThisWorkbook.Sheets("Contract Attributes")
Set wsOuput = ThisWorkbook.Sheets("ReviewerTab")
'~~> Take the input from the user
searchText = InputBox("Which contract modification would you like to review?")
If Len(Trim(searchText)) = 0 Then Exit Sub
With wsInput
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Loop through the cells and check for criteria
For i = 1 To lRow
If InStr(1, .Range("A" & i).Value2, searchText, vbTextCompare) Then
'~~> identify the rows you need to copy and store them
'~~> in a range object
If RangeToCopy Is Nothing Then
Set RangeToCopy = .Range("A" & i).MergeArea.EntireRow
Else
Set RangeToCopy = Union(RangeToCopy, .Range("A" & i).MergeArea.EntireRow)
End If
End If
Next i
End With
'~~> Copy them across. You can insert them as well
If Not RangeToCopy Is Nothing Then
RangeToCopy.Copy wsOuput.Rows(num)
End If
End Sub
IN ACTION
You need to include the merge area before "Select".
After you copy the rows, you need to count how many merged rows in the copy. I add a new variable num2 to do so. The loop cannot just simply num=num+1, it varies from what rows copied.
You may try the below code.
Sub attributes()
'--------------------------------------------------------------------
Dim Cel, aCell1, aCell2, aCell3, aCellAsses As Range, ws, ws0 As Worksheet
Dim strAsses1 As Boolean
Dim num As Integer
Dim num2 As Integer
Set ws = ActiveWorkbook.Sheets("Contract Attributes")
Set ws0 = ActiveWorkbook.Sheets("ReviewerTab")
ws.Activate
Set aCell1 = ActiveWorkbook.Sheets("Contract Attributes").Range("A1:A150")
strName1 = InputBox("Which contract modification would you like to review?")
num = 5
For Each Cel In aCell1
If InStr(1, Cel, strName1, vbTextCompare) > 0 Or InStr(1, Cel, "x") > 0 Then
Range(Cells(Cel.Row, 1), Cells(Cel.Row, Cells(Cel.Row, Columns.Count).End(xlToLeft).Column)).Select
num2 = Selection.Rows.Count
Selection.EntireRow.Copy
ws0.Activate
Rows(num).Insert
ws.Activate
num = num + num2
End If
Next Cel
End Sub

Copying data from selection of rows to different sheet based on cell string start and end

I have my data in columns A:L in Sheet2 and wish to copy each block based on the starting point, as certain cell text and the end point, again as certain cell text!
So in the example the cell start text should be "Tank Engine" and the cell end text would be "INFORMATION: Tank Engine". Therefore, column A:L, rows 1:18 should be copied into Sheet3 at cell A1, but only where the cell text exists as this can be dynamic. I need to reference column A to paste to in Sheet3, and copy only the rows that begin with "Tank Engine" and end with "INFORMATION: Tank Engine", which are rows 1:18. The next block would be Columns A:L, rows 25:41 based on the string "Weatherman" and this being pasted into Sheet3 Cell M:X etc.....
The rows are dynamic the columns are static..... I have tried many many snippets of VBA but this is quite particular so cannot find a good match!!
Sub Mike4()
Dim i As Long
lastcolumn = Cells(1, Columns.Count).End(xlToLeft).Column
For i = 1 To lastcolumn
If Cells(1, i) = "Tank Engine" Then
'lastrow = Columns(i).SpecialCells(xlLastCell).Row
lastRow = Columns(i).Find("INFORMATION: Tank Engine").Row
Range(Cells(2, i), Cells(lastRow, i)).Copy Sheet3.Range("A" & Sheet3.Range("A" & Rows.Count).End(xlUp).Row + 1)
End If
Next i
End Sub
I am trying to get the above to then paste the columns with the rows affected into a specific cell then to search for Weatherman as described above but need a starting point that something is working then be able to build on that...As stated previously I have lots of snippets of code but none accumulatively work for what I want to achieve if at all. Any help would be greatly appreciated!! Thanks in advance...Many Thanks!!
Look at this example:
Option Explicit
Sub CopyMyStuff()
Dim wsSrc As Worksheet
Set wsSrc = ThisWorkbook.Worksheets("SourceSheet")
Dim wsDest As Worksheet
Set wsDest = ThisWorkbook.Worksheets("Sheet3")
'find start
Dim FoundStart As Range
Set FoundStart = wsSrc.Range("A:L").Find(What:="Tank Engine", LookAt:=xlWhole)
If FoundStart Is Nothing Then
MsgBox "start not found"
Exit Sub
End If
'find end
Dim FoundEnd As Range
Set FoundEnd = wsSrc.Range("A:L").Find(What:="INFORMATION: Tank Engine", LookAt:=xlWhole, After:=FoundStart)
If FoundEnd Is Nothing Then
MsgBox "start not found"
Exit Sub
End If
wsSrc.Range(FoundStart, FoundEnd).Resize(ColumnSize:=12).Copy wsDest.Range("A1")
End Sub
Or more elegant with a function:
Option Explicit
Sub CopyMyStuff2()
Dim wsSrc As Worksheet 'define source
Set wsSrc = ThisWorkbook.Worksheets("SourceSheet")
Dim wsDest As Worksheet 'define destination
Set wsDest = ThisWorkbook.Worksheets("Sheet3")
Dim FindList As Variant 'defind search words
FindList = Array("Tank Engine", "Weatherman")
Dim i As Long
Dim FindItm As Variant
For Each FindItm In FindList
Dim CopyRange As Range
Set CopyRange = FindMyRange(wsSrc.Range("A:L"), FindItm, "INFORMATION: " & FindItm)
If Not CopyRange Is Nothing Then
CopyRange.Copy wsDest.Range("A1").Offset(ColumnOffset:=i) 'note that if the first column uses merged cells the ColumnOffset:=i otherwise it is ColumnOffset:=i*12
i = i + 1
End If
Next FindItm
End Sub
Function FindMyRange(SearchInRange As Range, ByVal StartString As String, ByVal EndString As String) As Range
'find start
Dim FoundStart As Range
Set FoundStart = SearchInRange.Find(What:=StartString, LookAt:=xlWhole)
If FoundStart Is Nothing Then GoTo ERR_NOTHING_FOUND
'find end
Dim FoundEnd As Range
Set FoundEnd = SearchInRange.Find(What:=EndString, LookAt:=xlWhole, After:=FoundStart)
If FoundEnd Is Nothing Then GoTo ERR_NOTHING_FOUND
Set FindMyRange = SearchInRange.Parent.Range(FoundStart, FoundEnd).Resize(ColumnSize:=12)
Exit Function
ERR_NOTHING_FOUND:
FindMyRange = Nothing
End Function

How to copy cells downwards without overwriting what's under it?

https://dl.dropbox.com/u/3327208/Excel/copydown.xlsx
This is the sheet if you can't view dropbox.
This is the workbook. What I'm looking to do is where it shows 3M, copy the title of the company down to where it shows Total in Column A, and do the same with the next company.
How do I do this in Excel VBA? I know I can use the last row, but it's not exactly the best way for this I believe, because the original version will have over 300 different companies.
Here is the original code I am using for now. Without the extra bits added in.
Option Explicit
Sub Import()
Dim lastrow As Long
Dim wsIMP As Worksheet 'Import
Dim wsTOT As Worksheet 'Total
Dim wsSHI As Worksheet 'Shipped
Dim wsEST As Worksheet 'Estimate
Dim wsISS As Worksheet 'Issued
Dim Shift As Range
Set wsIMP = Sheets("Import")
Set wsTOT = Sheets("Total")
Set wsSHI = Sheets("Shipped")
Set wsEST = Sheets("Estimate")
Set wsISS = Sheets("Issued")
With wsIMP
wsIMP.Range("E6").Cut wsIMP.Range("E5")
wsIMP.Range("B7:G7").Delete xlShiftUp
End Sub
Matt, I had a great function for this a few months back, but I forgot to copy into my library. However, I've done a pretty good mock-up of what I had before. (I was using it to fill down entries in a pivot table for some reason or other).
Anyway, here it is. You may need to tweak it to meet your exact needs, and I am not claiming it's not prone to any errors at the moment, but it should be a great start.
EDIT = I've updated my code post to integrate into yours to make it easier for you.
Sub Import()
Dim lastrow As Long
Dim wsIMP As Worksheet, wsTOT As Worksheet 'Total
Dim wsSHI As Worksheet, wsEST As Worksheet 'Estimate
Dim wsISS As Worksheet, Shift As Range
Set wsIMP = Sheets("Import")
Set wsTOT = Sheets("Total")
Set wsSHI = Sheets("Shipped")
Set wsEST = Sheets("Estimate")
Set wsISS = Sheets("Issued")
With wsIMP
.Range("E6").Cut .Range("E5")
.Range("B7:G7").Delete xlShiftUp
Call FillDown(.Range("A1"), "B")
'-> more code here
End With
End Sub
Sub FillDown(begRng As Range, col As String)
Dim rowLast As Long, rngStart As Range, rngEnd As Range
rowLast = Range(col & Rows.Count).End(xlUp).Row
Set rngStart = begRng
Do
If rngStart.End(xlDown).Row < rowLast Then
Set rngEnd = rngStart.End(xlDown).Offset(-1)
Else
Set rngEnd = Cells(rowLast, rngStart.Column)
End If
Range(rngStart, rngEnd).FillDown
Set rngStart = rngStart.End(xlDown)
Loop Until rngStart.Row = rowLast
End Sub
enter code here
As long as there are no formulas you don't want to overwrite...
EDIT - updated to set original range based off end of column B
Sub Macro1()
Dim sht as WorkSheet
Set sht = ActiveSheet
With sht.Range(sht.Range("A7"), _
sht.Cells(Rows.Count, 2).End(xlUp).Offset(0, -1))
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
.Value = .Value
End With
End Sub

Resources