Word VBA shape forecolor wdThemeColorAccent2 shows as ThemeColor 1 in the menu - vba

I wrote some macro code in Word (Office 365) to set the color of a shape outline to one of the theme colors. The code for doing that to a shape looks like this:
shape.line.foreColor.ObjectThemeColor = wdThemeColorAccent2
By assigning a 'wdXX' color to the ObjectThemeColor field, the color of the line around the shape will automatically change when the document ColorTheme is changed.
My problem (or the first weirdness) is that when I assign Accent2 with the code above and then do: select the shape, Menu, Format, Shape Outline, and hover over the color box with a red outline (which marks the active line color), the tooltip says "Turquoise, Accent 1" not "Accent 2."
I would have expected the wdThemeColorAccent2 color to be called Accent 2 in the tooltip, but it is not.
The second problem is that there is apparently no way for me to assign the last color shown in the menu using macro code. Because of the offset (Accent 2 in code = Accent 1 in the menu), I would need to use wdThemeColorAccent7 in code to assign the last color shown in the menu (labeled Accent 6 in the tooltip).
I'm wondering if this is a bug in Word (it sure looks like it to me), or if I am doing something wrong. To reproduce the situation, I created a simple empty rectangle, selected it, and ran the line of code above to change the outline color of the shape. Here's a little subroutine that illustrates the problem (select your shape before running the subroutine).
Sub TestAccent()
Dim shp As Shape
Set shp = selection.ShapeRange(1)
shp.line.foreColor.ObjectThemeColor = wdThemeColorAccent4
shp.line.Weight = 0.5
shp.line.Visible = True
End Sub

I believe the colors in the "theme scale" (see image below) don't correspond to the names of the WdThemeColorIndex, but rather to the underlying numerical value. If you look in the VBA Editor's Object Browser (F2), and search wdThemeColorAccent you'll get the full list. Click on a member in the list and at the bottom you'll see the numerical value.
The value 0 is assigned to MainDark1 and isn't recognized by VBA. Values 1, 2 and 3 are assigned to MainLight1, MainDark2 and MainLight2 which are black, white and the first entry in the image (These colors repeat in the last four enumerations for background and text). Values 4 (wdThemeColorAccent4) through 9 (wdThemeColorAccent6) correspond to the remainder of the colors in the image below. (Note: more discussion after image!)
So, no, I don't think it's a bug, just your expectations don't match what the developers were thinking when they assigned the numerical enumeration to the enumeration names. Or maybe the people who designed the color schemes changed their minds after the VBA code was locked down... And I imagine the names you see in the tooltips are another step removed from the VBA. You might find the information in this article helpful.
If you use the values, rather than the names, things could be less confusing. Or, define your own Enum:
Public Enum ColorSchemeAccents
Accent1 = 3
Accent2 = 4
Accent3 = 5
Accent4 = 6
Accent5 = 7
Accent6 = 8
Accent7 = 9
Accent8 = 10
End Enum
Sub TestAccent()
Dim shp As Shape
Set shp = Selection.ShapeRange(1)
shp.Line.ForeColor.ObjectThemeColor = ColorSchemeAccents.Accent8
shp.Fill.ForeColor = RGB(250, 250, 250)
shp.Line.Weight = 2
shp.Line.Visible = True
End Sub

Although the ColorFormat object's .ObjectThemeColor is defined as a wdThemeColorIndex in fact the value depends on context.
If it is a Word object - such as text, then you should use the wdThemeColorIndex constants, but if it is an Office object - such as shape, then you have to use the msoThemeColorIndex constants. These are weirdly NOT the same - mostly the mso constants are one more than the wd constants, but not for the Background1&2 and Text1&2 cases - Text1&2 are the same in both cases, but for Background1&2 mso is two more than wd.
A side effect of this is that it appears impossible in VBA to set the Background2 colour, as its mso value is 16 and so out-of-range BUT if you use the native GUI to set it, it can be set to 16!
Looks really poor design/implementation that needs cleaning up!

Related

How can I use VBA code to access auto-populated shades of Mso Theme Accent colors?

I'm writing a VBA Powerpoint macro to change the fill color of a text box to a shade of my Office theme's Accent 1 color. If I wanted to make the fill color the Accent 1 color itself, I would write it like this:
ActivePresentation.Slides(1).Shapes(1).Fill.ForeColor.SchemeColor = MsoThemeAccent1
However, instead of the Accent 1 color, I want the auto-generated "Accent 1, Lighter 80%" shade from the palette. Here is the accent shade I'm trying to access with my code.
I want to write the macro so the color is always the "Lighter 80%" shade value of the Accent color, so that it's dynamic if a different theme is chosen (i.e. don't want to write it as a fixed RGB value). The problem is that I don't see these shade values enumerated in the ColorScheme Index, and these shades are not uniform .TintAndShade values across themes. Is there a way to call a theme's accent colors' shades by name rather than RGB code?
You cannot do this with one command, you need to set 2 properties of ForeColor: The SchemeColor-property (as you do already) and the TintAndShade-property. You set the TintAndShade-Value for 80% as value 0.8:
With ActivePresentation.Slides(1).Shapes(1).Fill.ForeColor
.SchemeColor = msoThemeAccent1
.TintAndShade = 0.8
End With
Set TintAndShade to 0 will set the color to the "original" Theme color, set it to 1 (=100%) will result in white. You can use any value between 0 and 1 for the TintAndShade-value.
Setting the color using ForeColor (and TintAndShade) will automatically adjust the RGB value. However, it's not vice versa when you use the RGB-property to set the color: the ForeColor-property is set to 0 when you do so. This has a consequence when you change the Design-Palette: When you used ForeColor, the colors will be adapted to the new palette. When you used RGB, the color will stay the same.
Hint: To figure those things out, switch to Excel and use the Macro recorder. The shape model is the same for both Excel and Powerpoint and you can see in the generated code which properties you have and how to use them.
Update: I thought I understood colors in VBA, turns out I missed something. Stupid me did a shortcut and formatted a cell in Excel, not a shape. Found out that using the exact same values for SchemeColor and TintAndShade on a cell and on a Shape result in different colors... To get the "80% lighter" into a Shape, you have to use the property Brightness. The TintAndShade-value should be set to 0.
Also, instead of using .SchemeColor, you can use .ObjectSchemeColor - however, I didn't notice any difference using the one or the other.
With ActivePresentation.Slides(1).Shapes(1).Fill.ForeColor
.ObjectThemeColor = msoThemeAccent1
.TintAndShade = 0
.Brightness = 0.8
End With

How do i change the picture displayed on a label in a user form?

I'm putting together a VBA form in Word 2007, and am trying to change the picture field of a label based on a flag variable. As i understand, the picture field is a string comprising the path of a bitmap. When i try to set it equal to a string variable containing an image path, i get a type mismatch compile error. I have tested, and both strings do point to images, complete with '.bmp' extension. The offending code follows:
If flag1 Then
Makashi.Back1.Picture = dark
Else
Makashi.Back1.Picture = bright
End If
If it matters, Back1 is a label on the Makashi user form.
What am i doing wrong? Is this impossible, and i should instead make two overlapping labels, one of them occasionally invisible? Is there some other way to do this?
Assuming dark and bright are path\file strings (e.g. dark = "c:\test.bmp"), try:
If flag1 Then
Makashi.Back1.Picture = LoadPicture(dark)
Else
Makashi.Back1.Picture = LoadPicture(bright)
End If

In Word VBA, how can I correctly set font colours that use the theme colour tints?

I am writing a VBA macro which sets the colour of one piece of text to match the colour another piece of text. I am having difficulty when the text is coloured with a tint of one of the theme colours.
Option Explicit
Sub ChangeRangeColour()
Dim rSourceColourRange As Range
Set rSourceColourRange = ActiveDocument.Range(Start:=10, End:=20)
Dim rDestinationRange As Range
Set rDestinationRange = ActiveDocument.Range(Start:=30, End:=40)
rDestinationRange.Font.Color = rSourceColourRange.Font.Color
End Sub
I'm using .Font.Color, although it's undocumented, because .Font.ColorIndex doesn't seem to work correctly with custom colours. If there is another property I should be using instead, please let me know.
This works fine for custom colours, standard colours and basic theme colours. When the text is coloured with a tint of one of the theme colours, the destination text is changed to the theme colour with no tint. How can I get the tint along with the colour?
Well, .Font.Color certainly used to work, at least in WD2007 - WD2013. It returned a long value that was either the specific RGB, for non-theme colours, or the equivalent of the HEX value of the combination of theme color and tint (or shade). Tony Jollans wrote a detailed article for Word 2007 that gives all the gory details of theme colours.
.Font.Color is undocumented because it has been deprecated and from testing in Word 365 I can confirm that it no longer works like it used to. It now only returns the theme colour not the tint or shade.
#Cindy Meister suggested .Font.TextColor which sounds as though it should give the values you seek. Unfortunately, although it returns a ColorFormat object complete with .ObjectThemeColor, .Brightness and .TintAndShade properties, the only valid information you will get is from .ObjectThemeColor, so that will not give you what you need.
To get all the information you require you have to set logic aside and examine .Font.Fill.ForeColor. (Like, really?! Font has a fill colour??) This will also return a ColorFormat object but this time all the properties return valid data.
You should therefore change:
rDestinationRange.Font.Color = rSourceColourRange.Font.Color
to:
With rDestinationRange.Font.Fill.ForeColor
.ObjectThemeColor = rSourceColourRange.Font.Fill.ForeColor.ObjectThemeColor
.Brightness = rSourceColourRange.Font.Fill.ForeColor.Brightness
End With
For completeness, the .Brightness property returns a value of -1 for 100% (i.e. no tint or shade), 0.6 for 40% lighter, 0.4 for 60% lighter, etc. Negative values are used for shades with -0.75 representing 25% darker and -0.5 50% darker.
It is also possible to set .Brightness to values that don't correspond to those in the palette, e.g. 0.25 for 75% lighter, or -0.9 for 10% darker.

Powerpoint VBA to search for and change colour/highlight keywords in textbox

I am very new to VB and am exploring this method to simplify mundane manual work process of highlighting certain text in Powerpoint textboxes.
My intention is for VBA to search for keywords in the textbox, then changes the colour of this line and also a few other lines. e.g. search for the line that contains the word "video", if it returns that line 7 contains this word, I want to change the colour of line 7 and maybe lines 3, 10 and 11 to red colour.
Since your question is generic, We can only give a generic response.
First thing you need to know about VBA in powerpoint for your issue is that you need to access things like objects. You'll first need to access the current Slide and Shape your textbox is in. In this example, Let's assume the textbox you want to access is in the first slide, in the first shape:
Set oTextbox = ActivePresentation.Slides(1).Shapes(1)
With oTextbox
text = .TextFrame.TextRange.Characters.Text 'To access the textbox text.
If InStr(1,text,"some_text")
.TextFrame.TextRange.Font.Color.RGB = [255 0 0] 'To change the color of a textbox.
End If
End With
.TextFrame.TextRange.Characters.Text accesses the shape's text.
To search for a given text in the textbox, you can use the InStr
command to see if the text you want is in your textbox.
.TextFrame.TextRange.Font.Color.RGB accesses the text's color.
This is at least a start for you.

Match labels to arrows in Excel flowchart using VBA

I'm writing a code generation tool using VBA in Excel (don't ask why—long story). I need to be able to "parse" a flowchart.
The problem is that Excel allows shapes to contain text, with the exception of connectors: lines and arrows can't contain text. To label an arrow, you just put a text box on top of it—but the box isn't "attached" to the arrow in a way that VBA can easily capture.
For example, a user might draw something like this:
Within my VBA code, I can use ActiveSheet.Shapes to find that the flowchart contains seven shapes: there are five boxes (the two labels are just boxes with no border) and two arrows. Then Shape.TextFrame2 will tell me what's written inside each box, and Shape.ConnectorFormat will tell me which box goes at the start and end of each arrow.
What I need is code that can deduce:
Label A belongs to the arrow from Box 1 to Box 2
Label B belongs to the arrow from Box 1 to Box 3
I can think of three ways of doing this, none of them satisfactory.
Ask the user to group each label with its corresponding arrow.
Find out the coordinates of the endpoints of each arrow, then
calculate which arrows pass through which labels.
Find out the coordinates of the corners of each box, then calculate
which labels lie between which pairs of boxes.
Method 1 makes things easier for the programmer but harder for the user. It opens up a lot of potential for user error. I don't see this as an acceptable solution.
Method 2 would be reasonably easy to implement, except that I don't know how to find out the coordinates!
Method 3 is doable (Shape.Left etc will give the coordinates) but computationally quite messy. It also has potential for ambiguity (depending on placement, the same label may be associated with more than one arrow).
Note that methods 2 and 3 both involve trying to match every label with every arrow: the complexity is quadratic. Typical applications will have 10–50 arrows, so this approach is feasible, if somewhat inelegant.
Does anyone have a better idea? Ideally it would be something that doesn't involve coordinate geometry and complicated logic, and doesn't involve asking users to change the way they draw flowcharts.
Edited to add: example 2 in response to Tim Williams
Here's a label whose bounding box intersects the bounding box of both arrows, and whose midpoint isn't inside the bounding box of either arrow. Visually it's easy for a human to see that it belongs with the left arrow, but programmatically it's hard to deal with. If I can find out the coordinates of the arrows' endpoints, then I can calculate that one arrow passes through the label's box but the other doesn't. But if all I have is the bounding rectangles of the arrows, then it doesn't work.
Interesting problem. What if you considered the range covered by the arrow and the range covered by the textbox and matched them up based on the most overlap.
Sub ListShapes()
Dim shp As Shape
Dim shpArrow As Shape
Dim vaArrows As Variant
Dim i As Long
Dim rIntersect As Range
Dim aBestFit() As String
Dim lMax As Long
vaArrows = Split("Straight Arrow Connector 7,Straight Arrow Connector 9", ",")
ReDim aBestFit(LBound(vaArrows) To UBound(vaArrows))
For i = LBound(vaArrows) To UBound(vaArrows)
Set shpArrow = Sheet1.Shapes(vaArrows(i))
lMax = 0
For Each shp In Sheet1.Shapes
If shp.Name Like "Label*" Then
Set rIntersect = Intersect(Sheet1.Range(shp.TopLeftCell, shp.BottomRightCell), _
Sheet1.Range(shpArrow.TopLeftCell, shpArrow.BottomRightCell))
If Not rIntersect Is Nothing Then
If rIntersect.Count > lMax Then
lMax = rIntersect.Count
aBestFit(i) = shp.Name
End If
End If
End If
Next shp
Next i
For i = LBound(vaArrows) To UBound(vaArrows)
Debug.Print vaArrows(i), aBestFit(i)
Next i
End Sub
I tested this with the five box-two arrow setup and nothing more complicated. I put my two arrows in an array, but I assume you have ways to identify the arrows. I also named my untethered boxes "Label x" so I could identify them, but again I assume you have something more sophisticated.
The code loops through every arrow. Inside that loop, it loops through every shape. If it's a label, then it counts the cells in the intersection of the two ranges. Whichever has the most is stored in the best fit array.
It would be nice if you had a reasonable corpus of flow charts to test this to see where the pitfalls are. I don't think this is necessarily better than use the coordinates, just a different approach.
You can find the coordinates of the arrow's endpoints as follows.
First of all, the .Left, .Top, .Width and .Height properties describe the bounding rectangle of the arrow, as Tim Williams points out.
Next, check the .HorizontalFlip and .VerticalFlip properties. If both are false, then the arrow runs from top left to bottom right in its bounding rectangle. That is, the beginning of the arrow has coordinates (.Left,.Top) and the end has coordinates (.Left+.Width,.Top+.Height).
If either *.Flip is true, then the coordinates need to be swapped around as appropriate. E.g., if .HorizontalFlip is true but .VerticalFlip false, then the arrow runs from (.Left+.Width,.Top) to (.Left,.Top+.Height).
As far as I can tell, this is not documented anywhere on MSDN. Thanks to Andy Pope for mentioning it at excelforums.com.
Given this, method 2 seems like the best approach.