Deleting columns from a table with merged cells - vba

I'm trying to delete columns from a table which has horizontally merged cells
Selection.MoveEnd Unit:=WdUnits.wdCell, Count:=3
Selection.Columns.Delete
Eventhough columns are getting deleted, merged cells are removed in the process leaving a broken table.
Almost similar approach to delete rows works fine as explained in this answer
Workaround
I'm doing something like this as work around
Selection.MoveEnd Unit:=WdUnits.wdCell, Count:=3
Selection.MoveDown Unit:=WdUnits.wdLine, Count:=2, Extend:=wdExtend
Selection.Cells.Delete
Then setting width of Cell at index 1,2 to rest of the table rows. This way you can avoid merged cell getting deleted.

Word tables are not always intuitive. If a cell spans a column that is to be deleted, then the ENTIRE spanned cell will always be deleted, as you have shown.
When I'm NOT using VBA, I always unmerge cells before deleting rows or columns; otherwise Word's behavior is hard to predict.
Using VBA I would suggest the following:
'splits the header row of the current table into 7 cells
Selection.tables(1).cell(1,2).split numrows:=1, numcolumns:=7
'your code to delete columns
Selection.MoveEnd Unit:=WdUnits.wdCell, Count:=3
Selection.Columns.Delete
'merge the header row back into one span
ActiveDocument.Range( _
start:= Selection.tables(1).cell(1,2).range.start, _
end := Selection.tables(1).cell(1,5).range.end _
).cells.Merge
or for a more general approach, to delete n columns:
width = Selection.tables(1).columns.count - 1
Selection.tables(1).cell(1,2).split numrows:=1, _
numcolumns:= width - 1
Selection.MoveEnd Unit:=WdUnits.wdCell, Count:= n
Selection.Columns.Delete
ActiveDocument.Range( _
start:= Selection.tables(1).cell(1,2).range.start, _
end := Selection.tables(1).cell(1,width-n-1).range.end _
).cells.Merge

This should do it
Sub DeleteCols()
Dim Col2Delete As Range
Set Col2Delete = Selection.EntireColumn
Col2Delete.Delete
End Sub

First of all, we must look at the table not as rows/columns, but as indexed cells. (fig.1)
Now, we should select a range that starts from second cell and end to the last cell.
Finally, delete selection columns
Set Rng = Tbl.Range.Cells(2).Range
Rng.End = Tbl.Range.Cells(Tbl.Range.Cells.Count).Range.End
Rng.Select
Selection.Columns.Delete

Related

Word Copying, Pasting and Deleting Columns with VBA

I have a word document with tables throughout.
I have a macro that I use to copy values from cells in certain columns and paste them into other columns. Then the macro deletes several columns from the table.
Generally I use bookmarks for the copying and pasting.
The Bookmarks are all cells in a specific column, except the header.
So for example "Objectives" bookmark would span from row2 of column 2 of table5, until the end of column2. So if the user adds a row, that would also be included in the bookmark.
Here is an example below:
If ActiveDocument.Bookmarks.Exists("ProposedOverallObj") = True Then
ActiveDocument.Bookmarks.Item("ProposedOverallObj").Select
Selection.Cut
Selection.GoTo What:=wdGoToBookmark, Name:="Objectives"
With ActiveDocument.Bookmarks
.DefaultSorting = wdSortByName
.ShowHidden = False
End With
Selection.PasteAndFormat (wdPasteDefault)
End If
Dim Table1 As Table
Set Table1 = ThisDocument.Tables(5)
If Table1.Columns.Count >= 5 Then
With Table1
.Columns(5).Delete
.Columns(4).Delete
.Columns(3).Delete
End With
Else
MsgBox "Not enough columns!"
End If
and it goes on like this for about 13 tables. The first few tables work fine, then i start running into a weird error where randomly the line of text above my table gets copied into the table header and then my "delete columns" code breaks and tells me that one of the columns has already been deleted. (it hasn't).
Is there a better way to do this? Might this be happening because I am using .Select? I know it can be sketchy. Would it be better to assign IDs to my column ranges rather than using bookmarks and select? if so, could you provide me with a small example code for using Dim on a range in a table (my range will always start on row2 and go to the end of the column)?

Excel VBA script filters header unexpectedly

Introduction
I have some spreadsheets like the following.
Here the header is on rows 16 and 17. There is a "header" to the left (not shown) among the earlier rows and columns that includes a picture, some non-tabular data, a legend, etc., that is unimportant here. Header text on row 16 is obfuscated because reasons. Data marked in bold red indicates that that sample point has undergone some process. Here is the code snippet from the script that highlights those data points in bold red.
' Traverse columns applying redding until hitting the row end, Comment, or SpGr: whichever comes first
For currIndex = abcDateCol + 1 To lastCol
' Check for exit conditions:
If Cells(abcDateRowDesc, currIndex).Value() = "Comments" Then Exit For
If Cells(abcDateRowDesc, currIndex).Value() <> "" Then
If Cells(abcDateRowDesc, currIndex + 1).Value() = "process" Then
' Looks like we have a column of something Red-able
Columns(ColumnLetter(currIndex) & ":" & ColumnLetter(currIndex + 1)).Select
Selection.AutoFilter ' Turn on autofiltering (hopefully)
Selection.AutoFilter Field:=2, Criteria1:="=1", Operator:=xlOr, Criteria2:="=e"
Selection.Font.ColorIndex = 3
Selection.Font.Bold = True
Selection.AutoFilter ' Turn off autofiltering
Columns(ColumnLetter(currIndex + 1) & ":" & ColumnLetter(currIndex + 1)).EntireColumn.Delete Shift:=xlToLeft
End If
End If
Next currIndex
Context
Here, abcDateCol refers to column AE, lastCol refers to column AQ, abcDateRow (not shown, but available) and abcDateRowDesc refer to the header rows 16 and 17 respectively, and the ColumnLetter function is a user-defined function that returns the human-readable column letter(s) given a column number; this is common functionality you may have seen elsewhere, or even made yourself.
Let's Continue
Never mind that the condition in If Cells(abcDateRowDesc, currIndex).Value() = "Comments" is never satisfied because of an oversight (I'm assuming) -- two different rows, guaranteed.
Let's take a look at what the spreadsheet looks like before this script is executed.
So, the script takes pairs of columns, and for each pair of columns it marks data cells bold red if a data cell's right-adjacent cell has a 1 (or an "e"?) (as a boolean; answers the question, "Has this sample point undergone whatever process?") and then trashes the "process" column.
The Problem
A client wants the gratuitous header gone, so they may more easily import the spreadsheet into whatever solution they have. Delete rows 1 through 15, and this is what I get.
What in the bleepity-bleep happened to the header? I don't understand how this first row gets highlighted. It seems too perfectly weird. Now, let's revisit the very first spreadsheet.
I've filled the "header" with some dummy text after the script executed. Wow, there's the first row reddened again, this time ad infinitum! So, this problem has always existed. Oh, and the first column, too! And, it magically stops right above the proper header so we would never see it.
The Questions
Why is this script unexpectedly reddening the first row and column? Can this be easily solved, or am I looking at some sort of a rewrite? If so, please point me in the general direction.
It helps to mention that these spreadsheets are generated from a Windows application and their scripts executed before a user has a copy of their spreadsheet. Also, regarding the second picture (the spreadsheet with the "process" columns shown), this spreadsheet is not something that normally exists. I generated it for the sake of this post by skipping the script's for loop. The application uses a chosen spreadsheet template, that looks the same minus the data, fills in the sample data, and then executes several scripts over the data.
I considered using conditional formatting, but there are a few dozen spreadsheet templates. Even if I just change the one I need, I can't change the fact that these common scripts run over it. I feel my best option is to correct the script. And, I wouldn't change the script to account for my edge case. The whole ecosystem feels flaky, but that's just subjective.
Note
I am not the author of this script (or any of my company's VBA!). I'm considering this an inheritance tax levied upon me.
*Update
I was asked if I traced through this code. I apologize that I didn't include that information in my original post. Here is what I know. Selection.Font.ColorIndex = 3 turns the cells in the selection that satisfy the autofilter plus the first row (two cells as only two columns are selected at a given time), and Selection.Font.Bold = True makes the same cells bold in the same manner. I suspect it has something to do with the autofilter, so I'm going to take a look at the answers now.
This edit should fix your problems, hopefully (they did for my remake of your spreadsheet, but we won't know til you try on the real thing)
' Traverse columns applying redding until hitting the row end, Comment, or SpGr: whichever comes first
For currIndex = abcDateCol + 1 To lastCol
' Check for exit conditions:
If Cells(abcDateRowDesc, currIndex).Value() = "Comments" Then Exit For
If Cells(abcDateRowDesc, currIndex).Value() <> "" Then
If Cells(abcDateRowDesc, currIndex + 1).Value() = "process" Then
' Looks like we have a column of something Red-able
'Columns(ColumnLetter(currIndex) & ":" & ColumnLetter(currIndex + 1)).Select
With Range(Cells(abcDateRowDesc, currIndex), Cells(abcDateRowDesc, currIndex + 1).End(xlDown))
.AutoFilter 2, "=1", xlOr, "=e"
' Don't format header
With .Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count) .Font.ColorIndex = 3
.Font.Bold = True
.AutoFilter ' Turn off autofiltering
End With
End With
Columns(currIndex + 1).Delete xlShiftLeft
End If
End If
Next currIndex
This all starts with a quirk of how the code is choosing its range to autofilter. The selected area is the full column, instead of the area you actually want to format (row 18 to the last entry). It seems that autofiltering on a full column with empty top rows automatically sets the first nonempty row as the header row. So the header is left unfiltered/unhidden by the statement, and it gets colored in as part of the full column selection. So that's why your headers are getting colored.
Now, if you tried to test this by putting data in the above empty rows like "a", those values would become the first ones in the column and would be selected as the headers - meaning those values get colored. Whatever is in the first nonempty row of your columns will be the autofilter header and will get colored.
But that should only affect the columns you explicitly colored, not the entirety of the first row, right? The problem here is that Excel likes to make assumptions about data in order to save time. So if you have a whole row full of red, bold "a"'s and right next to them you put in another "a" to test whether that cell is formatted or not... well, it automatically gives you a red, bold "a" despite the cell being previously unformatted! And if you keep going down the row in this way, it'll appear like your whole row got formatted. But, if you were to jump over a few columns (say, 5-ish) and enter in another "a", voila, it's unformatted, and any "a"s you put in near it will be too. You can also check what Excel by deleting an unformatted "a" in a far off column, then continuing to enter "a"'s all the way down until you reach that same cell - this time, the "a" will be red and bold because all of the others in the row were, too, even though we just checked that this was an unformatted cell!
Basically, having the wrong range for your autofilter made things act very unexpectedly, then trying to test the formatting issue by entering in values just made everything less clear. The code I've provided just autofilters the relevant area (row 17 to the last contiguous row), fixing the core issue.
here's a (commented) refactoring of your code that should do:
Option Explicit
Sub main()
Dim abcDateCol As Long, lastCol As Long, abcDateRow As Long, abcDateRowDesc As Long, currIndex As Long
abcDateCol = 31
lastCol = 43
abcDateRow = 16 '<--| you can change it to 1 for the last "scenario"
abcDateRowDesc = 17 '<--| you can change it to 2 for the last "scenario"
For currIndex = abcDateCol + 1 To lastCol '<--| loop through columns
With Cells(abcDateRow, currIndex) '<--| refer to cell in current column on row abcDateRow
If .Value = "Comments" Then Exit For '<--| Check for exit conditions on row 'abcDateRow'
If .Offset(1).Value <> "" And .Offset(1, 1).Value = "process" Then '<--| Check for processing conditions on row 'abcDateRowDesc'
With .Resize(.Offset(, 1).End(xlDown).Row - .Row + 1, 2) '<-- consider the range from current referenced cell 1 column to the right and down to last 'process' number/letter
.AutoFilter Field:=2, Criteria1:="=1", Operator:=xlOr, Criteria2:="=e" '<--| filter on "process" field with "1" or "e"
If Application.WorksheetFunction.Subtotal(103, .Cells.Resize(, 1)) > 1 Then '<--| if any values match...
With .Offset(2).Resize(.Rows.Count - 2, 1).SpecialCells(xlCellTypeVisible).Font '<--|... consider only filtered values skipping headers (2 rows), and apply formatting
.ColorIndex = 3
.Bold = True
End With
End If
.AutoFilter '<-- reset autofilter
.Resize(, 1).Offset(, 1).EntireColumn.Delete Shift:=xlToLeft '<-- delete the "2nd" column (i.e. one column offsetted to the right)
End With
End If
End With
Next currIndex
End Sub
there were two faults in your "inherited" code:
If Cells(abcDateRowDesc, currIndex).Value() = "Comments" Then Exit For was to be referred to abcDateRow index row instead
the formatting would be applied to all cells, were they filtered (matching) or not

vba: delete filtered rows but not first one (or store it and paste after deletion)

Working on Microsoft Visual Basic Application Edition 7.1 in Excel 2013
Data are on columns from A to D, rows' number varies from time to time. I would like to delete all rows for which column B's value doesn't start with LCR (and also I would like not to bore with a for...next loop).
Something like:
Columns("B:B").AutoFilter Field:=1, Criteria1:="<>LCR*"
Selection.Delete
Unfortunately, this code deletes heading row (row number 1) and I don't want.
I tried to store row number 1 elsewhere in a range variable, but it doesn't work (run-time error '424')
Set r1 = Range("A1:D1")
r1.Copy
Columns("B:B").AutoFilter Field:=1, Criteria1:="<>LCR*"
Selection.Delete
With Range("A1:D1")
.Insert Shift:=xlDown
.Select
.Value = r1.Value
End With
How can I tell the filter to start from row number two (or how can I correctly store content of row number one so to paste it after deletion by filter)?
Thanks in advance for your help
Define your range for deleting as Range(Cells(2,2),Cells(ActiveSheet.UsedRange.Rows.Count,2)) (To replace the Selection call). This will delete everything except for the first cell in the column
Edit to avoid excel prompt: Range(Cells(2,2),Cells(ActiveSheet.UsedRange.Rows.Count,2)).EntireRow
in a more complete way you could go like this
Sub main()
With Worksheets("MyWantedSheet") '<--| always specify full worksheet reference (change "MyWantedSheet" with your actual sheet name)
With .Columns("B:B") '.Resize(.Cells(.Rows.Count, "B").End(xlUp).Row) '<--| refer to wanted column range down to its last non empty cell
.AutoFilter '<--| remove possible preeeding autofilter filtering
.AutoFilter Field:=1, Criteria1:="<>LCR*" '<--| apply current filtering
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then '<--| if there are visible cells other than the "header" one
.Resize(.Parent.Cells(.Parent.Rows.Count, "B").End(xlUp).Row - 1).Offset(1).SpecialCells(xlCellTypeVisible).EntireRow.Delete '<--|delete visible rows other than the first ("headers") one
End If
End With
.AutoFilterMode = False '<--| remove drop-down arrows
End With
End Sub

VBA code to merge two cells together

My scenario is a document with one two-column table. I am reading from a database and populating each row/column, but for some records, I want to merge the two columns into one cell and populate that as a row, then after that, continue the two column scenario. I want to issue a VBA statement to merge the two cells together to make one cell across the entire row. This is impossible to "record" as a new macro. I don't know what the row number is at run time, I only know the column numbers. My search string is: '"Word 2013" VBA table merge cells' but I get a lot of site that show you how to do it manually, not with VBA code.
My current code:
With ActiveDocument.Tables(1)
.Cell(Row:=1, Column:=1).merge _
MergeTo:=.Cell(Row:=1, Column:=2)
.Borders.Enable = True
End With
In the document, I have a small table of one row and two columns. But I should be able to have a table of three rows with any mixture of columns, right? I just want to pick any two columns and merge them together to make one space, but at run-time I do not know what row number to provide.
I finally got this worked out, and I want to again thank you for your help. Here is my current code. The entire document is one two-column table, skinny, and includes hyperlinks so that it can be read and navigated on an iPhone. When a Unit value changes in the data, I want to 1) insert a new row below and merge it into one column, add a 'Go Home' link, then continue adding two-column rows.
The document is finally converted to a .PDF and accessed by iOS users.
'Add a row for a 'back to home' link
If (intUnitOrder > intCurrentUnit) Then
Selection.InsertRowsBelow (1)
rowno = Selection.Information(wdEndOfRangeRowNumber) - 1
With ActiveDocument.Tables(1)
.Cell(Row:=rowno, Column:=1).Merge MergeTo:=.Cell(Row:=rowno, _
Column:=2)
End With
Selection.Tables(1).Rows(rowno).Range.ParagraphFormat. _
Alignment = wdAlignParagraphRight
Selection.Shading.BackgroundPatternColor = RGB(230, 230, 230)
Selection.Font.ColorIndex = wdBlue
Selection.Font.Italic = True
ActiveDocument.Hyperlinks.Add Anchor:=Selection.Range, _
Address:="#Home", SubAddress:="", _
ScreenTip:="Go back to the top", _
TextToDisplay:="back to Home"
Selection.MoveDown wdLine, 1
intCurrentUnit = intUnitOrder
End If

Excel: Copy and insert rows on another sheet based on cell

I'm trying to make a code that checks for numbers in a master sheet called All in column D (ex. 780101) and if it meets the criteria, it copies the whole row and inserts (not paste) it to another sheet with the name of the criteria (ex. 780101), starting on row 6.
The code I have doesn't work like I want it to. It doesn't copy all the rows that meet the criteria and sometimes it inserts blank rows.
Sub Insert()
For Each Cell In Sheets("All").Range("D:D")
If Cell.Value = "780101" Then
matchRow = Cell.Row
Rows(matchRow & ":" & matchRow + 1).Select
Selection.Copy
Sheets("780101").Select
Rows("6:6").Select
Selection.Insert Shift:=xlDown
End If
Next
End Sub
I'm just starting to learn VBA, so if it could be possible the names of the sheets would be the criteria of the cell values (the code is made for only one sheet - 780101, but there are 20 of sheets with different names).
It's tough to make recommendations without seeing sample data and what could potentially be causing the problems you are having but you can run this rehash of your existing code.
Sub Insert()
Dim dc As Range
With Sheets("All")
For Each dc In Intersect(.Range("D:D"), .UsedRange)
If dc.Value2 = 780101 Then
dc.Resize(2, 1).EntireRow.Copy
Sheets("780101").Rows(6).Insert Shift:=xlDown
End If
Next
End With
End Sub
The nature of running that from top to bottom means that the results will be reversed. You may wish to consider running the main loop from bottom to top to maintain the order.