One useful feature of Visio is the ability to add reviewers' notes and scribbles via the Review
tab. You can add comments, which are automatically numbered against the
current user, but they are not associated with any particular shape,
except by juxtaposition. This means that a reviewer's comment does not
move if you move the shape that it relates.
The comments are assigned to the current user, which you can set using the File | Options dialog.
In fact, these comments are not added to the current page unless you switch on Track Markup,
and they are not even normal shapes. They are actually stored as
annotation rows in the ShapeSheet of the page and are not printable.
When you switch on Track Markup, a new special page is created as an overlay over the existing page. This new page is of type Visio.VisPageTypes.visTypeMarkup,
and is named after the foreground page that it is associated with, but
with a suffix of the user's initials. The idea is that a drawing can be
passed from user to user, with each adding their own distinct markup
page, without affecting the original drawing.
When you add a comment, it gets added as a row in the Annotation section of the ShapeSheet of the page.
You can see that each comment has an X and Y value for its location in the page, an index, and a datetime stamp.
Both the Track Markup and Show Markup buttons automatically reveal the Reviewing pane to the right of the diagram. The visibility of this pane can be toggled with the Reviewing Pane button. With Track Markup off but with Show Markup on, the user can see the tabs of all the associated markup pages.
You can double-click on a comment in the diagram, or in the Reviewing pane to display the details of the comment.
So I decided to use this Review
feature to add current issues as comments for the page and each
affected shape. However, I did not want these notes to be confused with
any notes that the current user may wish to create, so I decided to
create a dummy user, Validation Explorer, with the initials vex,
in order to keep them separate. Of course, I do not expect anyone to
manually add this dummy user; it will be added automatically. The only
trace that it exists will be an entry in the Reviewer section of the ShapeSheet of the document, because this is where Visio automatically creates an entry when Track Markup is switched on.
Saving the current user settings
There are two application Settings to provide the strings for the dummy user.
I then added two private strings to store the current user's settings.
private string theUserName = "";
private string theUserInitials = "";
These variables are set during the constructor of the VEDocument class:
this.theUserName = veApplication.VisioApplication.Settings.UserName;
this.theUserInitials = veApplication.VisioApplication.Settings.UserInitials;
They will be required in order to set the user details back again.
Displaying the issue markup page
The Annotate button adds the issues to the Reviewer Comments automatically for the page and each shape that has issues.
After checking that the
active page is not already a markup page, this method collects all of
the issues that are not ignored in the current page, and groups them by
the page or shape. See the var pagIssues = from issu in this.VEIssues ... statement below to see how cool Linq is. It then transfers these objects into a Dictionary because experimentation found that the pagIssues collection is emptied as soon as the active page is changed. This is because I have elsewhere setup a ViewCollection on the VEIssues collection that automatically filters by the active page.
The Visio application settings are then changed to the dummy user before Track Markup and View Markup are switched on. This automatically changes the active page to the markup page.
The comments are then added to the markup page, and finally, Track Markup is switched off, but View Markup is left on so that the user can see the comments.
public void DisplayIssueMarkup()
{
try
{
//Check the page type
Visio.Page pag = (Visio.Page)veApplication.VisioApplication.ActiveWindow.Page;
if (pag.Type == Visio.VisPageTypes.visTypeMarkup) return;
Visio diagrams, annotatingissue markup page//Group the issues for this page by target
var pagIssues = from issu in this.VEIssues where issu.IsIgnored == false && issu.TargetPageID == pag.ID group issu by issu.Target into g
select new { Target = g.Key, Issues = g };
//Transfer into a dictionary otherwise it will be empty when the page changes
Dictionary<object, List<VEIssue>> dicIssues = new Dictionary<object, List<VEIssue>>();
foreach (var v in pagIssues)
{
List<VEIssue> lst = new List<VEIssue>();
foreach (var i in v.Issues)
{
lst.Add(i);
}
dicIssues.Add(v.Target, lst);
}
//Set the dummy user settings
veApplication.VisioApplication.Settings.UserName = Properties.Settings.Default.vexUserName;
veApplication.VisioApplication.Settings.UserInitials = Properties.Settings.Default.vexUserInitials;
//Turn on Track Markup
//this will use the User settings to either create a new markup page or go to a previously created one
this.document.DocumentSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionObject,
(short)Visio.VisRowIndices.visRowDoc, (short)Visio.VisCellIndices.visDocAddMarkup).FormulaU = true.ToString();
//Turn on View Markup
this.document.DocumentSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionObject,
(short)Visio.VisRowIndices.visRowDoc, (short)Visio.VisCellIndices.visDocViewMarkup).FormulaU = true.ToString();
//Get the markup page
pag = (Visio.Page)veApplication.VisioApplication.ActiveWindow.Page;
if (pag.Type == Visio.VisPageTypes.visTypeMarkup)
{
int rvwrID = pag.ReviewerID;
//Clear any existing annotations
if (pag.PageSheet.get_SectionExists( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)Visio.VisExistsFlags.visExistsAnywhere) != 0)
{
pag.PageSheet.DeleteSection( (short)Visio.VisSectionIndices.visSectionAnnotation);
}
Visio diagrams, annotatingissue markup page//Add notes to the markup page
foreach (var k in dicIssues.Keys)
{
Visio.Shape shp = null;
string note = @"";
List<VEIssue> lst = (List<VEIssue>)dicIssues[k];
foreach (VEIssue i in lst)
{
note += i.DisplayName + "\n";
}
if (k is Visio.Page)
{ addIssueNote(pag, null, rvwrID, note); }
else if (k is Visio.Shape)
{ addIssueNote(pag, (Visio.Shape)k, rvwrID, note); }
}
//Turn off track markup
this.document.DocumentSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionObject,
(short)Visio.VisRowIndices.visRowDoc, (short)Visio.VisCellIndices.visDocAddMarkup).FormulaU = false.ToString();
}
}
catch (Exception)
{
throw;
}
//Set the Settings back to the current user
veApplication.VisioApplication.Settings.UserName = this.theUserName;
veApplication.VisioApplication.Settings.UserInitials = this.theUserInitials;
}
Add in the issue comments
The issue comments are added to the markup page as follows:
private void addIssueNote(Visio.Page pag, Visio.Shape shp, int rvwrID, string msg)
{
//Get the last row number in the Annotations section of the ShapeSheet of the page
int intAnnotationRow = pag.PageSheet.AddRow( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)Visio.VisRowIndices.visRowLast, 0);
if (shp != null)
{
//Add the comment
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationX).FormulaU = "=GUARD(Pages[" + shp.ContainingPage.Name + "]!" + shp.NameID + "!PinX)";
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationY).FormulaU = "=GUARD(Pages[" + shp.ContainingPage.Name + "]!" + shp.NameID + "!PinY)";
}
else
{
//Add the comment at the centre of the page, but allow it to be re-positioned, if required
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationX).FormulaU = "=PageWidth*0.5";
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationY).FormulaU = "=PageHeight*0.5";
}
//Add the reviewer ID
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationReviewerID).FormulaU = rvwrID.ToString();
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationReviewerID).FormulaU = rvwrID.ToString();
//Add the index
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationMarkerIndex).FormulaU = (intAnnotationRow + 1).ToString();
//Add timestamp
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationDate).FormulaU = "DATETIME(" + DateTime.Now.ToOADate() + ")";
//Add the concatenated issues
pag.PageSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionAnnotation, (short)intAnnotationRow,
(short)Visio.VisCellIndices.visAnnotationComment).FormulaU = "\"" + msg + "\"";
}
Hiding the issue markup page
This is only called if the
active page type is not a foreground page. It ensures that the active
page is returned back to the foreground page by ensuring that Track Markup and View Markup are switched off. Finally, an attempt is made to hide the Reviewing pane by using the DoCmd()
method on the Visio application object. This will only toggle the
visibility though, but it is most probable that it is visible, so this
will hide it most of the time.
public void HideIssueMarkup()
{
try
{
//Ensure that the user Settings are correct
veApplication.VisioApplication.Settings.UserName = this.theUserName;
veApplication.VisioApplication.Settings.UserInitials = this.theUserInitials;
//Turn off Add Markup
this.document.DocumentSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionObject,
(short)Visio.VisRowIndices.visRowDoc, (short)Visio.VisCellIndices.visDocAddMarkup).FormulaU = false.ToString();
//Turn off View Markup
this.document.DocumentSheet.get_CellsSRC( (short)Visio.VisSectionIndices.visSectionObject,
(short)Visio.VisRowIndices.visRowDoc, (short)Visio.VisCellIndices.visDocViewMarkup).FormulaU = false.ToString();
//Hide the Reviewing pane (probably)
this.TheApplication.VisioApplication.DoCmd( (short)Visio.VisUICmds.visCmdTaskPaneReviewer);
}
catch (Exception)
{
throw;
}
}