I have a lot of large tables in automatically generated word documents and I want to delete columns that have no values in them. They will all have headers, so essentially I need to check the values of rows 2 to the end or just get the whole column in a string and check after the first chr(14) which I understand is the column marker.
Is this possible without looping through the cells, row by row, which appears to be slow, or selecting the column which seems to cause the screen to have issues and the UI freezes, sometimes crashing Word.
What you want to do is perfectly possible but can run into an issue. There is a difference in the number of cells in the selection range reported (and consequently the text to process) depending on whether you use
selection.cells
or
selection.range.cells
The former works as expected, the latter does not.
The code below deletes columns in the way in which you describe and also includes debug.print statements to demonstrate the problem.
I've tested the code on a 5x6 table. Your experience may differ.
Sub DeleteEmptyTableColumns(this_table As Word.Table)
Dim my_cells As Range
Dim my_column As Long
Dim my_text As String
Dim my_last_row As Long
' Assumes that the Table is uniform
my_last_row = this_table.Rows.Count
Application.ScreenUpdating = False
With this_table
For my_column = .Columns.Count To 1 Step -1
DoEvents
Set my_cells = .Range.Document.Range( _
Start:=.Cell(2, my_column).Range.Start, _
End:=.Cell(my_last_row, my_column).Range.End)
' We have to use selection to get the correct text
my_cells.Select
Debug.Print
' Wrong numbers and text
Debug.Print my_cells.Cells.Count
Debug.Print my_cells.Text
' Correct information
Debug.Print Selection.Cells.Count
Debug.Print Selection.Text
' Back to the wrong information
Debug.Print Selection.Range.Cells.Count
Debug.Print Selection.Range.Text
my_text = Selection.Text
' Add other replacments as required.
my_text = Replace(my_text, " ", vbNullString)
my_text = Replace(my_text, vbCrLf, vbNullString)
my_text = Replace(my_text, Chr$(13), vbNullString)
my_text = Replace(my_text, Chr$(7), vbNullString)
If Len(my_text) = 0 Then
this_table.Columns(my_column).Delete
End If
Next
End With
Application.ScreenUpdating = True
End Sub
Related
I have a code like so:
Sub MoveToBeginningSentence()
Application.ScreenUpdating = False
Dim selectedWords As Range
Dim selectedText As String
Const punctuation As String = " & Chr(145) & "
On Error GoTo ErrorReport
' Cancel macro when there's no text selected
Selection.Cut
Selection.MoveLeft Unit:=wdSentence, Count:=1, Extend:=wdMove
Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Set selectedWords = Selection.Range
selectedText = selectedWords
If InStr(selectedText, punctuation) = 0 Then
Selection.MoveLeft Unit:=wdSentence, Count:=1, Extend:=wdMove
Selection.Paste
Else
Selection.MoveLeft Unit:=wdSentence, Count:=1, Extend:=wdMove
Selection.Paste
Selection.Paste
Selection.Paste
Selection.Paste
End If
ErrorReport:
End Sub
Basically, it help me move whatever text I have selected to the beginning of the sentence in Word. If there's no quotation mark, then paste once. If there is a quote mark, paste 4 times.
The problem is regardless of whether there's any quotation there or not, it will only paste once. If I set the macro to detect any other character, it will work fine. But every single time I try to force it to detect smart quotations, it will fail.
Is there any way to fix it?
Working with the Selection object is always a bit chancy; on the whole, it's better to work with a Range object. You can have only one Selection; you can have as many Ranges as you need.
Because your code uses the Selection object it's not 100% clear what the code does. Based on my best guess, I put together the following example which you can tweak if it's not exactly right.
At the beginning, I check whether there's something in the selection, or it's a blinking insertion point. If no text is selected, the macro ends. This is better than invoking Error handling, then not handling anything: If other problems crop up in your code, you wouldn't know about them.
A Range object is instantiated for the selection - there's no need to "cut" it, as you'll see further along. Based on this, the entire sentence is also assigned to a Range object. The text of the sentence is picked up, then the sentence's Range is "collapsed" to its starting point. (Think of this like pressing the left arrow on the keyboard.)
Now the sentence's text is checked for the character Chr(145). If it's not there, the original selection's text (including formatting) is added at the beginning of the sentence. If it's there, then it's added four times.
Finally, the original selection is deleted.
Sub MoveToBeginningSentence()
Application.ScreenUpdating = False
Dim selectedText As String
Dim punctuation As String
punctuation = Chr(145) ' ‘ "smart" apostrophe
Dim selRange As word.Range
Dim curSentence As word.Range
Dim i As Long
' Cancel macro when there's no text selected
If Selection.Type = wdSelectionIP Then Exit Sub
Set selRange = Selection.Range
Set curSentence = selRange.Sentences(1)
selectedText = curSentence.Text
curSentence.Collapse wdCollapseStart
If InStr(selectedText, punctuation) = 0 Then
curSentence.FormattedText = selRange.FormattedText
Else
For i = 1 To 4
curSentence.FormattedText = selRange.FormattedText
curSentence.Collapse wdCollapseEnd
Next
End If
selRange.Delete
End Sub
Please check out this code.
Sub MoveToBeginningSentence()
' 19 Jan 2018
Dim Rng As Range
Dim SelText As String
Dim Repeats As Integer
Dim i As Integer
With Selection.Range
SelText = .Text ' copy the selected text
Set Rng = .Sentences(1) ' identify the current sentence
End With
If Len(SelText) Then ' Skip when no text is selected
With Rng
Application.ScreenUpdating = False
Selection.Range.Text = "" ' delete the selected text
Repeats = IIf(IsQuote(.Text), 4, 1)
If Repeats = 4 Then .MoveStart wdCharacter, 1
For i = 1 To Repeats
.Text = SelText & .Text
Next i
Application.ScreenUpdating = True
End With
Else
MsgBox "Please select some text.", _
vbExclamation, "Selection is empty"
End If
End Sub
Private Function IsQuote(Txt As String) As Boolean
' 19 Jan 2018
Dim Quotes
Dim Ch As Long
Dim i As Long
Quotes = Array(34, 147, 148, -24143, -24144)
Ch = Asc(Txt)
' Debug.Print Ch ' read ASCII code of first character
For i = 0 To UBound(Quotes)
If Ch = Quotes(i) Then Exit For
Next i
IsQuote = (i <= UBound(Quotes))
End Function
The approach taken is to identify the first character of the selected sentence using the ASC() function. For a normal quotation mark that would be 34. In my test I came up with -24143 and -24144 (opening and closing). I couldn't identify Chr(145) but found MS stating that curly quotation marks are Chr(147) and Chr(148) respectively. Therefore I added a function that checks all of them. If you enable the line Debug.Print Ch in the function the character code actually found will be printed to the immediate window. You might add more character codes to the array Quotes.
The code itself doesn't consider spaces between words. Perhaps Word will take care of that, and perhaps you don't need it.
You need to supply InStr with the starting position as a first parameter:
If InStr(1, selectedText, punctuation) = 0 Then
Also
Const punctuation As String = " & Chr(145) & "
is going to search for space-ampersand-space-Chr(145)-space-ampersand-space. If you want to search for the smart quote character then use
Const punctuation As String = Chr(145)
Hope that helps.
In Word I am trying to compare the text in a specific cell in a table to a string and struggling.
Sub aMacro()
'
' aMacro Macro
'
'
Dim t As Table
Dim doc As Word.Document
For Each t In ActiveDocument.Tables
Dim Cell As String
Cell = t.Cell(1, 1).Range.Text
If StrComp(Cell, "Name") Then 'This one probably does as well but I haven't tested it yet
Dim Name As String
Dim Amount As String
Dim Value As String
Dim valueInt As Integer
Name = t.Cell(2, 1).Range.Text
Value = t.Cell(2, 3).Range.Text
Amount = t.Cell(2, 4).Range.Text
valueInt = 0
If StrComp(Value, "Monday") Then 'this always activates, regardless of what I compare
valueInt = 5
Selection.TypeText Text:="This is here"
ElseIf StrComp(Value, "Tuesday") Then
valueInt = 4
End If
Selection.TypeText Text:="Name: " + Name 'These all print out with the correct values from the supplied tables
Selection.TypeText Text:="Amount: " + Amount
Selection.TypeText Text:="Value: " + Value
Selection.TypeText Text:=valueInt
End If
Next
End Sub
I can get the values of the cells into variables but I cant seem to do any comparison on these variables. I also can't seem to use the .Value function that a lot of stack overflow posts suggest since it seems to be only available in Excel.
Does anyone happen to know how to compare the text from a table cell to a predefined string? I am using these comparisons to identify which tables contain the required data in a document so if there is a better way that would also be helpful, however I would still need to know how to do the comparisons for later use.
If you are trying to determine if the "Value" is Monday, this would be the syntax:
If Value = "Monday" Then
If you are trying to determine if Value Contains Monday, use Instr
If Instr(Value, "Monday") > 0 Then
I'm struggling a bit with CheckSpelling in Excel. I have a merged cell that I want to check, but only this cell. Here's what I'm doing.
ActiveSheet.Unprotect strSheetPassword
Application.DisplayAlerts = False
Set ma = Range("B21").MergeArea
ma.MergeCells = False
Union(Range("B1"), ma(1, 1)).CheckSpelling
ma.MergeCells = True
Application.DisplayAlerts = True
ActiveSheet.Protect strSheetPassword
It's checking the cell I want, but it's also checking the rest of the document. In reading other posts, I got the impression that checking a single cell causes CheckSpelling to check the entire document. This is why I put in the Union with the Range("B1") - B1 contains header text that doesn't have any misspellings and is normally locked, so that users can't change it. But, it is still checking the rest of the sheet! I've tried quite a few variations on this, but it still keeps checking the rest of the sheet.
CONCLUSION
I had been under the impression that it was possible to invoke the CheckSpelling form and have it only check certain cells. Apparently, this isn't true. Instead of building my own form, I should be able to get away with checking the whole sheet each time, although I really don't like that. Thanks for all the feedback!
For a single merged cell:
Sub spell_me()
Dim b As Boolean
b = Application.CheckSpelling(Word:=ActiveCell.Text)
MsgBox b & vbCrLf & ActiveCell.Address & vbCrLf & ActiveCell.Text
End Sub
EDIT#1:
To find the miscreant word, you could Split() the text into individual words and check each word.
If it is enough if the wrong part gets highlighted you can use this:
Sub SpellCheck()
Dim response As Boolean
Dim words As Variant
Dim wordCount As Long
Dim startAt As Long
words = Split(ActiveCell.Text, " ")
'set all of the text to automatic color
ActiveCell.Font.ColorIndex = xlAutomatic
For wordCount = LBound(words) To UBound(words)
response = Application.CheckSpelling(word:=words(wordCount))
If Not response Then
'find out where it is in the text and color the font red
startAt = InStr(ActiveCell.Text & " ", words(wordCount) & " ")
ActiveCell.Characters(Start:=startAt, Length:=Len(words(wordCount))).Font.Color = vbRed
End If
Next
End Sub
I have a couple of tables and want to replace column 2 or column 5 (if it exists) with check boxes.
If there is an asterisk in the cell, I want the check box checked = True.
If there's no asterisk, the cell will only be a unchecked check box. These check boxes are from the developer tab, under controls, legacy forms.
I researched but failed:
replacing an asterisk with a check box (checked)
limiting it to a specific column (see image)
replacing a blank cell with a check box (unchecked)
limiting the action to a specific column (2 and 5 (if it exists))
Dim oCell As Cell
Dim oRow As Row
For Each oRow In Selection.Tables(1).Rows
For Each oCell In oRow.Cells 'this won't work specifically with my example, needs to be a little more specific
If oCell.Range.Text = "*" Then
MsgBox oCell.RowIndex & ", " & oCell.ColumnIndex & " check it!"
'I don't how to put in a check box here
End If
Next oCell
Next oRow
'I want to combine the top code and code below...right?
'do for each cell in column 2
With ActiveDocument.FormFields.Add(Range:=ActiveDocument.Selection, Type:=wdFieldFormCheckBox)
If cellvalue = "" Then 'just verbal logic here
.CheckBox.Value = False
End If
If cellvalue = "*" Then 'just verbal logic here
.checkbox.Value = True
End If
End With
Here's how I would do this:
Dim objDoc As Document
Dim oCell As Cell
Dim oCol As Column
Dim objTable As Table
Dim bFlag As Boolean
Set objDoc = ActiveDocument
Set objTable = Selection.Tables(1)
'This may or may not be necessary, but I think it's a good idea.
'Tables with spans can not be accessed via the spanned object.
'Helper function below.
If IsColumnAccessible(objTable, 2) Then
For Each oCell In objTable.Columns(2).Cells
'This is the easiest way to check for an asterisk,
'but it assumes you have decent control over your
'content. This checks for an asterisk anywhere in the
'cell. If you need to be more specific, keep in mind
'that the cell will contain a paragraph return as well,
'at a minimum.
bFlag = (InStr(oCell.Range.Text, "*") > 0)
'Delete the content of the cell; again, this assumes
'the only options are blank or asterisk.
oCell.Range.Delete
objDoc.FormFields.Add Range:=oCell.Range, Type:=wdFieldFormCheckBox
'Set the value. I found some weird results doing this
'any other way (such as setting the form field to a variable).
'This worked, though.
If bFlag Then
oCell.Range.FormFields(1).CheckBox.Value = True
End If
Next oCell
End If
'Then do the same for column 5.
Public Function IsColumnAccessible(ByRef objTable As Table, iColumn As Integer) As Boolean
Dim objCol As Column
'This is a little helper function that returns false if
'the column can't be accessed. If you know you won't have
'any spans, you can probably skip this.
On Error GoTo IsNotAccessible
IsColumnAccessible = True
Set objCol = objTable.Columns(iColumn)
Exit Function
IsNotAccessible:
IsColumnAccessible = False
End Function
I have a VBA script designed to strip non-breaking spaces out of a table in Microsoft Word by searching for NBSPS and then replacing them with regular spaces and then using trim to clean up spaces before and after text. As it stand, the code will work for everything except trimming the first cell in a table (it does however, replace the spaces in the first cell). I was hoping to find out if there are any unique behaviors to Word/VBA that might cause this as I will be making several macros for tables in MS Word.
Sub nbsbCleanupQuick()
' use find and replace to replace all nbsp in the table with regular spaces
' use trim to clean up unwanted spaces
' For some reason this macro will replace nbsp with regular spaces but will not trim in the first cell of a table
' vars for counting
Dim numRows, numCells As Integer
Dim rindex, cindex As Integer
'container for cell contents
Dim container As String
Selection.Tables(1).Select
numRows = Selection.Rows.Count
For rindex = 1 To numRows
numCells = Selection.Tables(1).Rows(rindex).Cells.Count
For cindex = 1 To numCells
container = ""
Selection.Tables(1).Rows(rindex).Cells(cindex).Range.Select
With Selection.Find
.Text = Chr(160)
.Replacement.Text = Chr(32)
.Execute Replace:=wdReplaceAll, Wrap:=wdFindContinue
End With
If (Len(Selection.Text) - 2 > 0) Then
container = Left(Selection.Text, Len(Selection.Text) - 2)
End If
Selection.Text = Trim(container)
Next
Next
End Sub
Any help would be appreciated :)
Because you are at the beginning of bigger project I'll give you some advices with some more changes of your code. See proposed solution below with some comments explaining reasons for changes inside the code. Obviously this solves the problem you described in your question.
Sub nbsbCleanupQuick()
' use find and replace to replace all nbsp in the table with regular spaces
' use trim to clean up unwanted spaces
' For some reason this macro will replace nbsp with regular spaces but will not trim in the first cell of a table
' vars for counting
Dim numRows, numCells As Integer
Dim rindex, cindex As Integer
'container for cell contents
Dim container As String
Selection.Tables(1).Select
numRows = Selection.Rows.Count
'do replacement once, at the beginning
'it will be more efficient option
ActiveDocument.Tables(1).Select
With Selection.Find
.Text = Chr(160)
.Replacement.Text = Chr(32)
.Execute Replace:=wdReplaceAll ', Wrap:=wdFindContinue
'keep wrap parameter switch off to do replacement only within table 1
End With
'to iterate through all cells in table use this kind of loop
Dim tblCell As Cell
For Each tblCell In ActiveDocument.Tables(1).Range.Cells
'it's not necessary to select but I didn't want to
'change your solution completly
tblCell.Select
'this check is easier to understand
If (Len(Selection.Text) > 2) Then
container = Left(Selection.Text, Len(Selection.Text) - 2)
End If
Selection.Text = Trim(container)
Next
End Sub