Need help refining my excel macro for deleting blank rows or performing another action - vba

Basically what I'm trying to accomplish is to search the document for blank rows and delete them, if any. This works great if there are blank rows to delete; however, if there are no blank rows, the macro ends with an error. I'd be eternally grateful if someone could advise me how to make this into an "if blank rows then this, if none then that"
Sheets ("xml") .Select
Cells.Select
Selection.SpecialCells(x1CellTypeBlanks).Select
Selection.EntireRow.Delete
Enter my second macro (this part works fine)
Regards

Let me point you to the canonical:
How to avoid using Select/Activate in Excel VBA macros
So you can start to understand why your current code fails or performs undesired operation. What happens when there are no blank cells in your selection? You'll get an error. Why?
Because in that circumstance, Selection.SpecialCells(xlCellTypeBlanks) evaluates to Nothing. (You can verify this using some debug statements) And because Nothing does not have any properties or methods, you'll get an error, because you're really saying:
Nothing.Select
Which is a null program, does not grok, does not compute, etc.
So, you need to test for nothingness with something like this:
Sheets("xml").Select
Cells.Select
If Not Selection.SpecialCells(x1CellTypeBlanks) Is Nothing Then
Selection.SpecialCells(x1CellTypeBlanks).EntireRow.Delete
End If
I still suggest avoiding Select at all costs (it is superfluous about 99% of the time and makes for sloppy code which is difficult to debug and maintain).
So you could do something more complete following that line of thought:
Dim blankCells as Range '## Use a range variable.
'## Assign to your variable:
Set blankCells = Sheets("xml").Cells.SpecialCells(xlCellTypeBlanks)
'## check for nothingness, delete if needed:
If Not blankCells Is Nothing then blankCells.EntireRow.Delete
Follow-up from comments
So in VBA we are able to declare variables which represent objects or data/values, much like a maths variable in an equation.
A Range is a type of object part of the Excel object model, which consists of the Workbook/Worksheets/Cells/Ranges/etc. (far more than I could hope to convey to you, here)
http://msdn.microsoft.com/en-us/library/office/ff846392(v=office.14).aspx
A good example of why to use variables might be here if you scroll down to the "Why Use Variables" section.
http://www.ozgrid.com/VBA/variables.htm
This is of course very simple... but the reader's digest version is that variables allow us to repeatedly refer to the same object (or value for sipmle data types) without explicitly referring to it each time.
THen there is the handy side-effect that the code bcomes more easy to read, maintain and debug, when we use variables instead of absolute references:
Dim rng as Range
Set rng = Sheets(1).Range("A1:Q543").Resize(Application.WorksheetFunction.CountA(Sheets(1).Range("A:A"),))
Imagine that fairly (but not ridiculously) complicated range construct. If you needed to refer to that range more than once in your code, it would be silly not to assign it to a variable, if for no other reason than to save your own sanity from typing (and possibly mistyping a part of it). It is also easy to maintain, since you need only modify the one assignment statement and all subsequent references to rng would reflect that change.

Related

Application.Cells VS Application.ActiveSheet.Cells

The Macro Recorder generated the following statement:
Cells.Select
Now I understand that without the object qualifier this will return all the cells as a Range object.
However, I am wondering what the fully qualified version of this statement is?
Is it:
Application.Cells.Select
Application.ActiveSheet.Cells
Application.ActiveWorkbook.ActiveSheet.Cells
In other words, which one of those fully qualified statements is actually executed by VBE when it runs Cells.Select?
What is the difference between all of these??? As all of these access the same object in the end - is it just personal preference as to which statement I would use if I wanted to explicitly qualify all the objects?
Thank you so much!
It's complicated :)
As all of these access the same object in the end
True. Keywords "in the end". The difference is how many steps it takes to get there...
Unqualified Cells (or Range, Rows, Columns, Names, etc.) aren't magic, they're member calls (Property Get) against a hidden, global-scope object cleverly named Global:
You can validate that this hidden object is involved, by blowing up in a standard module:
Sub GoesBoom()
'throws error 1004 "Method 'Range' of object '_Global' failed"
Debug.Print Range(Sheet2.Cells(1, 1), Sheet3.Cells(1, 1))
End Sub
_Global and Global are closely related - without diving deep into COM, you can consider Global the class, and _Global its interface (it's not really quite like that though - look into "COM coClasses" for more information).
But Cells is a property of the Range class:
I think it's reasonable to presume that Global calls are pretty much all redirected to Application, which exposes all members of Global, and then some.
Now as you noted, Application also have a Cells property - but Cells belong on a Worksheet, so no matter what we do, we need to end up with a Worksheet qualifier... and then any worksheet belongs in a Worksheets collection, which belongs in a Workbook object - so we can infer that an unqualified Cells call would be, in fully-explicit notation, equivalent to... (drumroll):
Application.ActiveWorkbook.ActiveSheet.Cells
But you don't need to be that explicit, because ActiveSheet has a Parent that is always going to be the ActiveWorkbook, so this is also explicit, without going overboard:
ActiveSheet.Cells
But that's all assuming global context. This answer explains everything about it - the gist of it, is that if you're in a worksheet's code-behind, then an unqualified Cells member call isn't Global.Cells, but Me.Cells.
Now, note that Cells returns a Range. Thus, whenever you invoke it against a Range without providing parameters, you're making a redundant member call:
ActiveSheet.Range("A1:B10").Cells ' parameterless Range.Cells is redundant
Let's take the post apart:
Cells.Select
Now I understand that without the object qualifier this will return
all the cells as a Range object.
That's actually somewhat incorrect. While it is true that .Cells returns a Range.Cells object which returns all the cells, Cells.Select is actually a method of the Range object which - as you may have guessed - Selects the range (in our case, all the cells)
The Select method, as per MSDN actually returns a Variant and not a Range object.
That it is a pretty important distinction to make, especially if you plan on passing that value to anything. So if we pretended to be a compiler
Cells -> ActiveWorkbook.ActiveSheet.Range.Cells returns Range of all the cells
Range.Cells.Select -> first we take our returned Range, we then select the cells in Worksheet and actually return a Variant (not Range)
As to the other part of the question. It depends where your module is placed. By default, Cells is shorthand for the following statement:
Application.ActiveWorkbook.ActiveSheet.Range.Cells
This however is subject to change depending on where your module is placed and if Application, workbook or sheet has been modified.
In general, it is a good coding practice to always specify at least a specific Worksheet object whenever you're referencing a Range, eg.
Sheets("Sheet1").Range.Cells
This is explicit and therefore less error prone and clearer to comprehend, be it for you or anyone working with your code.. You always know what exactly you're working with and not leave it to guesswork.
Obviously, the moment you start working with multiple workbooks, it's a good idea to incorporate Workbook objects statements before the Sheet. You get my point.
Last but not least, whatever you're trying to do, it's probably for the best you avoid using Select. It's generally not worth it and prone to unexpected behaviour.
Check this question here: How to avoid using Select in Excel VBA
If you just type Cells - in and of itself it does nothing. It is the same as Range.Cells. The only advantage of Cells is that it can accept numeric value for column (second argument). It's very handy when you do complex manipulations.
Range.Cells just returns Range object. When you have Range object, think of it as a small Excel worksheet. Say, you have range Range("F3:J10"). Then following ranges all refer to H3 cell:
Range("F3:J10").Cells(3)
Range("F3:J10")(3)
Range("F3:J10").Cells(1, 3)
Range("F3:J10")(1, 3)
Range("F3:J10").Cells(1, "C")
Range("F3:J10")(1, "C")
Range("F3:J10").Range("C1")

VBA Word Why doesn't .BaseStyle stay capitalized? [duplicate]

For unknow reason my Excel VBA editor changes:
Cells(ActiveCell.Row, 1).Value = MyString
into
Cells(ActiveCell.row, 1).Value = MyString
Word "Row" should start with capital "R" but after I type it, it changes to small "r". I have checked the code and I am sure I do not use "raw" as a variable. The macro itself works fine as if it was written "Row". On other workbooks everything is ok (R is capitalized).
Anybody has idea why it happens?
I was also getting a bit tired of from looking where exactly I have declared a Row with a small letter, as far as it was not declared anywhere.
Thus, found a great solution - add the following in a module:
Public Sub TestMe
Dim Row as Long
End Sub
And see the whole code changing. Then you may delete it. Or simply write Dim Row as Long on a new line, somewhere in your code. And then delete it.
VBA isn't case sensitive, so I wouldn't lose too much sleep over it. The editor tries to convert all of the variable cases to however it was dimmed. Most likely the ActiveCell definition was screwed up somehow.
I used variable row in VBA of that worksheet. Then I changed the name of the variable row to something else like MyRowName Although there was no such variable as row in VBA anymore, it still kept lower case for that word. As I mentioned above everything worked fine i.e. ActiveCell.row returned what it should for ActiveCell.Row.
For just aesthetic reasons, I have copied the whole VBA to another worksheet and the bug was crunched. Row returned to Upper case.

Unused UDF being called when doing CalculateFullRebuild

I have some User Defined Functions in an Excel book. I used them for a while but, after a while, I deleted the calls to these functions from the cells because I found a better way to accomplish the same task (I didn't delete the function definition itself in the VBA editor). So, these functions are no longer being called neither in the book nor from any VBA code, I checked it using a search to be 100% sure.
Now I'm doing some review on my code and I noticed something strange: in a Sub procedure in the same workbook (which has nothing to do with these functions) I call Application.CalculateFullRebuild. When this happens those UDF get called, I can see it by setting a break point inside the UDF.
I'd like to know why is it happening and what can be done to avoid it, as it is slowing that Sub unnecessarily.
Thanks!
Application.CalculateFullRebuild MSDN reference has this to say:
The CalculateFullRebuild method is similar to re-entering all formulas. ... [When run] a full calculation of the data in all open workbooks is performed and the dependencies are rebuilt.
Further MSDN reference states:
Causes Excel to rebuild the dependency tree and the calculation chain
This means that any UDFs in the module code or sheet code will be recalculated because Excel is rebuilding and testing functions for dependency and use in the calculation chain.
If you are looking for a way to simply manually calculate the existing formulas in the sheet via your Sub, you can use 'Application.Calculate' (MSDN):
Application.Calculate 'for all open Sheets
Sheets("Name of Sheet").Calculate 'Specific Sheet
Sheets("Name of Sheet").Range("Name of Range").Calculate 'Specific Range
The system is working as it should. Consider:
Function qwerty() As String
qwerty = "qwerty"
MsgBox "XX"
End Function
It is non-Volatile and has no arguments. It will be calculated at the time it is entered in a worksheet cell. Application.Calculate may cause it to be calculated once, however:
Sub ytrewq()
Application.CalculateFullRebuild
End Sub
will cause the UDF to be re-calculated each time ytrewq is run.
To the moment my approach has been commenting all the code inside the UDF with two objectives: increasing speed on one side and checking if any side effect happened on the other side. To the moment, I have not observed any side effect, so more to the point that they are not being used anywhere.
Right now the application I'm developing is working quite well, but I'll try the solutions you're proposing just out of curiosity. By bets are on either it's being used somewhere hidden and forgoten or simply that I have some rubbish inide the workbook structure that is not getting cleaned.
Thanks!
Update
Tried again the next day and those UDF are no longer being called. Thus, I'll have to assume that something odd was going on with Excel that went away when I restarted it.
Anyway, thanks a lot for the Application.Caller thing, which I didn't know about.

Trouble with undefined Object in VBA Run-Time error '91'

I've been working all week to prepare a VBA application, which I'll be using in a meeting today. Unfortunately the code that has been running all week last week without a hitch, has decided to break over the weekend.
I constantly get Object variable or With block variable not set Run-time error '91' from this statement:
With Sheet5
Set adjrng = .Range(.Cells(.Range("G43:G60").Find(.Range("H39").Value).Row, 10), .Cells(.Range("G43:G60").Find(.Range("H39").Value).Row, 21))
End With
idea is to set a range in the row of the Range G43:G60 where the Value of H39 matches from Column 10 to Column 21.
Anybody spot the issue? My brainz are to nervous and sleepy this morning...
Thanks a bunch
Ben
EDIT:
After playing a bit with find and replace, the issue seems to be that excel has not yet properly calculated the "lookin" and "lookup" Ranges G43:G60 and H39. A simple recalculation didn't make excel rediscover the contents but when I used one of my input toggles to display a different value in those cells, and the went back to the original it did manage to find it.
Maybe using find for this is bad style, the find formula has these kind of hicups usually or any other comments on this? For now everything works fine again, but I'm afraid of running into these issues again. Any tips would thus still be appreciated.
EDIT: (from comment below)
We have a dynamic range (G43:J60) where unique identifiers are listed in column G and data is to the right. if something is changed in the data part of the range AND the lines uniqued identifier in column G matches the one in cell H39 a sub() is triggered via worksheet_ change intersect(target, adjrng) Defining that adjrng is the part that throws errors when find returns null.
I believe you are simply trying to set a range hoping that there will be two matches to the value in H39 within the G43:G60 range. While I avoid on Error Resume Next (never could adjust to the logic of breaking something in the hope to accomplish something), I always check that the values will be there when I look for them.
Dim rwUNIQ as long
Set adjrng = nothing
With Sheet5
if cbool(application.countif(.Range("G43:G60"), .Range("H39").Value)) then
rwUNIQ = application.match(.Range("H39").Value, .Range("G43:G60"), 0)
Set adjrng = .Cells(42 + rwUNIQ, 10).resize(1, 11)
end if
if not adjrng is nothing then
'do something with adjrng
end if
Set adjrng = nothing
End With
That checks to make sure that there are at least two H39 values in G43:G60 before proceeding. There is no further error control because we've counted at least two of them. You might want to compensate with an Else for when there isn't. If a single H39 value was found, you also might want to select a single row.
Remember that the .Find uses many parameters that were retained from the last time Find was used, whether in VBA or with a user on the worksheet. You have a real lack of parameters that specify the options that Find should use to proceed. e.g. xlPart or xlWhole, After:=what?, look in formulas or values, etc.
EDIT:* Modified the code to look for a single instance of the value in H39 and .Resize to expand the width (as per OP's comments).

Breaking down evaluate in vba

I've been searching online trying to figure out what the use of evaluate is. What I get from msdn is: " An expression that returns an object in the Applies To list." I have no clue what this means.
The reason I ask is because I've been given a piece of code and I'm trying to make some logical sense out of it. Why is it written this way? What is the advantage of using evaluate instead of a more traditional approach? What is the correct syntax of a long line of nested functions?
Here is my code example:
With Range("B1", Cells(Rows.Count, "B").End(xlUp))
.Value = Evaluate("Index(If(Left(Trim(" & .Address & "),1)=""."",Replace(Trim(" & .Address & "),1,1,""""),Trim(" & .Address & ")),)")
End With
Can someone help me break this down and make some sense out of it? It is supposed to remove leading periods and get rid of excess spaces in all cells in column b. My problem is that it only works if I run it twice. If I could make some sense out of this then I may be able to manipulate it to make it function correctly.
For extra credit, How would I build a statement like this if I wanted to go through the same range and remove all dashes ("-")?
I really want to learn. Any help appreciated.
OK here goes:
Evaluate tells the application to evaluate a function in this context.
The function is a string concatenation of "Index(If(Left(... which includes some dynamic components (.Address), because it's being applied to the entire range:
Range("B1", Cells(Rows.Count, "B").End(xlUp))
What this does, effectively, is to evaluate the formula for each cell in that range, but only writes the formula's evaluated value to each cell.
Equivalent would be to fill the range with the formula, and then do a copy + paste-special (values only) to the range. This is obviously more expensive in terms of memory and time consuming processes, especially for a larger range object.
I personally don't favor this approach, but that's a matter of my personal preference primarily.
Advantages in this case is that it's filling in an entire range of cells -- which could be 10 cells or 10,000 cells or 1,048,576 cells (in Excel 2007+) in one statement.
Alternative methods would be to do a For/Next loop (which is expensive in terms of memory and therefore slow on large ranges), even if you're doing a loop over an array in memory and writing the resulting array to the worksheet, I think there is a certain elegance to using a single statement like this code.