Change Word table format from Excel - excel

I have a macro in Excel that creates a Word where some Excel tables are copied, and I have this code for format changing:
Set WordTable = myDoc.Tables(i)
With WordTable
.AutoFitBehavior (wdAutoFitWindow)
.Shading.Texture = wdTextureNone
.Shading.BackgroundPatternColor = wdColorWhite
.Range.Font.TextColor = wdColorBlack
.Range.ParagraphFormat.SpaceAfter = 0
End With
Everything works properly except when the reference "Microsoft Word Object Library" is not set. In this case, the shading turns black for some reason. Is there any way to solve it, apart from set this reference?
The problem is that this macro is part of a bigger Excel program that the user installs in their own PCs, so shouldn't be able to work with VBA.

The issue is, if you use that code in Excel and "Microsoft Word Object Library" is not set, Excel does not know the Word constants wdAutoFitWindow, wdTextureNone, wdColorWhite and wdColorBlack. Instead Excel will treat them as variables and since you did not initialize them each of it has the value 0.
Make sure you use Option Explicit so you get notified if you use something that is not defined!
I recommend always to activate Option Explicit:
In the VBA editor go to Tools › Options › Require Variable Declaration.
To solve the issue, you need to either replace them with their actual value (you can find the values for the enumerations here: Enumerations (Word)) or define them as constants in Excel.
Option Explicit
Public Sub Example
Const wdAutoFitWindow As Long = 1
Const wdTextureNone As Long = 0
Const wdColorWhite As Long = 16777215
Const wdColorBlack As Long = 0
Set WordTable = myDoc.Tables(i)
With WordTable
.AutoFitBehavior wdAutoFitWindow
.Shading.Texture = wdTextureNone
.Shading.BackgroundPatternColor = wdColorWhite
.Range.Font.TextColor = wdColorBlack
.Range.ParagraphFormat.SpaceAfter = 0
End With
End Sub
You can also define them outside the scope of a procedure if you plan to use them in multiple procedures/functions. Make sure to use Option Explicit on top of every module to ensure all variables are declared properly or you will quickly run into issues again.

Related

Find / replace text in embedded word object code stopped working

I have used this code successfully to replace content in an embedded word object from excel. I copied the code for a new excel file but now it doesn't work. It opens the file but doesn't replace although I can see that it IS finding the right text and replacement text. I'm kind of lost as to what is happening.
Dim strFindText As Range
Dim strReplaceText As Range
Dim nSplitItem As Long
Set strFindText = ActiveWorkbook.Worksheets("Utilisation Form").Range("c11:c20")
Set strReplaceText = ActiveWorkbook.Worksheets("Utilisation Form").Range("a11:a20")
nSplitItem = strFindText.Count
Debug.Print strFindText.Item(0)
For Each sh In ThisWorkbook.Sheets("Utilisation Form").Shapes
If sh.Name <> "Object 1" Then sh.Delete
Next
Set urobj = ThisWorkbook.Sheets("Utilisation Form").OLEObjects("Object 1")
Set wordtemp = urobj.Duplicate
wordtemp.Verb Verb:=xlOpen
Set wordtemp2 = wordtemp.Object
For x = 1 To nSplitItem
With wordtemp2.Content.Find
.Forward = True
.Text = strFindText.Item(x)
.ClearFormatting
.Replacement.Text = strReplaceText.Item(x)
.Execute Replace:=wdReplaceAll
End With
Next x
End Sub
Thanks for the support
When the early-binding technology is used in the code you need to add a corresponding COM reference to be able to use data types. Otherwise, you need to declare everything from the Word object model as Object in the code and use the late-binding technology.
To use early binding on an object, you need to know what its v-table looks like. In Visual Basic, you can do this by adding a reference to a type library that describes the object, its interface (v-table), and all the functions that can be called on the object. Once that is done, you can declare an object as being a certain type, then set and use that object using the v-table. For example, if you wanted to Automate Microsoft Office Excel using early binding, you would add a reference to the Microsoft Excel X.0 Object Library from the Project|References dialog, and then declare your variable as being of the type Excel.Application. From then on, all calls made to your object variable would be early bound.
Read more about that in the Using early binding and late binding in Automation article.

What about "Application" as default object in Excel VBA?

I have just written this easy macro in Excel VBA for merging a group of selected cells:
Sub Macro_Merge()
Dim Temp As String
Dim S As Variant
Temp = ""
For Each S In Selection
If Temp = "" Then
Temp = CStr(S.Value)
Else:
Temp = Temp + "," + CStr(S.Value)
End If
Next
Selection.Merge
Selection.Value = Temp
Selection.VerticalAlignment = xlTop
End Sub
This works fine, but I always see that annoying dialog box, warning me about loosing data while merging (which is exactly what I'm trying to avoid in my macro).
I can get rid of that dialog box, configuration the Application's DisplayAlerts property:
Application.DisplayAlerts = False
Selection.Merge
Selection.Value = Temp
Application.DisplayAlerts = True
This is working fine.
So, as Application is the default object, I tried to clean up my code, as follows:
DisplayAlerts = False
Selection.Merge
Selection.Value = Temp
DisplayAlerts = True
As you see, I simply omit mentioning the Application object. This is something which is allowed and I've done in the past. (If not in VBA, then Delphi, maybe?)
... but to my surprise, the dialog box appears again (although pressing F1 brings me to the official "Application.DisplayAlerts" documentation).
This leaves me with a simple question:
If a simple DisplayAlerts = ... does not equal Application.DisplayAlerts = ... anymore, what does it mean and how can I use it?
For your information, I'm working with Excel-365.
DisplayAlerts is an undeclared variable.
Certain Application properties and methods can (effectively) have the Application omitted:
ActiveCell, ActiveSheet, ActiveWorkbook, ActiveWindow, Addins, Charts, Selection, etc.
Calculate, Evaluate, Intersect, Run, Union, etc.
(but see this answer why/how this works):
A boolean property such as DisplayAlerts (EnableEvents, ScreenUpdating, etc) doesn't fall into the above category.
A golden rule in order not to fall into such a trap is the usage of Option Explicit while writing macros.
Just to add some information to the answer of #BigBen. If you write something like Workbooks or ActiveSheet in your code, VBA is not looking into the Application-object - it is looking into a (rather well hidden) object named Global.
The global object is exposing some (but not all) properties and methods of the Application-object, so ActiveSheet is referring to Application.ActiveSheet - but not because the Application has a member with this name but because the Global object defines that ActiveSheet means Application.ActiveSheet. In fact even the Application-object is accessed via the Global object.
There is hardly any information about this Global object or its concept. I found a page from Microsoft describing the Global object of MS Word, but the only explanation there is "Contains top-level properties and methods that don't need to be preceded by the Application property.". For Excel, I found this page on O'Reilly.
From time to time you get strange error messages like "Excel VBA Method 'Range' of object'_global' failed" - this is a pointer to the Global object. I would be glad to learn more about the concepts and mechanics of this object, but I am afraid that there are only very few people around that know more (except of course Mathieu Guindon AKA Mr. Rubberduck...). In daily life, we take it for granted that things like ActiveSheet simply works.

Update an Excel style in VBA

My Excel macro reads the answers to a survey from a set of Excel files. The answers of a survey contain a score (from 1 to 4) and a description. The goal is to generate a a matrix. Each cell of the matrix has a color that represents the score. I would like the user to be able to modify the layout of these cell. To make it easy to the user, I created a template matrix and a button. The user should be able to modify the layout of the cells and on a click of a button, a set of styles (Score 1, Score 2,...) should be generated. Once the matrix is created, the Workbook should be to function without the survey files.
I have tried a couple of things:
Try 1
ThisWorkbook.Styles.Add "Score 1", BasedOn:=cell1
This gives errors. I don't fully understand when they occur, but one of the causes is when the user modifies the cell layout by selecting another style.
Try 2
ThisWorkbook.Styles("Score 1").Delete
ThisWorkbook.Styles.Add "Score 1", BasedOn:=cell1
This is not a good idea: all cells loose their styling when it is executed a second time.
Try 3: Current
Copy the most frequently used properties of the cells layout and copy them to the style. If this style is deleted by the user, it is recreated. This procedures is not ideal, since most style properties won't be covered.
Is there a way to update a cell style that is more general? I would like there to be as little room as possible to make the workbook in an inconsistent and non-functional state.
I sticked with try 3. Because it required a lot of code for all properties that seemed possible to be edited, and because of copying borders is tricky, I post the result.
'xR1_Template: the cell to base the style on
'nm_Style: the name of the style
Public Function Upsert_Style(xR1_Template As Excel.Range, nm_Style As String) As Excel.Style
Dim xStyle As Excel.Style
Set xStyle = Fn.TryGet(ThisWorkbook.Styles, nm_Style)
If Fn.IsNothing(xStyle) Then
Set xStyle = ThisWorkbook.Styles.Add(nm_Style)
End If
xStyle.Font.Color = xR1_Template.Font.Color
xStyle.Font.Bold = xR1_Template.Font.Bold
xStyle.Font.Name = xR1_Template.Font.Name
xStyle.Font.Italic = xR1_Template.Font.Italic
xStyle.Font.Size = xR1_Template.Font.Size
xStyle.Font.Strikethrough = xR1_Template.Font.Strikethrough
xStyle.Font.Subscript = xR1_Template.Font.Subscript
xStyle.Font.Superscript = xR1_Template.Font.Superscript
xStyle.Font.Underline = xR1_Template.Font.Underline
xStyle.Interior.Color = xR1_Template.Interior.Color
xStyle.Interior.Pattern = xR1_Template.Interior.Pattern
xStyle.Interior.PatternColor = xR1_Template.Interior.PatternColor
'NOTE: necessary to delete all borders first. There's no way to delete them one by one.
xStyle.Borders.LineStyle = xlNone
Dim iBorder As Long
For iBorder = 1 To xR1_Template.Borders.Count
Dim xBorder As Excel.Border
'NOTE: The Borders property claims to work with xlBordersIndex argument, but this is not true.
' Normal indexing is used.
Set xBorder = xR1_Template.Borders(iBorder)
'NOTE: "none-style" borders (=no border), should be skipped.
' Once they are retrieved using the Borders property, they are always visible.
' Setting them with xlLineStyle.xlLineStyleNone does not hide them.
If xBorder.LineStyle <> XlLineStyle.xlLineStyleNone Then
Dim xBorder_Style As Excel.Border
Set xBorder_Style = xStyle.Borders(iBorder)
xBorder_Style.Color = xBorder.Color
xBorder_Style.LineStyle = xBorder.LineStyle
xBorder_Style.Weight = xBorder.Weight
End If
Next iBorder
xStyle.AddIndent = xR1_Template.AddIndent
xStyle.FormulaHidden = xR1_Template.FormulaHidden
xStyle.HorizontalAlignment = xR1_Template.HorizontalAlignment
xStyle.IndentLevel = xR1_Template.IndentLevel
xStyle.NumberFormat = xR1_Template.NumberFormat
xStyle.NumberFormatLocal = xR1_Template.NumberFormatLocal
xStyle.Orientation = xR1_Template.Orientation
xStyle.ShrinkToFit = xR1_Template.ShrinkToFit
xStyle.VerticalAlignment = xR1_Template.VerticalAlignment
xStyle.WrapText = xR1_Template.WrapText
xStyle.IndentLevel = xR1_Template.IndentLevel
Set Upsert_Style = xStyle
End Function

Some doubts about Dim and Set Worksheet

Normally, Dim should be done first and then Set should be done in vba code, like the below code.
Dim xWs1 As Worksheet
Dim xWs2 As Worksheet
.
.
.
Dim xWsN As Worksheet
Set xWs1 = Worksheets("A")
Set xWs2 = Worksheets("B")
.
.
.
Set xWsN = Worksheets("NNN")
If I need to Dim and Set 5 worksheets, 10 lines of code will be needed. It seems that the code will be too long when I need to Dim and Set more than 5 worksheets.
I found that if I just use Set, the vba code can also run properly. I would like to ask this will cause any problems if I didn't use Dim?
Set xWs1 = Worksheets("A")
Set xWs2 = Worksheets("B")
.
.
.
Set xWsN = Worksheets("NNN")
Thanks!
If you don't use dim statement , variable is automatically created as a Variant type.
The Variant type can be an integer, a string, a workbook, or any of the other type of variable and it can change as the variable changes, one moment it can be a string, then it can be changed to a workbook.
Using Dim
Without using Dim
There are mainly two problems with not using Dim,
Variant types uses more computer memory as a result it will make your code slow especially when you use loops.
Difficult to find errors ( in your case you can assign anything to variable XWs1 such as numbers , names etc. which can be avoided if you use dim)
How to Declare and Set 92 Objects using 92 Characters:
If "taking up space" is your concern, and you hypothetically want to explicitly set 5 procedure-level variables (w1..w5) to Set to 5 worksheets (Sht1..Sht5), you could use:
Example #1:
DefObj W
Sub wSet1()
Dim w1, w2, w3, w4, w5
Set w1=[Sht1]:Set w2=[Sht2]:Set w3=[Sht3]:Set w4=[Sht4]:Set w5=[Sht5]
End Sub
...or, even more space-efficient, if for example, you had 92 worksheets to Set in 92 declared variables? Then:
Example #2:
DefObj W
Sub wSet2():Dim wks(1To 92),x:For x=1To 92:Set wks(x)=Sheets("Sht"&x):Next x:End Sub
(That's 92 characters... not counting the line feed!)
Explanation:
Between these two ways of shortening declaration, we're using six shortcuts. Below is a summary of each, and see the links under "More Information" for full documentation on each.
Disclaimer: There are a number of reasons we shouldn't use shortcuts in programming. The obvious one is that, the more you compress code, the harder it is to read and understand (especially by others), and therefore can be harder to troubleshoot or expand upon.
If you don't know what the "standard methods" are, do not learn the shortcuts first! Learn how to do things "THE RIGHT WAY" before learning the shortcuts, no matter how appealing it may seem. There was a time that I argued that neatness like indentation and commenting, and full, proper techniques, didn't matter. I was wrong; had to learn that the hard way. If you're reading this, you'll probably have to learn the hard way too, but at least:
Don't use shortcuts when posting example code in your Stack Overflow questions. (This is not a method of [MCVE]!) You will probably get yelled at! ...and possibly have you questions down-voted or ignored... You were warned!
✓ DefObj (Default Data Types)
[Deftype statements][1] are a forgotten method of declaring default data types. Normally, the default data type is [`Variant`][2], so this:
Dim myVariable as Variant
...is identical to:
Dim myVariable
...however the DefObj W statement (used at module-level) says:
All variables declared in this module, that start with the letter 'W' default to type Object (unless otherwise specified). Note that Deftypes statements must be used at module-level (before your first Sub).
The entire list: (More Info)
DefBool DefByte DefCur DefDate DefDbl DefDec DefInt DefLng DefSng DefStr DefObj DefVar
✓ , (Commas in 'Dim' Statements)
When declaring variables with Dim, multiple variables can be listed on the same line, separated with a comma. Therefore this:
Sub mySub()
Dim myVariable1 as Currency
Dim myVariable2 as Currency
…
...is identical to this: (combining examples with Deftypes)
DefCur m
Sub mySub()
Dim myVariable1, myVariable1
…
✓ Sheets ('Sheets' collection)
The WorkSheets Object refers to the collection of all the Worksheet objects in the specified or active workbook.
The Charts Object` refers to the collection of **all the Chart objects in the specified or active workbook.
But the **Sheets Objectrefers to ***both*** theWorksheets*and*Charts` collections.
So, if a workbook has 3 worksheets and 2 chart sheet, in VBA:
Sheets.Count will return 5
Worksheets.Count will return 3
Warning: Using Sheets could cause a conflict if you have a Chart and a Worksheet with the same name (and should also be avoided when referring to worksheets in other files). But for a simple single-file, worksheet-only workbook, save yourself some Work and stick with just Sheets.
✓ [ ] (Square-Bracket Reference Shortcuts)
[Square brackets] can be used as a shortcut when referring to Worksheets, Cell Ranges and individual Cells. You can use either the A1 Reference Style or a named range within brackets as a shortcut for the Range property. You do not have to type the word "Range" or use quotation marks.
Worksheets("Sheet1").[A1:B5].ClearContents
[MyRange].Value = 30
This is barely documented, and even less documented is the fact that, if used in the logical order, square brackets can be used to refer to worksheets.
Combining examples, all of these statements will have identical result:
Worksheets("Sheet1").Range("A1") = Now()
Sheets("Sheet1").Range("A1") = Now()
Worksheets("Sheet1").[A1] = Now()
Sheets("Sheet1").[A1] = Now()
[Sheet1].[A1] = Now()
✓ wks() (Variable Arrays)
If you have a large number of similar objects to declare, it's often easier (and more organized) to group them together in an array. An array can be declared as any type including, for example, Object, Worksheet. (...or even the rarely-used and bizarre types like LongLong and IConverterApplicationPreferences. (Apparently whoever thought up that one doesn't care for shortcuts.)
✓ For..Set..Next (Loop to Set Variable Arrays)
When using an array of objects (any any variable sets), the next logical step is to reduce code with any tasks that need to be performed on the entire group of objects.
Other Notes:
Example #1 could have been compressed to one line but I wanted it to be easy to read in the answer. If our sheet names were S1..S5 instead of the oh-so-lengthy Sht1..Sht5, and we use the :, we could accomplish the same thing in 105 characters:
Example #1b:
DefObj W
Sub wSet():Dim w1,w2,w3,w4,w5:Set w1=[S1]:Set w2=[S2]:Set w3=[S3]:Set w4=[S4]:Set w5=[S5]:End Sub
Data Type Shortcut Symbols
Another rarely used set of dates back to 1974: data type shortcuts chosen by Gary Kildall for the CP/M Operating System
Symbol  Data Type  Constant                                                            
% Integer vbInteger = 2
$ String vbString = 8
& Long vbLong = 3
# Decimal vbDecimal = 6
! Single vbSingle = 4
# Double vbDouble = 5
Still supported today in many coding languages, you could for example use these interchangeably:
Dim myVariable as String
Dim myVariable$
More Information:
Microsoft.com : How to Break and Combine Statements in Code (VB/VBA)
MSDN : Refer to Cells by Using Shortcut Notation
Excel Hero : Excel VBA Shortcut Range References
MSDN : Using Data Types Efficiently
MSDN : Dim Statement (VBA)
ExcelHowTo : Worksheets vs. Sheets
Stack Overflow : Difference between Worksheets & Worksheet objects
MSDN : Set Statement
MSDN : Declaring Arrays
Take the following example of why using implicit variable declaration is usually a bad idea:
Sub Test()
myVariable = 10
myOutcome = myVaraible + 5
End Test
myOutcome = 5. Can you see why?
I misspelled myVariable in the second line, so I just essentially created a brand new variable myVaraible (which had a default value of 0).
This is why you should always use Option Explicit at the beginning of every module; and why you should always explicitly declare all variables.
While it still works, you are just setting yourself up for needless debugging headaches.
If your issue is that you want to condense your code to use less lines, you can do something like this:
Option Explicit
Sub Test()
Dim myVariable As Long: myVariable = 10
Dim myOutput As Long
myOutput = myVariable + 5
End Sub
You can also declare multiple variables on the same line:
Option Explicit
Sub Test()
Dim myVariable As Long, myOutput As Long
myVariable = 10
myOutput = myVariable + 5
End Sub
Not necessarily recommending this (as it can degrade readability), but it's yet another method of declaring variables. This does require the same data type, but you can add your worksheets in an array (from your example):
Option Explicit
Sub Test()
Dim xWs(1 To 5) As Worksheet
Set xWs(1) = Worksheets("A")
Set xWs(2) = Worksheets("B")
Set xWs(3) = Worksheets("C")
Set xWs(4) = Worksheets("D")
Set xWs(5) = Worksheets("E")
End Sub

Excel VBA Global Range variable out of scope after sub procedure completes

I am perplexed why my global variable within a module fall out of scope at the conclusion of a sub procedure.
I declare the range at the top of the module out side of all subproc and functions
as below
Option Explicit
Dim TIMEDATA As Range
Dim FREQDATA As Range
Const StartLoc = "B4"
Const flowLoc = "F4"
Const dtLoc = "J8"
In my subproc I define one of the ranges.
Public Sub PortandConvertData()
<SNIP>
Set TIMEDATA = calcSheet.Range(Cells(2, 2).Address, Cells(2 + dataSize, 2).Address)
End Sub
After the sub completes in the watch window I see the variable TIMEDATA go from
Range/Range to Range and the value go from correct to simply out of context.
I want to store data in the module rather than pasting in a sheet or something.
Any help is much appreciated
Make sure that the Context in the Watch properties includes the Procedure/Module you are actually watching. You can make sure by setting the context to All Modules:
From the Watches panel: Right Click the Expression --> Edit Watch --> From the Context group set Procedure/Module to All.
If this is not the actual issue, then you are having the same issue I'm having from Access VBA.
This thread explains the same:
ThisWorkbook not holding global variable value to cancel ontime()

Resources