Writing loop statement for active workbook - vba

First of all, don't laugh. And if you do, just do it quietly. I have no idea how to write looping Visual basic, but I took my best shot. I'm trying to learn VBA as best I can, by doing, and I just need a push in the right direction. The idea is I want to write a simple macro that looks at each worksheet, evaluates whether or not there is grouping in rows 19-43,if there is grouping, gets rid of the grouping, if there is not grouping, go to the next worksheet, and if there is no more worksheets, end the loop.
Here's my attempt which obviously did not work.
Dim V As Variant
V = ActiveWorksheet
For Each V In Workbook
Rows("a29:43").Select
Selection.Rows.Ungroup
Next
Now, I also have no idea what the qualification is to say something like if it is grouped, then this, if not grouped, then this. I would imagine it's something like grouping = true but I couldn't figure it out. I am VERY grateful to any who spare some time to look at this.

You need to define the collection that you want to iterate over, so in this case it would be the worksheets contained in the workbook:
Dim wbk As Workbook
Dim sht As WorkSheet
Set wbk = ActiveWorkbook
For Each sht In wbk.Worksheets
sht.Rows("a29:43").Select ' this may give you trouble. Possibly try sht.Range("a29:a43").Select?
Selection.Rows.Ungroup
Next
Set wbk = Nothing ' Clean up our objects. Just to be sure.
As an aside, I don't know if it needs mentioned but just in case, I'd recommend you avoid ever using single-character variable names.

Couple of things:
As per #pnuts, you're missing an 'A' in your target range. This can be critical.
Ungroup will fail if rows are not grouped. This works kind of like Autofilter.
Best practices would be to use Range--not Rows, and just use the Rows property of Range--when dealing with target cells, unless a different approach is necessary or justified.
Here's a not-so-different take. Note the clear and explicit declarations of each variable. Also, read the comments. They're not the best, but they can help you on your apparent quest to learn more VBA. ;)
Sub UngroupRanges()
Dim wBk As Workbook, wSht As Worksheet
Dim rTarget As Range
Set wBk = ThisWorkbook 'Let wBk be this workbook.
For Each wSht In wBk.Worksheets 'Read: for each sheet in this workbook's collection of worksheets.
Set rTarget = wSht.Range("A29:A43") 'Read: I'll be setting rTarget as my target cells.
On Error Resume Next 'Read: If I hit an error after this line, I'll just continue executing.
rTarget.Rows.Ungroup 'Read: For all rows in rTarget, I'll do an ungroup.
On Error GoTo 0 'Read: If I hit an error after this, I won't skip anymore.
Next wSht 'Read: Move on to next worksheet.
Set wBk = Nothing 'Read: Release the assignment of this workbook to wBk.
End Sub
Let us know if this helps.

Related

Selecting activeworksheet only when it exists in the workbook

I have a VBA module where I want to select a worksheet, but only if it is present in the workbook.
So if I use activeworksheets code this gives an error if the worksheet is not in the workbook.
I have these for 3 worksheets, so I have tried if error but this only works if one of the worksheets is missing, as if error only handles the first case and cannot handle further cases.
Any suggestions?
There are many ways to solve this. My suggestion is doing
Dim ws as Worksheet
Set ws = Worksheets(1)
if ws.Name == "Worksheet I want"
'Do your thing
End if
Idk how many sheets you have. Use a for with Application.Sheets.Count if you need to.
Hope this helps

shorthand for workbook().worksheets()?

I may be blind, but I've been working with VBA for a few years now but still write out
Workbook("Book1").Sheets("Sheet1").Range("A1").Value
or (after dimming Book1 as a workbook and Sheet1 as a string
Book1.Sheets(Sheet1).Range("A1").Value
Is there a way that you can shorthand the "workbook.sheets" part (without doing a "With" statement)?
Sure. Just do it the wrong way:
Sheet1.Activate
Range("A1").Value = 42
Unqualified in a standard code module, Range is a member of _Global, which implements its Range property by returning the specified range on whichever worksheet happens to be active... if any (in a worksheet's code-behind, it implicitly refers to Me.Range, i.e. a range on that sheet).
If you're going to implicitly work off ActiveSheet, you can also defer type resolution to run-time with a less performant late-bound call, and make the host application (here Excel) evaluate a bracketed expression for even faster typing:
[A1].Value = 42
Heck, the Range type has a default member that points to its Value, so you could even do this:
[A1] = 42
As you can see, less code isn't always better code. Qualify your Worksheet member calls, and use default members consciously and judiciously.
Every time someone makes an implicit call on _Global, a baby unicorn dies and two new Stack Overflow questions involving errors stemming from unqualified worksheet calls are summonned from the darkness.
Sarcasm aside, if you find yourself constantly chaining such Workbook("Book1").Sheets("Sheet1").Range(...)... calls, then you're constantly dereferencing the same objects, over and over: that's not only redundant to type, it's also redundant to execute.
If you're working with ThisWorkbook (the workbook running the code), you never have a legitimate reason to do this to dereference a worksheet that exists at compile-time. Use its code name instead:
Sheet1.Range(...)...
If the workbook only exists at run-time, or otherwise isn't ThisWorkbook, then at one point in time your code opened or created that workbook - there's no need to ever dereference it from the Workbooks collection, ...if you stored the reference in the first place:
Set wbExisting = Workbooks.Open(path)
Set wbNew = Workbooks.Add
Same for worksheets that are created at run-time in other workbooks by your code: keep that object reference!
Set wsNew = wbNew.Worksheets.Add
This leaves only 1 scenario where you would ever need a string to dereference a specific worksheet: the sheet already exists in a workbook that isn't ThisWorkbook.
If that workbook's structure isn't (or can't be) protected, avoid hard-coding the sheet's index or name if you can:
Set wsExisting = wbExisting.Worksheets(1) ' user may have moved it!
Set wsExisting = wbExisting.Worksheets("Summary") ' user may have renamed it!
TL;DR
Work with objects. Declare objects, assign object references, and work with them, pass them as arguments to your procedures. There's no reason to be constantly dereferencing objects like you're doing. If you need to do it, then you only ever need to do it once.
Sure. Just do it the right way:
Dim wb As Workbook
Set wb = Workbooks("Book1")
Dim ws As Worksheet
Set ws = wb.Worksheets("Sheet1")
Dim x As Variant
x = ws.Range("A1").Value
(Sorry Mat's Mug - I had to have a bit of a dig with that first line :D)
OK, I'm going to take the "Book1" name literally, and assume that you're writing code to deal with a new workbook - probably with something like:
Dim myWorkbook As Workbook
Workbooks.Add
Set myWorkbook = Workbooks("Book1")
That's already a bad start, because:
The language of the user will determine the "Book" part of the name.
The numeric suffix will increment with every additional new workbook
So, many inexperienced coders try this:
Dim myWorkbook As Workbook
Workbooks.Add
Set myWorkbook = ActiveWorkbook
But that's open to error too. What if there are event handlers looking to change the active workbook? What is the user changes the active workbook while stepping through code?
The best way to assign your myWorkbook variable is with something like this:
Dim myWorkbook As Workbook
Set myWorkbook = Workbooks.Add
And, just like when adding a new workbook, you should follow the same approach when opening an existing workbook:
Dim myWorkbook As Workbook
Set myWorkbook = Workbooks.Open("C:\Foo.xlsx")
In both cases, you know you've got a reference to the correct workbook, and you don't care what it is called or whether it is active. You've just made your code more robust and more efficient.
Alternatively, if your VBA is working with the workbook in which it resides, you can just use ThisWorkbook or the codename of the sheet(s).
Debug.Print ThisWorkbook.Name
'By default, a sheet named "Sheet" has a codename of 'Sheet1'
Debug.Assert Sheet1.Name = ThisWorkbook("Sheet1").Name
'But give meaningful names to your sheet name and
'sheet codename, and your code becomes clearer:
Debug.Assert AppSettings.Name = ThisWorkbook("Settings").Name
You'll probably find that most of your code deals with the workbook in which it resides, or existing workbooks that your code opens, or new workbooks that your code creates. All of those situations are handled above. On the rare occasions in which your code must interact with workbooks that are already open, or are opened by some other process, you'll need to refer to the workbook by name, or enumerate the Workbooks collection:
Dim myWorkbook As Workbook
Set myWorkbook = Workbooks("DailyChecklist.xlsx")
For Each myWorkbook In Workbooks
Debug.Print myWorkbook.Name
Next myWorkbook
The one exception being add-ins, which can't be enumerated using the Workbooks collection, but can be referenced with Workbooks("MyAddin.xlam")
Like this
Sub temp()
Dim WB As Workbook, WS As Worksheet
Set WB = ActiveWorkbook
Set WS = WB.Sheets(2)
MsgBox WS.Range("A2").Text
End Sub
You can set the worksheet variable along with its parent workbook
Dim wb1s1 As Worksheet
Set wb1s1 = Workbook("Book1").Sheets("Sheet1")
And then write
wb1s1.Range("A1").Value

Excel file decided one day to format all of the cells that were General to Custom [$-409]ddd

I've been using this particular file for a number of years (expense tracking/checkbook). It has a few simple macros, but none of them have acted up after several years of fine-tuning to do anything like the effect I've encountered. I'm running Excel 2013, if that makes any difference to the scenario.
Several months ago I noticed that when entering data into an unused area on one of the worksheets, the result shown would be "Mon", "Tue", etc. Looking at the formatting drop-down list in the toolbar showed Custom, instead of General, as I would have expected (the specific formatting is [$-409]ddd). For a long time I just adjusted the formatting on the new work to whatever I needed it to be (General, Accounting, Percentage, etc) and carried on. It's become frustrating recently and I decided to investigate further.
It appears that ALL of the cells that were normally formatted as General, are really formatted as Custom. Most of the cells I didn't notice it on are simply text like Balance, Contribution, etc. so I didn't realize the formatting had changed. Only the cells that I specifically formatted as Accounting, Number, Percentage, etc. remain unaffected by the blanket "Custom-ization".
I don't have any code in my macros that do blanket changes to [$-409]ddd, only one section of code that applies "mmm dd" on one specific page, and it's hard coded to "mmm dd".
Does anyone have any clues on what may have happened? I'm open to suggestions on how to remedy the situation as well. I'm considering just a brute-force macro that walks through all of the cells in all of the worksheets, checks the formatting against [$-409]ddd and changes them to General.
This can happen if the Normal Style has been corrupted. Examine it (using right-click) and fix if necessary:
Gary's Student is most likely right as to the cause of this. Anyway, if you don't know how to fix it that way or the cause turns out to be something else, here is a brute-force way to remedy the situation in all worksheets in the workbook.
Some words of caution:
1) This will take a very long time to run, since it loops through all cells in the workbook.
2) Make sure you insert the name of the wrong number format exactly right, or it won't work.
3) Make a copy of the workbook in question before you try this to make sure you don't break anything unintentionally.
Sub resetNumberFormats()
Dim sht As Excel.Worksheet
Dim cll As Range
Dim wrongNumberformat As String
Application.ScreenUpdating = False
wrongNumberformat = "[$-409]ddd"
For Each sht In Worksheets
For Each cll In sht
If wrongNumberformat = cll.NumberFormat Then cll.NumberFormat = "General"
Next cll
Next sht
Application.ScreenUpdating = True
End Sub
Edit
The following code is much much faster and works in an instant by me. Try this instead:
Sub setNumberFormats()
Dim sht As Excel.Worksheet
Dim cll As Range
Dim wrongNumberformat As String
' insert VBA code for wrong number format below
wrongNumberformat = "[$-409]ddd"
With Application
.FindFormat.NumberFormat = wrongNumberformat
.ScreenUpdating = False
End With
On Error Resume Next
For Each sht In Worksheets
Do While Err.Number = 0
sht.Cells.Find(What:="*", SearchFormat:=True).NumberFormat = "General"
Loop
Err.Clear
Next sht
Application.ScreenUpdating = True
End Sub

Copying Data from External workbook based on headers not in order to another workbook

I hope this is the right place to ask this question as I am on the verge of going crazy. I am so rusty and I have zero experience with VBA (only with C++, java)
The problem:
I am trying to copy data from one workbook to another.
Lets say I have a workbook (called DATA) with several worksheets filled with data. Each column of data has a unique heading (all headings on the same row).
On the other hand I have another workbook (called REPORT) with one worksheet that contains only the heading of the data (in one row). They are not in the same order as in DATA workbook. For example I have 3 headings in REPORT worksheet that can be found in different worksheets in DATA workbook.
I need to loop through all the worksheets in the DATA workbook and copy paste the whole column to the REPORT worksheet when the same heading is found.
This image may help to understand. Explanation
Thanks ALOT for your help in advance. I have searched alot for this code but found similar stuff but didnt manage to understand any .
First attempt at doing it, but getting an error of Run-time error '1004'.
Any help?
Dim MyFile As String
Dim ws As Worksheet
''Workbook that contains one worksheet with all the headings ONLY NO DATA
Dim TargetWS As Worksheet
Set TargetWS = ActiveSheet
Dim TargetHeader As Range
''Location of Headers I want to search for in source file
Set TargetHeader = TargetWS.Range("A1:G")
''Source workbook that contains multiple sheets with data and headings _
not in same order as target file
Dim SourceWB As Workbook
Set SourceWB = Workbooks("Source.xlsx")
Dim SourceHeaderRow As Integer: SourceHeaderRow = 1
Dim SourceCell As Range
''Stores the col of the found value and the last row of data in that col
Dim RealLastRow As Long
Dim SourceCol As Integer
''Looping through all worksheets in source file, looking for the heading I want _
then copying that whole column to the target file I have
For Each ws In SourceWB.Sheets
ws.Activate
For Each Cell In TargetHeader
If Cell.Value <> "" Then
Set SourceCell = Rows(SourceHeaderRow).Find _
(Cell.Value, LookIn:=xlValues, LookAt:=xlWhole)
If Not SourceCell Is Nothing Then
SourceCol = SourceCell.Column
RealLastRow = Columns(SourceCol).Find("*", LookIn:=xlValues, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
If RealLastRow > SourceHeaderRow Then
Range(Cells(SourceHeaderRow + 1, SourceCol), Cells(RealLastRow, _
SourceCol)).Copy
TargetWS.Cells(2, Cell.Column).PasteSpecial xlPasteValues
End If
End If
End If
Next
Next
Your question didn't specify what part of the problem you're actually stuck on, so I'll assume you don't know how to start. Note that nobody on here is going to provide you with the full working solution to your problem - that's upto you to figure out.
A few tips to get you to start working:
The first question you're going to ask yourself with problems involving multiple workbooks is typically going to be which workbook am i going to attach my macro to?
In your case, the REPORT Workbook looks like a saner option, since you probably want someone to be clicking on something in the report in order to generate it. You could also argue the other way around though.
Once you have chosen where to put your VBA, you have to establish a reference to the other workbook.
You either have to load the other Excel file from disk using Workbooks.Open, or have both Workbooks be open at the same time in your Excel Instance, which I'd recommend for you because it's easier. In this case simply establish the reference using the Workbooks object.
Dim exampleRefToDATA As Workbook: Set exampleRefToDATA = Workbooks("data.xlsx") ' or index
Then, cycle through each Worksheet
using something like For Each ws As WorkSheet In exampleRefToDATA.WorkSheets as your For Loop
In that Loop, loop through the first column using something like
For Each allName As Range In ws.Range(... for you to figure out ...)
In this Loop, you'll have to look if that name is in your REPORTS sheet by doing another loop like
For Each thisName As Range in Range(... seriously, there's enough on stackoverflow on how to properly iterate over the used range of a row ...)
Note how this Range() call is Equivalent to ActiveWorkbook.ActiveWorkSheet.Range, which is your Reports sheet.
Then just check for equality and copy the row over if necessary. Again, copying a row has also been covered here before.
Hope this was helpful for you.

How do I get a for loop to work on all the worksheets in my workbook?

Simple question, and I feel like an idiot for not knowing this. What happened was I imported some data from several word docs, and it left me with some extraneous data on all of the sheets. I want to go through and have it delete the top three rows of each sheet automatically (normally I wouldn't mind, but for 100+ sheets it is a bit annoying).
Here is what I have tried so far:
Sub delete_rows()
Dim delRange As Range
Dim wkSheet As Worksheet
For Each wkSheet In ThisWorkbook
Set delRange = Rows("1:3")
delRange.Delete
Next wkSheet
End Sub
I get this error kicked back:
"runtime error '438':
object doesn't support this property or method"
The error comes up at the start of the for loop.
Any chance I could get some help?
Thanks!
You are using the Worksheet type instead of a variable. Also you are not referencing the Worksheets collection in your For Each loop.
For Each wkSheet in ThisWorkbook.Worksheets
'...
Next wkSheet
Also, you don't need to use a variable if you are just deleting 3 rows. You can do directly:
Rows("1:3").Delete