After creating a datarecordset, the next step is to link shapes to its rows. A shape can be linked to only one row within a single datarecordset, but it can be linked to multiple datarecordsets – one row in each. Multiple shapes can be linked to the same row. The steps of creating the shapes, linking the shapes, and adding a data graphic can be done in a single step.
Datarecordsets can be refreshed. They can be refreshed on demand or they can be refreshed automatically at prescribed intervals.
When a datarecordset is refreshed, it may change. These changes can lead to conflicts with the linking that was previously established. Visio provides tools for detecting these conflicts and detecting changes to the datarecordset so that your program can react appropriately to the changes.
At the end of this lesson we will review a macro that refreshes the datarecordset for the organization chart created in the previous lesson. After the data is refreshed the code checks for conflicts in the data and for additions and deletions to the data.
Lesson Objectives
After completing this lesson you will be able to:
-
Link existing shapes to rows in a datarecordset
-
Add shapes to a drawing and link them at the same time
-
Link many shapes to a row in a datarecordset
-
Link shapes to rows in multiple datarecordsets
-
Automatically link shapes
-
Add data graphics to linked shapes
-
Discover existing links
-
Break links
-
Manage refresh
-
Discover and manage conflicts after refresh
24.Linking data to shapes
After you connect your Visio drawing to an external data source, you can link the shapes in the drawing to data from that source programmatically. You can link one or more shapes to a single row of data in a data recordset or to multiple rows of data in different data recordsets. However, you cannot link shapes to multiple rows of data in the same recordset.
You can link existing shapes to data, one shape at a time or many at a time; or, you can create shapes and link them to data simultaneously.
When you want to create shapes that are already linked to data, on a drawing page that either does not contain any shapes or contains shapes other than the ones you want to link, you can use the Page.DropLinked method and the Page.DropManyLinkedU method to create one or more additional shapes already linked to data. These methods resemble the existing Page.Drop and Page.DropManyU methods in that they create additional shapes at a specified location on the page; but they also create links between the new shapes and specified data rows in a specified data recordset.
An example of using DropLinked follows.
Set vsoShape = ActivePage.DropLinked(vsoMaster, dblX, dblY, _
lngDataRecordsetID, lngRowID, True)
-
vsoMaster is a reference to the master that is being dropped on the page
-
dblx, dbly define the drop location and correspond to the created shape’s pin location
-
lngDataRecordsetID is the ID of the datarecordset being linked to
-
lngRowID is the ID of the row within the datarecordset being linked to
-
the last parameter (True) is a Boolean that specifies whether a data graphic is to be applied to the shape automatically
-
vsoShape is a returned reference to the created shape
Try it! -
In the file Working with DataRecordsets.vsd, run the macros AddFromXML_Example (although any open datarecordset will do) and then DropLinkedShape.
-
View the code in DropLinkedShape.
An example of DropLinkedManyU was covered in an earlier example. The complete code can be viewed in the macro DropManyLinkedShape. The core piece of code follows:
'Create one shape record for each record in the datarecordset
lngReturned = ActivePage.DropManyLinkedU(avsoMaster, _
dblXY, vsoDataRecordset.ID, _
lngRowIDs, True, alngShapeIDs)
-
avsoMaster is an array of shapes to be dropped, one per record in the datarecordset
-
dblxy is an array of x, y coordinates that determine the drop locations for the new shapes
-
vsoDataRecorset.ID is the ID of the datarecordset being linked to
-
lngRowIDs is an array of row IDs within the datarecordset that determine the row that each shape is linked to
-
the next to last parameter (True) is a Boolean that specifies whether a data graphic is to be applied to the shapes automatically
-
alngShapeIDs is an array of IDs for the created shapes
-
lngReturned is a returned value specifying the number of created shapes
Linking existing shapes to data Link a single shape to a single row
To link a single shape to a single row LinkToData method of the shape as shown below.
vsoShape.LinkToData vsoDataRecordset.ID, lngRowID, boolAddDataGraphic
The following information is needed:
-
vsoShape is a reference to the shape to be linked
-
vsoDataRecordset.ID is the ID of the datarecordset being linked to
-
lngRowID is the ID of the row within the datarecordset
-
boolAddDataGraphic is a Boolean specifying whether a data graphic is to be applied automatically to the shape
Link a shape to multiple datarecordsets
A single shape can be linked a single row within a single datarecordset, but it can still be linked to a different row within a different datarecordset.
To link a shape to rows in multiple datarecordsets apply the LinkToData method multiple times, once for each datarecordset.
Link multiple shapes to a row in a datarecordset
This is the same as linking an individual shape except the LinkToData method of the Selection object is used.
If Visio cannot establish a link between a shape and the data row, Visio skips that shape and goes on to the next shape in the selection. After you run the method, to determine if all shapes in the selection are actually linked to the data row, call the Shape.GetLinkedDataRow method on each shape in the selection. If that method fails for any shape, it indicates that the shape is not linked to the data row. Visio will usually succeed in linking a row to a shape unless the shape is already linked to data and the link-replacement-behavior setting for the datarecordset specifies that the link should not be replaced.
Link multiple shapes to multiple rows
The Page.LinkShapesToDataRows method is similar to the Selection.LinkToData method because it links multiple shapes. The LinkShapesToDataRows method links shapes to multiple data rows. The LinkToData method of the Selection object links multiple shapes to a single row.
ActivePage.LinkShapesToDataRows vsoDataRecordset.ID, alngDataRowIDs, alngShapeIDs, boolAddDataGraphic
To link shapes, pass the LinkShapesToDataRows method a pair of arrays: one for shapes, and one for data rows. Note that the matching array positions must correspond. As a result, for example, the shape at position 1 in the shape array is linked to the data at position 1 in the data row array. Again, when you call the method, you can optionally specify whether to apply an existing data graphic to linked shapes.
Automatically link shapes and data rows
If you are unsure about the correspondence between shapes and data rows, but you know a match exists between a specific attribute of every shape and the data in one column in the data recordset, the Selection.AutomaticLink method provides a way to link a selection of existing shapes to multiple rows of data. Note that it must be the same attribute for all shapes.
The help file for the AutomaticLink method gives a coding example for setting up an automatic link where a single column to be matched is specified. Let’s set up an example for matching multiple columns and let’s use the record macro feature of Visio to create the code.
Try it! -
Open Working with DataRecordsets.vsd and run the macro BuildOrgMacros.AddDataRecordset to create a datarecordset from ORGDATA.xls.
-
Drop three rectangles on the page and link them to the first three records in the datarecordset. This will add shape data fields to the rectangles.
-
Unlink the three shapes.
-
Turn on the macro recorder.
-
Select the three shapes.
-
Right-click in the External Data window and choose Automatically Link. The Automatically Link wizard is presented.
-
On the first page choose Selected Shapes and then Next.
-
Choose Data Column = Name and Shape Field = Name. Then click the and button.
-
Choose Data Column = Title and Shape Field = Title.
-
Click Next and Finish. This will link the three selected shapes.
-
Stop the macro recorder and look at the code.
The last portion of the code should look similar to the following.
ColumnNames1(0) = "Name"
FieldTypes1(0) = visAutoLinkCustPropsLabel
FieldNames1(0) = "Name"
ColumnNames1(1) = "Title"
FieldTypes1(1) = visAutoLinkCustPropsLabel
FieldNames1(1) = "Title"
Application.ActiveWindow.Selection.AutomaticLink 41, ColumnNames1, _
FieldTypes1, FieldNames1, visAutoLinkReplaceExistingLinks, _
IDsofLinkedShapes1
We are passing to the method arrays for column names, field types, and field names. In this case we are specifying that multiple columns and fields must match before the shapes can be linked.
The penultimate parameter specifies the auto link behavior. Use values from the VisAutoLinksBehavior enumeration. These enumerated values provide options to customize the method, for example to replace existing links with new ones.
The last parameter returns an array of shape IDs of the linked shapes.
Discovering links
Discovering information about links is very straight forward. The sample code in the Visio help files gives examples using these methods. Some key methods include:
-
Page.GetShapesLinkedToData – given a datarecordset, returns an array of shape IDs on a page linked to the datarecordset
-
Page.GetShapesLinkedToDataRow – given a datarecordset and a row ID, returns an array of shape IDs on a page that are liked to that row
-
Shape.GetLinkedDataRow – given a datarecordset, returns the ID of the row the shape is linked to
-
Shape.GetLinkedDataRecordsetIDs – returns a list of IDs of all datarecordsets that are linked to by this shape
-
Shape.GetCustomPropertyLinkedColumn – given a datarecordset ID and a shape’s Shape Data row ID, returns the name of the data column linked to this shape data row
-
Shape.GetCustomPropertiesLinkedToData – given a datarecordset ID, returns an array of Shape Data row IDs that are linked to the data columns in the datarecordset
-
Shape.IsCustomPropertyLinked - given a datarecordset ID and a shape’s Shape Data row ID, returns whether that Shape Data item is linked
Breaking links
Breaking links through automation is handled with the Shape.BreakLinkToData and the Selection.BreakLinkToData methods.
In addition, actions in the user interface can cause links to be broken, such as deleting a datarecordset or deleting the Shape Data item in a shape that is linked.
Regardless of how the link was broken, the Shape.ShapeDataLinkDeleted event is fired.
A note on the use of IDs in Visio
Visio keeps unique IDs for shapes, datarecordsets, and rows within the datarecordsets.
Shape IDs
Shape IDs are unique to the page. Shape IDs are reused when one shape is deleted and then another shape is added.
Shape UniqueIDs
Unique IDs are GUIDs. They are unique within the scope of the application.
To convert between shape IDs and unique IDs, you can use two methods of the Page object, ShapeIDsToUniqueIDs and UniqueIDsToShapeIDs.
By default, a shape does not have a unique ID. A shape acquires a unique ID only if you set its UniqueID property. If a Shape object has a unique ID, no other shape in any other document will have the same ID.
DataRecordset IDs
DataRecordsets have unique IDs accessible via the DataRecordset.ID property. These ID values are not reused if a DataRecordset is closed. Generally, the DataRecordset.ID value will NOT be the same as the position of the DataRecordset within the DataRecordsets collection. There is no index value for a DataRecordset.
Row IDs within a DataRecordset
Rows of data within a DataRecordset generally have unique IDs starting with a value of 1, but they may not run consecutively depending on recent refresh operations.
Because shapes are linked by their shape IDs to specific data rows, when Visio refreshes linked data, it must determine which rows in the linked data recordset or recordsets were added, changed, or removed since the last time the data was refreshed.
To identify these rows, Visio uses the row IDs assigned to the rows in the data recordset. Visio can assign these row IDs two ways, depending on whether you designated primary keys for the data recordset when you created it.
Refreshing Data Recordsets That Do Not Have Primary Keys
When you create a data recordset, Visio assigns row IDs to all the rows in the recordset based on the existing order of the rows in the data source. Accordingly, the first row in the recordset is always assigned row ID 1, the second is assigned row ID 2, and so on.
Subsequently, data rows can be added or removed from the original data source. Then, when data is refreshed, the data recordset reflects those changes. As a result, row order in the data recordset may change.
For example, in a five-row data recordset, if the fourth row in the data source is removed, when Visio refreshes the data recordset connected to that data source, the fifth row in the data recordset becomes the new fourth row and is assigned row ID 4. Row ID 5 is removed from the data recordset. As a result, shapes linked to row ID 5 lose their links, and shapes linked to row ID 4 now get data from the row previously in the fifth position.
As you can see, not assigning primary keys to data recordsets when you create them can result in broken links between shapes and data, or in Visio linking shapes to rows other than the ones to which you want them linked.
Refreshing Data Recordsets That Have Primary Keys
You can help prevent these broken or mismatched links by assigning primary keys to data recordsets. A primary key identifies the name of the data column or columns that contain unique identifiers for each row. The value in the primary key column for each row uniquely identifies that row in the data recordset. Primary keys are often ID values, but you can set any column or combination of columns to be the primary key. However, to get consistent results when you refresh data, it is essential that you make the primary key column value (or set of values for multiple primary key columns) unique for each row.
As a result, when you refresh or when Visio refreshes a data recordset that includes primary keys, its rows retain the same row IDs they had before the refresh operation. Because Visio links shapes to data rows by ID—shape ID to row ID—and because row IDs remain the same after a refresh operation, data-linked shapes remain linked to the correct row. Note that row IDs are never recycled for a given a data recordset.
25.Managing Refresh
DataRecordsets can be refreshed programmatically using the DataRecordset.Refresh method. Calling this method executes the query string associated with the datarecordset and then updates the linked shapes with the data returned by the query.
When a datarecordset is created it does not refresh automatically. To enable automatic refresh set its refresh interval to some value greater than zero.
'Set the refresh interval to 2 minutes
vsoDataRecorset.RefreshInterval = 2
To turn off automatic refresh set the refresh interval to zero.
'Turn off automatic refresh
vsoDataRecorset.RefreshInterval = 0
If calling Refresh results in conflicts, Visio displays the Refresh Conflicts task pane in the UI, unless you set the RefreshSettings property to include the visRefreshNoReconciliationUI (=2) enumerated value. By default RefreshSettings=0.
As a developer, you should reconcile refresh conflicts programmatically by using the GetAllRefreshConflicts, GetMatchingRowsForRefreshConflict, and RemoveRefreshConflict methods.
'Don’t display refresh conflicts in the Refresh Conflicts task pane
vsoDataRecorset.RefreshSettings = visRefreshNoReconciliationUI
Identify changes and resolve conflicts
When a datarecordset is refreshed conflicts are displayed in the Refresh Conflicts task pane.
If a data row has been deleted and a shape was linked to it, that shape is displayed and the user is prompted for what to do with it.
If a new data row is added that is a duplicate of a previous row that was linked, the link is removed and the user is presented with the duplicate data and prompted for how to deal with it.
If a new data row is added that is not a duplicate, there is no conflict. From the user interface the new record is apparent by looking at the External Data window and noting the unlinked records.
The automation model provides tools to handle each of these scenarios.
GetAllRefreshConflicts
This method returns an array of shapes that are linked to data rows in a datarecordset that have non-resolved conflicts after a datarecordset is refreshed.
Conflicts can result when a single shape is linked to more than one row in the same data source, or when a shape is linked to a row in the data source that has been deleted. If the shape would be linked to multiple rows, the link is removed and the conflict is presented in the Refresh Conflicts task pane. Use GetMatchingRowsForRefreshConflict to retrieve all of the rows in the datarecordset that match with this shape.
If no conflicts exist, an empty array is returned.
GetMatchingRowsForRefreshConflict
When a shape is potentially linked to multiple rows in a datarecordset after a refresh, the list of matching rows can be retrieved with this method. It returns an array of row IDs from the datarecordset that each match the shape in conflict.
Conflicts can also occur when a previously data-linked row from the data recordset is removed. When this occurs, the method returns an empty array.
RemoveRefreshConflict
Once a conflict has been dealt with it can be removed from the list by using the RemoveRefreshConflict method of the shape.
DataRecordsetChanged event and DataRecordsetChanged object
Retrieving conflict information is great for when changes to the datarecordset results in conflicts, but this does not help when new rows are added to the datarecordset. For example, when a new row is added the user may want to add a new shape that is linked to the new rows.
Fortunately, there is a way to retrieve this information programmatically. Use the DataRecordsetChanged event to detect that the datarecordset has changed. Within the DataRecordsetChanged event you have access to the DataRecordsetChanged object which contains a wealth of information about what has changed in the datarecordset including data columns that have been added, changed, or deleted, and data rows that have been added or deleted.
Try it!
The sample code in Working with DataRecordsets.vsd has a macro that will Refresh a datarecordset and then check for and process changes using the methods and events just described. If you already have the file open, it is best to close it and then reopen it. Some of the code is not bullet proof, but it does illustrate how to use the methods.
-
Open Working with DataRecordsets.vsd from the Samples folder.
-
Run the macro BuildOrgMacros.DoItAll. This builds the organization chart we saw earlier using the OrgData.xls file.
-
Run the macro BuildOrgMacros.RefreshDataAndResolveConflicts. This macro refreshes the Org Data datarecordset by modifying the connection string to point to a modified version of the Excel file OrgDataModified.xls and then using the Refresh method.
-
The following changes are made to the organizational data:
-
The record for Toby Nixon is changed to Toby Dixon. In the code this is handled as one deleted record and one added record. The original Toby Nixon shape is deleted and a new shape is added.
-
The record for Meng Phua is changed to report to Toby Dixon. There is no conflict created by this change. The datarecordset and the shape data are updated on the refresh, but because the Toby Dixon record is new, the connector has to be changed to point to the correct “Reports_To” shape
-
A new record is added for Fred Taylor who reports to Toby Dixon and a duplicate record for Fred Taylor is also created. If both records are added at the same time there is no conflict because there is no shape linked to these rows. A new shape is added for Fred Taylor. When the second new Fred Taylor shape is processed it is ignored because it is a duplicate.
-
Look at the code for RefreshDataAndResolveConflicts. Only the refresh code is here.
-
The resolution of the conflicts is handled in the event procedure vsoDataRecordsets_DataRecordsetChanged which is in the ThisDocument module.
Note:
|
It is best to put the resolve conflicts code inside the event procedure. The event code does not fire immediately after the Refresh method so if you have some code in the event procedure and some code following the Refresh method, you won’t be able to determine which code is executed first.
| -
Look at the event procedure code. Conflicts are handled first using GetAllRefreshConflicts and then processing the conflict list. If a row has been deleted, the corresponding shape is deleted. If duplicate rows are created which have a corresponding shape, the shape is linked to one of the rows and the other row is ignored.
-
New rows added to the datarecordset are handled next. If a new row is added which does not represent a duplicate, a new shape is added to the drawing and linked its “Reports_To” shape.
-
All resulting changes in the drawing are in the lower right hand area of the drawing page.
Warning for VBA users implementing code samples from Help files
Below is the code sample provided in the Help file for GetMatchingRowsForRefreshConflic. Note that after the call to GetAllRefreshConflicts and to GetMatchingRowsForRefreshConflic there is an If test that uses the function IsEmpty to test for an empty array being returned by the methods.
Unfortunately, this test does not work in VBA. The IsEmpty function is designed to work with variables of type Variant and does not reliably return information for other data types. It returns False even when the array has no data.
There are several similar methods that return arrays and similar code samples that use IsEmpty to test these arrays. All of these samples will be problematic in VBA.
Public Sub GetMatchingRowsForRefreshConflict_Example()
Dim vsoDataRecordset As Visio.DataRecordset
Dim intRecordsetCount As Integer
Dim intShapeCount As Integer
Dim vsoShapes() As Visio.Shape
Dim intRowCount As Integer
Dim vsoShapeInConflict As Visio.Shape
Dim alngRowIDs() As Long
Dim lngvsoRowID As Long
intRecordsetCount = Visio.ActiveDocument.DataRecordsets.Count
Set vsoDataRecordset = Visio.ActiveDocument.DataRecordsets(intRecordsetCount)
vsoDataRecordset.Refresh
vsoShapes = vsoDataRecordset.GetAllRefreshConflicts
If IsEmpty(vsoShapes) Then
Debug.Print "No conflict"
Else
For intShapeCount = LBound(vsoShapes) To UBound(vsoShapes)
Set vsoShapeInConflict = vsoShapes(intShapeCount)
alngRowIDs = vsoDataRecordset.GetMatchingRowsForRefreshConflict(vsoShapeInConflict)
If IsEmpty(alngRowIDs) Then
Debug.Print "For shape:", vsoShapeInConflict.Name, "Row deleted."
Else
For intRowCount = LBound(alngRowIDs) To UBound(alngRowIDs)
lngvsoRowID = alngRowIDs(intRowCount)
Debug.Print "For shape:", vsoShapeInConflict.Name, "Row ID of row in conflict:", lngvsoRowID
Next intRowCount
End If
Next intShapeCount
End If
End Sub
Alternatives to using IsEmpty
Sometimes, but not always, the UBound function will work and return a value of -1 if the array is empty. However, in some cases the UBound function will throw a ‘subscript out of range’ error.
If using UBound does not work the only reliable alternative is to create an error handler to trap and process the error.
Sometimes data locations change or network connections are lost. Is there a way to build some resiliency into your code so that if your access to the data changes your program does not fall over?
Let’s examine some common scenarios:
-
If the data changes location you may have to change the connection string in your code to point to the new location. We’ve already seen that the connection string can be changed and the datarecordset refreshed so straightforward to build this kind of change into your code.
-
You can isolate your program from the data by using a data connection file and using the AddFromConnectionFile method. Connect to the data via a connection file with the assumption that the connection file stays in a fixed location and if the data moves, the connection file is updated with the new location.
-
The third variation is when the data becomes unavailable unexpectedly such as when a network connection is lost or if the data is moved, but you don’t know where it is. In this scenario we can’t reconnect to the data, but we can detect that the data could not be found and then fail as gracefully as possible.
To detect a failed connection use the Timeout property of the DataConnection object. This property is used to set a timeout interval (default is 15 seconds). If the interval elapses without success an error is raised. An error handler must be written to trap the error. An example follows showing a portion of the subroutine which refreshes a datarecordset and displays the error message when an error occurs:
'Set the connection string into the DataConnection object to point to
'the desired data
vsoDataRecordset.DataConnection.ConnectionString = strConnection
'Set up error handler to catch the timeout error
On Error GoTo ErrorHandler
vsoDataRecordset.DataConnection.Timeout = 15
'Refresh the data. This will fail if the file cannot be reached
vsoDataRecordset.Refresh
Exit Sub
ErrorHandler:
Debug.Print "Error: ", Err.Description
End Sub
Share with your friends: |