Visio has some interesting Characters

The Question

Recently, Scott, one my good friends asked me about the Visio Character section. A slightly strange request considering he is one of only a half dozen people world wide who MS, has recognized as a Visio expert. Scott does know I have not had a great year physically and I think he is just humouring me because he knows that answering Visio questions is one of my guilty pleasures. So, I will play along.
If you do get a chance to attend one of his webcasts (or any of the other Visio MVPs),
they are very interesting and informative.

“A VBA question: I have a shape with two text runs as shown below. The RunEnd property of the characters object returns 30. How do I determine the length of the first run or the start character for the second run, either of which will tell me what I need?”

There is not a lot of information out there about the Character section except that the first column is the number of characters in the first row. So, his first observation is correct, there are 30 characters.

Exploring

So, the first thing to do is recreate what he is seeing, but probably with fewer characters.

Sub FirstCheck()
Dim i As Integer, TextLen As Integer, tmpStr As String
Dim vsoChr As Characters, vsoShp As Shape
Set vsoShp = ActivePage.DrawRectangle(1, 1, 5, 5)

vsoShp.Text = "abc" & Chr(10) & "def"
Set vsoChr = vsoShp.Characters
tmpStr = vsoShp.Text
TextLen = Len(vsoShp.Text)
Debug.Print "TextLen = "; TextLen
For i = 1 To TextLen 
vsoChr.Begin = i: vsoChr.End = i
Debug.Print i; " "; Mid(tmpStr, i, 1); " ["; Asc(Mid(tmpStr, i, 1)); "]"
Next i
End Sub
TextLen =  7 
 1  a [ 97 ]
 2  b [ 98 ]
 3  c [ 99 ]
 4  <LF> [ 10 ]
 5  d [ 100 ]
 6  e [ 101 ]
 7  f [ 102 ]
Interesting, though the text shows as two lines, it only creates a single Character row and the label, supposedly the number of characters shows as zero though the TextLen is seven "abcdef" + <LF>. The documentation does say that the rows are based on similar attributes, so change the "abc" to be red and the result is...

I need help

In addition to answering Scott’s question, I decided to throw in a few extra, Character and TextField tidbits I have come across, just to fill out the discussion.

My usual reference source is Google, so I did a Google search on “Visio Character”.
One of the references was to the Visio VBA page on my Visio.MVPS.org page. It was a post by someone on the Visio team made in 2004 as an answer to a forum question.

Sub GetCharRows()
'from Heidi Munson - Microsoft Corporation

'The Characters object deals with fields a bit differently than the 
'labeling in the ShapeSheet window. To analyze the text in the shape,
'use the .RunBegin and .RunEnd properties of the Characters object. 
'The VBA code below provides a simple example of analyzing text using 'RunBegin and RunEnd. It expects the  active window to be a drawing type 'window and for at least one shape to be selected. It analyzes the text in 'the first shape in the selection and finds runs of Text in the shape that 'corresponds to each of the rows in the Characters section.

Dim i As Integer, iCharCount As Integer, iBegin As Integer
Dim iEnd As Integer, vsoChr As Visio.Characters, vsoShp As Visio.Shape

Set vsoShp = ActivePage.Shapes(1)
i = 0
' The initial characters object return from the Characters Property of the
' shape spans all the text in the shape, (warning - This may not be true
' if the first character of the text block is a carriage return)

Set vsoChr = vsoShp.Characters
iCharCount = vsoChr.CharCount
Debug.Print "Len text = "; Len(vsoShp.Text);
Debug.Print " CharCount = "; vsoChr.CharCount
While (i + 1 < iCharCountDebug.Print
' Set the characters object to span one character
vsoChr.Begin = i: vsoChr.End = i + 1
' Find the set of characters that share formatting with this character
iEnd = vsoChr.RunEnd(Visio.VisRunTypes.visCharPropRow)
iBegin = vsoChr.RunBegin(Visio.VisRunTypes.visCharPropRow)
' Set the characters object to span this run
vsoChr.Begin = iBegin: vsoChr.End = iEnd
' Update i so that the next time through the loop,
' we'll look at the first character after this run.
i = iEnd

' Output some helpful information about this run.
Debug.Print "Row Number = "; vsoChr.CharPropsRow(Visio.visBiasLeft);
Debug.Print " CharCount " & vsoChr.CharCount;
Debug.Print " RunBegin = " ; iBegin ; " RunEnd " ; iEnd;
'The text in this run may contain returns to make it easier to see 
' what text belongs to each run, wrap the text in brackets.
Debug.Print " <" & vsoChr & ">"
Wend
End Sub

This gave a few clues of more things I could explore. RunEnd, CharPropsRow and CharCount.

So, if we run Heidi’s code we get…
Len text = 7 CharCount = 7
Row Number = 0 CharCount 4 RunBegin = 0 RunEnd 4 <abc<LF>>
Row Number = 1 CharCount 3 RunBegin = 4 RunEnd 8 <def>

So, having my old website around was useful.

Visio.MVPs.org

I started volunteering with Visio long before having a web site was popular. Intially I had a website from my ISP that had a couple of Visio information pages, a history page, a 3rd party page, a VBA page, shapesheet page and a few other things. The VBA page had sample code I found that helped solve problems asked in the forums along with who contributed the code, the shapesheet page had sample shapesheet suggestions, The 3rd party page was a description and a URL for 3rd party Visio shape contribution. I wanted to preserve the authors IP, so I only provided URLs. In hindsight I probably should have asked the authors, when I had a chance, that if the URL breaks or the site is down that I could host the files.

Shortly after I set up the site, I had complaints that the site was down. With a little investigation it turns out that the ISP had put a monthly cap on traffic. So, after the 6th of the month, the site was unavailable.

At the same time, Karl and Felix set up http://www.MVPs.org and provided subsites to any MVP that wanted one for their product area. I took http://www.MVPs.og/Visio, which eventually was available as Visio.MVPs.org. The MVPs are volunteers who provide product support at their expense, so providing websites was greatly appreciated. Karl and Felix did provide the service for two decades and when websites became common and easy to obtain, they froze the websites, but they were still available.

So, what about other sources of information?

DVS

Cover from 94

DVS is another good reference. Originally it was a book that came with the software. Over the years it expanded and the name changed to Developing Visio Solutions. For Visio 2002, it was a book from Microsoft Press. On some of the installation media, it was also available as a pdf. I am sure if you look on the internet, you will find a copy of of DVS.pdf.

After Graham wrote his books that greatly expanded on the information in DVS, I use to kid him that DVS stood for Da Very Start.

One interesting thing in the book is Char.Font.

Char

vsoShp.Cells(“Char.Style[2]”).ResultIU = visItalic.
Though not explicitly stated, you can refer to the Style cell by using Char and the row number. This also works for the Font, Size and other cell, for example

vsoShp.Cells(“Char.Style[2]”).ResultIU = visItalic
which is the same as
vsoShp.CellsSRC(visSectionCharacter, visRowCharacter + 1, visCharacterStyle).ResultIU = visItalic

.

TextFields

So, how do the TextFields fit into the Character Section? So, let us try two text blocks, the page name and the creator with a few characters, before after and in between..

The correlation between TextField rows and the Character section is quite simple, the first TextField row relates to the first field placeholder in the Character section, the second to the second and so on. So, let us create a shape with the text “abcdef” and place the Text Fields PAGENAME() and CREATOR() between the 2nd and 3rd caracter and the fourth and fifth characters.

Sub FirstCheck()
Dim i As Integer, TextLen As Integer, tmpStr As String
Dim vsoChr As Characters, vsoShp As Shape
Set vsoShp = ActivePage.Shapes(1)
Set vsoChr = vsoShp.Characters
tmpStr = vsoShp.Text
TextLen = Len(vsoShp.Text)
Debug.Print "TextLen = "; TextLen
For i = 1 To TextLen
vsoChr.Begin = i: vsoChr.End = i
If Asc(Mid(tmpStr, i, 1)) = 10 Then Debug.Print i; "<LF>"; Else Debug.Print i; Mid(tmpStr, i, 1);
Debug.Print " ["; Trim(Asc(Mid(tmpStr, i, 1))); "]"
Next i
End Sub
TextLen =  8 
 1 a [97]
 2 b [98]
 3 ? [63]
 4 c [99]
 5 d [100]
 6 ? [63]
 7 e [101]
 8 f [102]

The TextField place holders are represented by “?”s but what about a real question mark? This is where IsField comes in, the documentation does not say much beyond it needs to be checked to avoid errors when using FieldCode, FieldCategory or FieldFormulaU.

Sub FirstCheck()
Dim i As Integer, TextLen As Integer, tmpStr As String
Dim vsoChr As Characters, vsoShp As Shape
Set vsoShp = ActivePage.Shapes(1)
Set vsoChr = vsoShp.Characters
tmpStr = vsoShp.Text
TextLen = Len(vsoShp.Text)
Debug.Print "TextLen = "; TextLen
For i = 1 To TextLen
vsoChr.Begin = i: vsoChr.End = i
If Asc(Mid(tmpStr, i, 1)) = 10 Then Debug.Print i; "<LF>"; Else Debug.Print i; Mid(tmpStr, i, 1);
Debug.Print " ["; Trim(Asc(Mid(tmpStr, i, 1))); "]";
Debug.Print " IsField ="; vsoChr.IsField;
Debug.Print vsoChr.RunBegin(VisRunTypes.visCharPropRow);
Debug.Print ">"; vsoChr.RunEnd(VisRunTypes.visCharPropRow);
Debug.Print "left ="; vsoChr.CharPropsRow(visBiasLeft);
Debug.Print "choose ="; vsoChr.CharPropsRow(visBiasLetVisioChoose);
Debug.Print "right ="; vsoChr.CharPropsRow(visBiasRight);
If vsoChr.IsField <> 0 Then
  Debug.Print "FieldCode ="; vsoChr.FieldCode;
  Debug.Print "FieldCategory ="; vsoChr.FieldCategory;
  Debug.Print "FieldFormulaU ="; vsoChr.FieldFormulaU
Else
Debug.Print
End If
Next i
End Sub

TextLen =  9 
 1 a [97] IsField = 0  0 > 27 left = 0 choose = 0 right = 0 
 2 b [98] IsField = 0  0 > 27 left = 0 choose = 0 right = 0 
 3 ? [63] IsField =-1  0 > 27 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =PAGENAME()
 4 c [99] IsField =-1  0 > 27 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =PAGENAME()
 5 ? [63] IsField =-1  0 > 27 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =PAGENAME()
 6 d [100] IsField =-1  0 > 27 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =PAGENAME()
 7 ? [63] IsField =-1  0 > 27 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =PAGENAME()
 8 e [101] IsField = 0  0 > 27 left = 0 choose = 0 right = 0 
 9 f [102] IsField = 0  0 > 27 left = 0 choose = 0 right = 0

I thought stepping through the text one character a time would be informative. Heidi’s code is more efficient and quickly jumps to the end of a Run. “9 > 27” show the begin / end of a run based on the current character. “left” “choose” “right” show the row number based on the character when looking to the left or right of the current position. “choose” is when you let Visio decide because it knows if you are on the last character of a run or the first character of the next run. In this example, there is only one row and the difference is not obvious. So, let us change the “d”Creator()”e” to green and throw in a question mark after the “c’.

Just to show that the TextFields are positional, let us also swap the Values of the two TextFields.

Sub FirstCheck()
Dim i As Integer, TextLen As Integer, tmpStr As String
Dim vsoChr As Characters, vsoShp As Shape
'Set vsoShp = ActivePage.DrawRectangle(1, 1, 5, 5)
'vsoShp.Text = "abc" & Chr(10) & "def"
Set vsoShp = ActivePage.Shapes(1)
Set vsoChr = vsoShp.Characters
tmpStr = vsoShp.Text
TextLen = Len(vsoShp.Text)
iCharCount = vsoChr.charCount
Debug.Print "TextLen = "; TextLen; " iCharCount = "; iCharCount

For i = 1 To iCharCount
vsoChr.Begin = i: vsoChr.End = i
Debug.Print i;
Debug.Print " IsField ="; vsoChr.IsField;
Debug.Print vsoChr.RunBegin(VisRunTypes.visCharPropRow);
Debug.Print ">"; vsoChr.RunEnd(VisRunTypes.visCharPropRow);
vsoChr.Begin = i: vsoChr.End = i

Debug.Print "left ="; vsoChr.CharPropsRow(visBiasLeft);
Debug.Print "choose ="; vsoChr.CharPropsRow(visBiasLetVisioChoose);
Debug.Print "right ="; vsoChr.CharPropsRow(visBiasRight);
If vsoChr.IsField <> 0 Then
  Debug.Print "FieldCode ="; vsoChr.FieldCode;
  Debug.Print "FieldCategory ="; vsoChr.FieldCategory;
  Debug.Print "FieldFormulaU ="; vsoChr.FieldFormulaU
Else
Debug.Print
End If
Next i
End Sub
TextLen = 9 iCharCount = 26 
 1 IsField = 0 0 > 17 left = 0 choose = 0 right = 0 
 2 IsField = 0 0 > 17 left = 0 choose = 0 right = 0 
 3 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 4 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 5 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 6 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 7 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 8 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 9 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 10 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 11 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 12 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 13 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 14 IsField =-1 0 > 17 left = 0 choose = 0 right = 0 FieldCode = 1 FieldCategory = 5 FieldFormulaU =CREATOR()
 15 IsField = 0 0 > 17 left = 0 choose = 0 right = 0 
 16 IsField = 0 0 > 17 left = 0 choose = 0 right = 0 
 17 IsField = 0 17 > 17 left = 0 choose = 0 right = 1 
 18 IsField = 0 17 > 25 left = 1 choose = 1 right = 1 
 19 IsField =-1 17 > 25 left = 1 choose = 1 right = 1 FieldCode = 0 FieldCategory = 2 FieldFormulaU =PAGENAME()
 20 IsField =-1 17 > 25 left = 1 choose = 1 right = 1 FieldCode = 0 FieldCategory = 2 FieldFormulaU =PAGENAME()
 21 IsField =-1 17 > 25 left = 1 choose = 1 right = 1 FieldCode = 0 FieldCategory = 2 FieldFormulaU =PAGENAME()
 22 IsField =-1 17 > 25 left = 1 choose = 1 right = 1 FieldCode = 0 FieldCategory = 2 FieldFormulaU =PAGENAME()
 23 IsField =-1 17 > 25 left = 1 choose = 1 right = 1 FieldCode = 0 FieldCategory = 2 FieldFormulaU =PAGENAME()
 24 IsField = 0 17 > 25 left = 1 choose = 1 right = 1 
 25 IsField = 0 25 > 25 left = 1 choose = 1 right = 2 
 26 IsField = 0 25 > 27 left = 2 choose = 2 right = 2

This may make more sense

posTextIsFieldbeginendleftchooserightFieldFieldField
CodeCategoryFormulaU
1a[97]0017000
2b[98]0017000
3?[63]-101700015=CREATOR()
4-101700015=CREATOR()
5-101700015=CREATOR()
6-101700015=CREATOR()
7-101700015=CREATOR()
8-101700015=CREATOR()
9-101700015=CREATOR()
10-101700015=CREATOR()
11-101700015=CREATOR()
12-101700015=CREATOR()
13-101700015=CREATOR()
14-101700015=CREATOR()
15c[99]017000
16?[63]017000
1701717001
18d[100]01725111
19?[63]-1172511102=PAGENAME()
20-1172511102=PAGENAME()
21-1172511102=PAGENAME()
22-1172511102=PAGENAME()
23-1172511102=PAGENAME()
2401725111
25e[101]02525112
26f[102]02527222

Overline

In 2004 I was asked about adding an overline to a block of text. At the time, the best answer was to create a blank line above and use an underline or try and position a line over the text, There was no way to add an overline through the UI. After playing with the Character section, it became obvious there was a simple solution, just set the Overline cell to true in the Character section.

So, hopefully Heidi’s code snippet answered Scott’s question and that there were a few interesting things for the lurkers.

John… Visio MVP in x-aisle
JohnVisioMVP.ca

Published by johnvisiomvp

The original Visio MVP. I have worked with the Visio team since 1993