Deleting all empty rows in a table using a macro

S

schweie

I have seen the following code on the MVPS website, but this only looks at
the cells in the first column of the table, i need it to look at the second
column of the table. Is this possible?

****Public Sub DeleteEmptyRows()

Dim oTable As Table, oRow As Range, oCell As Cell, Counter As Long, _
NumRows As Long, TextInRow As Boolean

' Specify which table you want to work on.
Set oTable = Selection.Tables(1)
' Set a range variable to the first row's range
Set oRow = oTable.Rows(1).Range
NumRows = oTable.Rows.Count
Application.ScreenUpdating = False

For Counter = 1 To NumRows

StatusBar = "Row " & Counter
TextInRow = False

For Each oCell In oRow.Rows(1).Cells
If Len(oCell.Range.Text) > 2 Then
'end of cell marker is actually 2 characters
TextInRow = True
Exit For
End If
Next oCell

If TextInRow Then
Set oRow = oRow.Next(wdRow)
Else
oRow.Rows(1).Delete
End If

Next Counter

Application.ScreenUpdating = True

End Sub

Note that you could delete the empty rows from all tables in a document by
replacing the line:

Set oTable = Selection.Tables(1)

With the line

For Each oTable In ActiveDocument.Tables

and adding the line:

Next oTable

just before:

Application.ScreenUpdating = True*****

Thankyou.
 
J

John McGhie [MVP - Word and Word Macintosh]

The code you posted looks in "each" cell in each entire row.

The construct " For Each oCell In oRow.Rows(1).Cells" expands to "For each
of the cells in the entire row within the nominated range..."

I would have coded the variable as "rRow" rather than "oRow" because it is a
range, not an object. To my mind, that makes the code difficult for humans
to understand.

Cheers


I have seen the following code on the MVPS website, but this only looks at
the cells in the first column of the table, i need it to look at the second
column of the table. Is this possible?

****Public Sub DeleteEmptyRows()

Dim oTable As Table, oRow As Range, oCell As Cell, Counter As Long, _
NumRows As Long, TextInRow As Boolean

' Specify which table you want to work on.
Set oTable = Selection.Tables(1)
' Set a range variable to the first row's range
Set oRow = oTable.Rows(1).Range
NumRows = oTable.Rows.Count
Application.ScreenUpdating = False

For Counter = 1 To NumRows

StatusBar = "Row " & Counter
TextInRow = False

For Each oCell In oRow.Rows(1).Cells
If Len(oCell.Range.Text) > 2 Then
'end of cell marker is actually 2 characters
TextInRow = True
Exit For
End If
Next oCell

If TextInRow Then
Set oRow = oRow.Next(wdRow)
Else
oRow.Rows(1).Delete
End If

Next Counter

Application.ScreenUpdating = True

End Sub

Note that you could delete the empty rows from all tables in a document by
replacing the line:

Set oTable = Selection.Tables(1)

With the line

For Each oTable In ActiveDocument.Tables

and adding the line:

Next oTable

just before:

Application.ScreenUpdating = True*****

Thankyou.

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Business Analyst, Consultant
Technical Writer.
Sydney, Australia +61 (0) 4 1209 1410
 
S

schweie

Ok...so do you have any suggestions as to how i can change the code for it
only to look in the second column of the table?

Cheers


John McGhie [MVP - Word and Word Macinto said:
The code you posted looks in "each" cell in each entire row.

The construct " For Each oCell In oRow.Rows(1).Cells" expands to "For each
of the cells in the entire row within the nominated range..."

I would have coded the variable as "rRow" rather than "oRow" because it is a
range, not an object. To my mind, that makes the code difficult for humans
to understand.

Cheers


I have seen the following code on the MVPS website, but this only looks at
the cells in the first column of the table, i need it to look at the second
column of the table. Is this possible?

****Public Sub DeleteEmptyRows()

Dim oTable As Table, oRow As Range, oCell As Cell, Counter As Long, _
NumRows As Long, TextInRow As Boolean

' Specify which table you want to work on.
Set oTable = Selection.Tables(1)
' Set a range variable to the first row's range
Set oRow = oTable.Rows(1).Range
NumRows = oTable.Rows.Count
Application.ScreenUpdating = False

For Counter = 1 To NumRows

StatusBar = "Row " & Counter
TextInRow = False

For Each oCell In oRow.Rows(1).Cells
If Len(oCell.Range.Text) > 2 Then
'end of cell marker is actually 2 characters
TextInRow = True
Exit For
End If
Next oCell

If TextInRow Then
Set oRow = oRow.Next(wdRow)
Else
oRow.Rows(1).Delete
End If

Next Counter

Application.ScreenUpdating = True

End Sub

Note that you could delete the empty rows from all tables in a document by
replacing the line:

Set oTable = Selection.Tables(1)

With the line

For Each oTable In ActiveDocument.Tables

and adding the line:

Next oTable

just before:

Application.ScreenUpdating = True*****

Thankyou.

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Business Analyst, Consultant
Technical Writer.
Sydney, Australia +61 (0) 4 1209 1410
 
J

John McGhie [MVP - Word and Word Macintosh]

Sure. I hesitate because I don't want to dig you in too deep, especially
with code that "deletes things" :) You WILL test this in a COPY, right?
:)

The simple answer to your question is "Sure, look only in cell 2!". But to
do that, we have to restructure the original code so we return each row as a
"Row" object (which is a collection of cells) rather than as a "Range"
object, which is a span of text. Once we have each row as a collection of
cells, it's trivial to say we want to examine the contents of "Cell number
2".

The original code is presumably designed to do something else. It's not the
most efficient way of doing THIS task. Efficiency doesn't matter much in
VBA, because such routines normally do not run long enough for the lack of
it to be measurable. But not having any more code to maintain than you NEED
is goodness. As part of that, making the COMPILER do the work is greatness.
In that original code, there were several instances where the author is
creating things that already exist in the VBA environment. Under some
circumstances, there are reasons why you have to do things that way, and I
don't know what the original author's circumstances are. But for the simple
problem you are presenting here, the following is less code and easier to
read:

Sub DeleteRow()

Dim oTable As Table, oRow As Row, oCell As Cell, _
TextInRow As Integer

' Specify which table you want to work on.
Set oTable = Selection.Tables(1)

For Each oRow In oTable.Rows

StatusBar = "Row " & oRow.Index
If Not oRow.HeadingFormat Then
If Len(oRow.Cells(2).Range.Text) < 3 Then
'end of cell marker is actually 2 characters
oRow.Select
TextInRow = MsgBox("Delete this row", vbOKCancel)
If TextInRow = 1 Then oRow.Delete
End If
End If
Next ' oRow

End Sub

The above code stops at each row and asks. If you don't want it to do that,
then change it this way:

Sub DeleteRow()

Dim oTable As Table, oRow As Row, oCell As Cell

' Specify which table you want to work on.
Set oTable = Selection.Tables(1)

For Each oRow In oTable.Rows
StatusBar = "Row " & oRow.Index

If Not oRow.HeadingFormat Then
If Len(oRow.Cells(2).Range.Text) < 3 Then oRow.Delete
'end of cell marker is actually 2 characters
End If
Next ' oRow

End Sub

Notes:
1) You do not have to set a counter for most collections. The system
already maintains one. In this case, it's ".Index".

2) If all you want to do is iterate an entire collection, use a "For
Each..." loop. The Visual Basic Environment then declares the necessary
variable and constructs the control loop for you.

3) If you want to read the Status Bar, don't turn Screen Updating off :)

4) For most tables, you don't want to delete the heading rows, whether they
have text in them or not. So test for the "Heading Rows repeat" property
(named "HeadingFormat" in VBA, and skip the row if it has that.

5) By turning your test inside out (< 3 rather than > 2) you can save
yourself a branch and a lot of lines of code. So instead of storing the
state in the variable, we can simply hammer the row if cell 2 is blank.
Now, to see if you have been paying attention, "Why did the original author
do it the other way?" :)

6) This macro will run slowly anyway. Anything to do with tables crawls in
Word 2004. But you can make it "slightly" faster by adding a comment
character in front of the name of the control variable at the end of the For
loop. That turns the name of the loop into "text" which does not have to be
evaluated, rather than a "variable" which does have to be evaluated. You
will get a completely undetectable improvement in speed doing that, but you
will satisfy my perfectionist sickness :)

Cheers

Ok...so do you have any suggestions as to how i can change the code for it
only to look in the second column of the table?

Cheers


John McGhie [MVP - Word and Word Macinto said:
The code you posted looks in "each" cell in each entire row.

The construct " For Each oCell In oRow.Rows(1).Cells" expands to "For each
of the cells in the entire row within the nominated range..."

I would have coded the variable as "rRow" rather than "oRow" because it is a
range, not an object. To my mind, that makes the code difficult for humans
to understand.

Cheers


I have seen the following code on the MVPS website, but this only looks at
the cells in the first column of the table, i need it to look at the second
column of the table. Is this possible?

****Public Sub DeleteEmptyRows()

Dim oTable As Table, oRow As Range, oCell As Cell, Counter As Long, _
NumRows As Long, TextInRow As Boolean

' Specify which table you want to work on.
Set oTable = Selection.Tables(1)
' Set a range variable to the first row's range
Set oRow = oTable.Rows(1).Range
NumRows = oTable.Rows.Count
Application.ScreenUpdating = False

For Counter = 1 To NumRows

StatusBar = "Row " & Counter
TextInRow = False

For Each oCell In oRow.Rows(1).Cells
If Len(oCell.Range.Text) > 2 Then
'end of cell marker is actually 2 characters
TextInRow = True
Exit For
End If
Next oCell

If TextInRow Then
Set oRow = oRow.Next(wdRow)
Else
oRow.Rows(1).Delete
End If

Next Counter

Application.ScreenUpdating = True

End Sub

Note that you could delete the empty rows from all tables in a document by
replacing the line:

Set oTable = Selection.Tables(1)

With the line

For Each oTable In ActiveDocument.Tables

and adding the line:

Next oTable

just before:

Application.ScreenUpdating = True*****

Thankyou.

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Business Analyst, Consultant
Technical Writer.
Sydney, Australia +61 (0) 4 1209 1410

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Business Analyst, Consultant
Technical Writer.
Sydney, Australia +61 (0) 4 1209 1410
 
J

JE McGimpsey

This macro will run slowly anyway. Anything to do with tables crawls in
Word 2004.

You can make the macro much faster (at least twice as fast in my test of
99 rows that needed to be deleted) by iterating from the last row to the
first. Probably something about repagination being faster:

Public Sub DeleteRow()
Dim i As Long
With ActiveDocument.Tables(1).Rows
For i = .Count To 2 Step -1
StatusBar = "Row " & i
With .Item(i)
If Len(.Cells(2).Range.Text) < 3 Then .Delete
End With
Next i
End With
End Sub

And I hate to disagree with John, but I really discourage commenting out
the control variable after Next. It's only evaluated once (at compile
time), not each loop, and while in this case there's only one variable,
when you have complex macros with multiple loops, having the compiler
verify that you're iterating the right loop can save a lot of time and
effort if you inadvertently make a logic error in your code. And what
you do with that 100 microseconds, anyway?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top