I am new to VBA and am trying to write a macro that takes a workbook with several sheets of data and formats it for printing. Each sheet has tables of information (like operating/income statements). I want this code to be able to work for workbooks anyone creates but has the same basic information. This means I need to find start and end of the data on each sheet because it is not always in the same place (someone might start from "A1" and someone else from "B4", etc).
I've looked on the many websites for different ways to locate the first row used and last column used. What I have so far sometimes locates the starting row, ending row, starting column, and ending column correctly and other times it doesn't.
Sub FormatWorkbook()
Dim ws As Worksheet
Dim rowStart As Long
Dim columnStart As Long
Dim rowEnd As Long
Dim columnEnd As Long
Dim printStart As String
Dim printEnd As String
Application.ScreenUpdating = False
'Turn off print communication
Application.PrintCommunication = False
'Loop through sheets
For Each ws In Worksheets
'Make current sheet activesheet
ws.Select
'Set rowStart, columnStart, rowEnd, and columnEnd to the used range
With ActiveSheet
rowStart = .Cells.Find(what:="*", after:=.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, searchorder:=xlByRows, searchdirection:=xlNext, MatchCase:=False).Row
columnStart = .Cells.Find(what:="*", after:=.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, searchorder:=xlByColumns, searchdirection:=xlNext, MatchCase:=False).Column
rowEnd = .Cells.Find(what:="*", after:=.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, searchorder:=xlByRows, searchdirection:=xlPrevious, MatchCase:=False).Row
columnEnd = .Cells.Find(what:="*", after:=.Range("A1"), LookAt:=xlPart, LookIn:=xlValues, searchorder:=xlByColumns, searchdirection:=xlPrevious, MatchCase:=False).Column
End With
This is just a portion of the program but the one I am most confused about. If anyone could help out I would really appreciate it. Also, if there is a better way to accomplish this task I'm all ears. The rest of my code is below for reference.
'Insert or Delete Space above the first used row
If rowStart < 4 Then
Do While rowStart < 4
Range("1:1").Select
Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
ws.Select
With ActiveSheet
rowStart = .Cells.Find(what:="*", after:=.Range("A1"), LookIn:=xlValues, searchorder:=xlByRows, searchdirection:=xlNext).Row
End With
Loop
ElseIf rowStart > 4 Then
Do While rowStart > 4
Range("1:1").Select
Selection.Delete Shift:=xlUp
ws.Select
With ActiveSheet
rowStart = .Cells.Find(what:="*", after:=.Range("A1"), LookIn:=xlValues, searchorder:=xlByRows, searchdirection:=xlNext).Row
End With
Loop
End If
'I think I need to adjust the columnStart, rowEnd, and columnEnd values after inserting and deleting rows
ws.Select
printStart = ActiveSheet.Cells(1, columnStart).Address
printEnd = ActiveSheet.Cells(rowEnd, columnEnd).Address
'Format headers, footers, and set the print area
ActiveSheet.PageSetup.CenterHeaderPicture.Filename = _
"\\antilles\MyDocs\xxxx\My Documents\My Pictures\xxxx.png"
With ActiveSheet.PageSetup
.CenterHeader = "&G"
.LeftFooter = "&""Palatino Linotype,Regular""&F"
.CenterFooter = "&""Palatino Linotype,Regular""Prepared By: xxxx"
.RightFooter = "&""Palatino Linotype,Regular""&D"
.PrintArea = printStart & ":" & printEnd
End With
Next ws
Application.PrintCommunication = True
End Sub
I've done a lot of this and I recommend simple crude methods. Without coding a whole thing, but rather hinting...
dim iRow as integer
For iRow = 1 to ...
cells(iRow,65000).end(xlup).select
if activecell.row = 1 then
activecell.entirecolumn.delete
exit For
endif
next iRow
As much as possible force some structure on the sheets. Then go see
http://www.ozgrid.com/VBA/ExcelRanges.htm
When googling, include
ozgrid|Pearson|Tushar|"Mr. Excel"|Erlandsen|Peltier|dailydoseofexcel
in the search string.
Excel VBA offers a selection of techniques for finding the first or last used row or column but none work in every situation.
Some problems with your code:
If the worksheet contains something, Find returns a range. A range has a row so your statements will work. But if the worksheet is empty, Find returns Nothing. You must use Set Rng = .Cells.Find ... then test Rng to not be Nothing before accessing Rng.Row or Rng.Column. See the code I reference below if this is not clear.
Note the after in After:=.Range("A1"). Find does not examine .Range("A1") until it has searched every other cell and wraps back to the beginning. In any example in which the start point is .Range("A1") the search direction will be xlPrevious so it immediately wraps and starts searching from the bottom right cell. Try After:=.Cells(Rows.Count, Columns.Count) when the search direction is xlNext.
There was an earlier question that was similar to yours. I posted some code which showed a selection of techniques for finding the last row or column and the situations in which they fail. I suggest you visit that answer and try the code: https://stackoverflow.com/a/18220846/973283.
Good luck and happy VBA programming.
Related
I'm trying to write a macro that will sort a work book that is generated by a system at work. I have attempted to chopshop some code together from other posts on this site with no success.
The goal is to search column A for any cells that contain either "IN" or "OUT" then move every thing to the right of these cells one cell to the right.
I have some code that works for the first output but it will only ever ready the first out put I know why it doesn't work but I don't know how to fix it.
Any help would be much appreciated,
Thanks,
Sub Data_only()
'
' Reworks_Data_only Macro
'
' Keyboard Shortcut: Ctrl+k
'
Columns("J:AB").Select
Selection.ClearContents
Cells.Select
Cells.EntireColumn.AutoFit`enter code here`
' ^ Cuts out unused columns and autofits the rest
Columns("A:A").Select
Selection.Find(What:="in", After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
' ^Searches Column A for "IN"
ActiveCell.Offset(, 1).Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
' ^Selects the found cell and shift the whole row to the right
End Sub
EDIT
This is a mock up of the file im looking to change, there would normally be a few hundred batches and a lot more columns but it should be workable.
batches mock up
Something like that would be possible if you like to use the Find function ...
Option Explicit
Public Sub Data_only()
MoveByFind "IN"
MoveByFind "OUT"
End Sub
Public Function MoveByFind(FindString As String)
Dim Found As Range
Set Found = Columns("A:A").Find(What:=FindString, LookIn:=xlFormulas, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not Found Is Nothing Then
Dim firstAddress As String
firstAddress = Found.Address 'remember first find for no endless loop
Do
Found.Offset(0, 1).Insert Shift:=xlToRight 'move cells right
Set Found = Columns("A:A").FindNext(After:=Found) 'find next
Loop While Not Found Is Nothing And Found.Address <> firstAddress 'loop until end or nothing found
End If
End Function
You can do this with a simple loop, rather than using the Find function:
Dim i as Long, LR as Long
LR = Cells(Rows.Count,1).End(xlUp).Row
For i = 2 to LR 'Assumes you have a header in row 1
If Cells(i,1).Value = "IN" OR Cells(i,1).Value = "OUT" Then
Cells(i,2).Insert Shift:=xlToRight
End If
Next i
Note that In and Out are case-sensitive.
You could also do this with the Find function, though you would find all, or use find next, and use the .insert as you've doen in your code.
Edit:
Assuming that the issue is hidden characters, InStr can be used:
Dim i As Long, LR As Long, j As Integer, k As Integer
LR = Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To LR 'Assumes you have a header in row 1
j = InStr(Cells(i, 1).Value, "IN")
k = InStr(Cells(i, 1).Value, "OUT")
If j > 0 Or k > 0 Then
Cells(i, 2).Insert Shift:=xlToRight
End If
Next i
This code works well in Excel 2003 but fails in Excel 2007. What am I not seeing in it that crashes it? It errors out when it gets to the "LastRow =".. This is my error message:
Run-Time Error 13 Type Mismatch
Dim LastRow As Long
Dim LastColumn As Integer
Dim LastCell As Range, NextCell As Range
' ****************************************************
' Finds LastRow and LastColumn
With Worksheets("DB")
' Find Last Row/Col
If WorksheetFunction.CountA(Cells) > 0 Then
' Search for any entry, by searching backwards by rows
LastRow = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
' Search for any entry, by searching backwards by columns
LastColumn = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
End If
Set NextCell = Worksheets("DB").Cells(LastRow + 1, (LastColumn))
End With
' ****************************************************
Found an error. Guess I copied the Lastrow and was GOING to change the second one to columns. but that still doesn't solve the hang on the first chunk. Opps while editing that last part to columns I see that I may have typed an extra "s" in the .Rows Looks like it SHOULD be .Row I'll see when I get home since my hard copy at work shows no "s". Guess that's what I get when trying to "Remember" the code when I get home. To "s" or not to "s", that is the question. LOL At least I think I solved it with a little poke the the head. Thanks Siddharth.
Are you sure it works with Excel 2003?
You have to use .Row instead of .Rows. See This
Your code will also fail because your LastColumn=0
Is this what you are trying?
Sub Sample()
Dim LastRow As Long
Dim LastColumn As Integer
Dim NextCell As Range
With Worksheets("DB")
If WorksheetFunction.CountA(Cells) > 0 Then
'~~> Find Last Row
LastRow = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
'~~> Find Last Column
LastColumn = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
End If
'~~> Set the cell to the first empty cell after the last cell
'~~> which has data
Set NextCell = Worksheets("DB").Cells(LastRow + 1, (LastColumn))
'~~> Display the address of that cell
MsgBox NextCell.Address
End With
End Sub
I am trying to create a VBA macro for post processing data, and it currently has a "Raw Data" sheet for the first sheet, and my post processing tools on the second sheet. What I have so far is a button that will search the data and create plots for the desired variables, but it pulls data for all of the test points. What I want to do is to be able to filter by test point from the data review sheet. What would be ideal would be to have an autofilter type dropdown menu on my post processing sheet where the test point can be selected, and the data on the previous sheet would be filtered.
Here's the search function I've been using:
Dim TestPt As Long
Dim rows As Long
rows = Sheets(1).UsedRange.rows.Count
'
Sheets(1).Select
Cells.Find(What:="TargetTestPointNumber", After:=ActiveCell, LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False _
, SearchFormat:=False).Activate
TestPt = ActiveCell.Column
Any help would be appreciated!
I'm not 100% sure what you needed exactly. But this code does a comparison between the value you want and the data sheet. If its = it will copy it to Row D so if you have stuff there you will need to change it. Also it assumes the data is in row 1.
Dim i As Long, lastRowD As Long, lastRowA As Long
With Sheets("datasheetname")
lastRowA = .Range("A" & .Rows.count).End(xlUp).Row
For i = 1 To lastRowA
lastRowD = .Range("D" & .Rows.count).End(xlUp).Row
If .Cells(i, 1).Value = "testvalue" Then
.Cells(lastRowD, 4).Value = "testvalue"
End If
Next i
.Range("D1", "D" & lastRowD).Select
ActiveSheet.Shapes.AddChart.Select
ActiveChart.ChartType = xlXYScatter
ActiveChart.SetSourceData Source:=Range("'datasheetname'!$D$1:$D" & lastRowD)
End With
note youll need to change test value and datasheetnames. As well as any of the column info. I tried it and it works. I just have no idea if this is what you need. If you need to copy more than 1 row, you would change in the if how many to do.
.cells(lastRowD,5).value= .cells(i,2).value etc
Sorry for the messy code, but I found this to work for me. Basically I copied the unique test point value to the other sheet, linked them to a ComboBox, and linked a macro to run with the ComboBox to autofilter the data on the other sheet. I'm sure there has to be a better way, but it works for me.
Sub ValueSelectionData()
'
Dim TestPt As Long
Dim rows As Long
Dim Value As Long 'used to select test point
rows = Sheets(1).UsedRange.rows.Count 'Row count on data sheet
Value = Sheets(2).Cells(2, 6).Value 'value linked to ComboBox selection
'
Sheets(2).Columns("A:A").ClearContents
Sheets(1).Select
Cells.Find(What:="TargetTestPointNumber", After:=ActiveCell, LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False _
, SearchFormat:=False).Activate 'searches for test point column in data
TestPt = ActiveCell.Column
Range(Sheets(1).Cells(2, TestPt), Sheets(1).Cells(rows, TestPt)).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Range(Sheets(2).Cells(1, 1), Sheets(2).Cells(1, 1)), Unique:=True
If Value > 0 Then
Sheets(1).Select
Cells.Find(What:="TargetTestPointNumber", After:=ActiveCell, LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False).Activate
TestPt = ActiveCell.Column
Value = Sheets(2).Cells(2, 6).Value 'desired test point to filter for
Sheets(1).Range(Sheets(1).Cells(1, TestPt), Sheets(1).Cells(rows, TestPt)).AutoFilter Field:=1, Criteria1:=Value 'autofilters data for desired test point
Else
'Clear all auto filters
If Sheets(1).AutoFilterMode Then
Sheets(1).ShowAllData
End If
End If
End Sub
I am looking to find a variable within a row and return the column reference. The code I have written so far is;
Dim VarianceDate As String
VarianceDate = Sheets("Summary").Range("C12").Value
Rows("6").Find(What:=VarianceDate, After:=ActiveCell, LookIn:=xlFormulas _
, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
TargetCol = ActiveCell.Column
In this example the Variance date is 01/06/2015, however when I stepinto the code in VBA it returns nothing. Except When I search for it manually It finds the correct cell.
Eventually I would like to use the TargetCol reference to help me extract the correct data into another workbook.
Any help would be much appreicated.
Thanks
You need to mention that the search term is a date and also if the activecell is not in your find range, it will throw an error due to the inclusion of After:=ActiveCell
Try the following code
Sub FindDateCol()
Dim VarianceDate As String: VarianceDate = Sheets("Summary").Range("C12").Value
Dim TargetCell As Range, TargetCol As Integer
Set TargetCell = Rows("6").Find(What:=CDate(VarianceDate), LookIn:=xlFormulas, LookAt:=xlPart)
If Not TargetCell Is Nothing Then TargetCol = TargetCell.Column
MsgBox TargetCol
End Sub
How do I modify this line in VBA to only select the columns that have values?
Set rng = Range("A1", Range("A65536").End(xlUp)).SpecialCells(xlCellTypeVisible)
I don't think I'm doing something right since the CountLarge property is several billion cells
Here is a sample of my data
#SiddharthRout Yes I only need the rows that have data. I think I have it working now with End(xlToLeft) from #JMax ... Now that I'm iterating over the cells, I can just quit the For each loop once the last row is reached. I might have this working now. – makerofthings7 14 mins ago
For this neither you need .SpecialCells nor do you need to loop through the rows :)
Here is a sample code. This will copy all the rows which have data to Sheet2 (TRIED AND TESTED)
Sub Sample()
Dim ws As Worksheet
Dim rng As Range
Dim LastRow As Long, LastCol As Long
Set ws = Sheets("Sheet1")
With ws
LastRow = .Cells.Find(What:="*", After:=.Range("A1"), Lookat:=xlPart, _
LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious, MatchCase:=False).Row
LastCol = .Cells.Find(What:="*", After:=.Range("A1"), Lookat:=xlPart, _
LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, _
MatchCase:=False).Column
With .Range("A1:" & Split(Cells(, LastCol).Address, "$")(1) & LastRow)
.AutoFilter Field:=1, Criteria1:="<>"
Set rng = ws.AutoFilter.Range
rng.Offset(1, 0).Resize(rng.Rows.Count - 1).Copy _
Destination:=Sheets("Sheet2").Range("A1")
End With
End With
End Sub
SNAPSHOT
I am assuming that all cells in a particular row will have data and there won't be a case like this
#makerofthings7: I think I know what exactly you are trying to do :) you don't need to use loops to achieve what you want. Just a quick question. Is it possible that say Cell C10 might have a value but B10 might not? – Siddharth Rout 12 mins ago
If there is then we will have to set the autofilter criteria accordingly.
Edit:
WAY 2
The other way would be to sort your data, pushing the blanks way down and then copying the resulting range :)
HTH