Now that the holidays are over, time to get some work done again.
I’ve been rewriting some older scripts recently (again), and a lot of them are prompting the modelers for values to be used in the scripts. I used to do this with a dialog box but now that PowerDesigner has forms, I’ve moved almost exclusively to extended attributes and forms. This gives me a lot more control over the form, and allows me to gather multiple parameters at the same time. It gives me greater control over lists of values, and it allows a script to “remember” the values chosen last time the script was run.
Let’s say I want to control access to the tables in my model through views. Within a model, there are three parameters I want to control for my view generation:
I could prompt the user three times, but that’s not terribly user friendly. I’m also very likely to regenerate or add new views over time, and I’ll likely want the same settings each time.
So, instead of prompting for each option individually, I’m going to create an extended attribute for each and a popup form that the user can use to set the values.
First, we create the three extended attributes we’ll use to control our script. Notice that I’ve created a category to keep them organized. I’ll create the same category for forms, templates etc. so it’s easy to see what’s related.
I’ve created “Place Access View On Diagram” as a boolean, and “Access View Suffix” as a string. “Access View Owner” needs to be a user defined in the model, so I’ve created it as a text attribute with a list of values. You can do this by clicking on the “Create” button next to the List of values: box on the attribute. When the template definition box pops up, enter this code in the editor:
This creates a new template called “access View Owner List”. I recommend you immediately go in, create your category, and then move the template into it for organizational purposes.
Now that we have our attributes, we need a way to set them (duh). So let’s make a form. Create our category, create our blank form, set it as a dialog box, and use the “Add Attribute” button to add our new extended attributes. Our extended model definition now looks like this:
Now we’ve got our extended attributes, and our form. We need a script with a few extra lines. Since the point of this post is parameters, not views, I’ve condensed everything but the dialog down to a couple of dots.
So here’s our script:
Set dlg = obj.CreateCustomDialog(“Access Control Views”)
If not dlg is Nothing Then
And here’s the result:
Now that we’ve set the values, we need to get them for the script:
owner = obj.getExtendedAttributeText(“Access View Owner”)
suffix = obj.getExtendedAttributeText(“Access View Suffix”)
display = obj.getExtendedAttributeText(“Place Access View on Diagram”)
Done. Now the user only has to set the values the first time the execute the script or if they change the values and you can see what values were chosen by looking at the extended attributes.
Next time, we’ll expand on this a bit with some additional options to make this more flexible.
Today’s post is on selections. Selections are a great way to script changes to multiple objects and a great way to make your custom methods more flexible.
Selections are just what they sound like, a group of objects, placed into a collection for processing. Let’s jump right in with a simple example:
Dim mySel Set mySel = ActiveModel.CreateSelection mySel.AddObjects ActiveModel, cls_table, false, true
The first two lines declare and create your selection. The third line adds all objects of the given class, in this case tables, to the selection. The last two parameters (false, true) tell the script to skip shortcuts and to recurse through sub-packages. Now that we have our selection, how do we use it. The following lines will loop through the collection. For my examples, I’m using SetNameAndCode to rebuild the codes on all the objects in the selection, just to demonstrate the concept.
Dim myObj Dim mySel Set mySel = ActiveModel.CreateSelection mySel.AddObjects ActiveModel, cls_table, false, true For each myObj in mySel.Objects With myObj Output .Name .SetNameAndCode .Name, "", True end with Next
So far, so good. It gets better though. You’re not restricted to a single class in a selection. So if you’d like to rebuild the codes for both tables and views, you can add both classes. Our revised code looks like:
Dim mySel Dim myObj Set mySel = ActiveModel.CreateSelection mySel.AddObjects ActiveModel, cls_table, false, true mySel.AddObjects ActiveModel, cls_view, false, true For each myObj in mySel.Objects With myObj Output .Name .SetNameAndCode .Name, "", True end with Next
Without a selection, we’d be stuck doing something like:
For each tab in ActiveModel.tables Output tab.Name Tab.SetNameAndCode Tab.Name, "", True Next For each view in ActiveModel.views output view.name view.SetNameAndCode view.Name, "", True Next
That’s not too bad, but notice our SetNameAndCode sample is very short. Also, we’re only dealing with two classes and we’re only dealing with objects in the root of the model. If we want to recurse through packages, there’s a lot of additional work yet to be done.
Now, suppose we don’t want to include all objects of a particular class, but we’d like to be a bit more choosey. We have a couple of options:
The first, AddActiveSelectionObjects method, does exactly what it says it does. Whatever objects you currently have selected, either in the browser or the active diagram are added to your current selection. I’ll focus on the other two.
The second two provide dialog boxes which you can use to define your selection manually.
Here’s an example:
Dim mySel Set mySel = ActiveModel.CreateSelection mySel.ShowObjMultiSelection ActiveModel, "Tables"
The first parameter is the name of the parent object. In this case, the active model. The second parameter, this case “Tables” is the name of the collection you want to select from. You’ll get a dialog that looks like this:
Choose the objects you want and processing using the same select loop. Instead of the active model, the first parameter can be changed a particular table, view, etc. This allows you to choose from objects that apply only to that object. This is very handy in a custom method. For example, this method would allow you to select references on a particular table. Keep in mind that the selection itself is still a property of the model, not Obj.
Sub %Method%(obj) dim mySel dim myObj Set mySel = ActiveModel.CreateSelection mySel.ShowObjMultiSelection Obj, "InReferences" for each myObj in mySel.Objects With myObj Output .Name .SetNameAndCode .Name, "", True end with next End Sub
If you’ve looked at the VBScript help file, you probably know there’s a great example of how to build your own custom selection list for the multi-selection method. I’m going to change it just a bit to show you how to get a single selection dialog with both inReferences and outReferences. This also demonstrates the Add method available to any collection.
Sub %Method%(obj) dim myObj dim ref'add all of the objects we’re interested in to the collection Set mySel = ActiveModel.CreateSelection For each ref in Obj.inReferences mySel.Objects.Add(ref) next For each ref in Obj.outReferences mySel.Objects.Add(ref) next 'now show the objects in the selection in your dialog mySel.ShowObjMultiSelection mySel, "Objects" for each myObj in mySel.Objects With myObj Output .Name .SetNameAndCode .Name, "", True end with next End
The other method of adding objects to a selection, SubShowObjectPicker, behaves quite a bit differently. I don’t use this method nearly as often, but we’ll cover it just for completeness.
Unlike the mult-selection dialog, ShowObjectPicker always (as far as I can tell) starts from the active model. You can choose which class of object the user gets to choose from (e.g. tables or columns) but you can’t choose to show only columns belonging to a specific table. You can filter by stereotype but unless you’re going to change the stereotype for an object at runtime (and set it back when you’re done), this isn’t an approach I find particularly useful. Anyway, here’s a code snippet and the result.
Dim mySel Set mySel = ActiveModel.CreateSelection mySel.ShowObjectPicker "Table:Column"
This gives you the following dialog:
Well, that’s enough for now. More to come.
So now that I’ve extolled the virtues of ExecuteCustomMethod, it’s time for me to gripe. Turns out ExecuteCustomMethod is only available for sub-classes of ExtensibleObject. Those aren’t the only classes that can have a custom method, it’s just that classes that aren’t derived from extensible method can’t have their custom methods executed from another object or script.
Now, most of the objects you work with on a regular basis are going to support execute custom method (most of the stuff you’d put on a diagram is covered), but the diagram itself isn’t, which is where I got caught. So, if you follow my advice and start using ExecuteCustomMethod be aware of those classes that can’t expose a custom method before you write a bunch of code that won’t work.
I’ve sliced the classes that WON’T work from the help file in case you’re interested.
+-ShortcutBase +-TargetModel +-BaseReplication +-BaseDiagram | \-PackageDiagram | +-BaseDialogView | | +-BaseExtensibleDialogView | | | \-DependencyMatrix | | +-RequirementsDocumentView | | +-TraceabilityMatrixView | | \-UserAllocationMatrixView | +-BusinessProcessDiagram | +-ProcessHierarchyDiagram | +-ProcessServiceDiagram | +-BaseLogicalConceptualDiagram | | +-ConceptualDiagram | | \-LogicalDiagram | +-InformationLiquidityDiagram | +-TransformationDiagram | +-TransformationControlFlowDiagram | +-FreeDiagram | +-ProjectDiagram | +-FrameworkMatrixDiagram | +-EnterpriseArchitectureDiagram | | +-ApplicationArchitectureDiagram | | +-CityPlanningDiagram | | +-OrganizationChartDiagram | | +-TechnologyInfrastructureDiagram | | +-ProcessMapDiagram | | +-BusinessCommunicationDiagram | | \-ServiceOrientedDiagram | +-ImpactAnalysisDiagram | +-MeriseTreatmentDiagram | | +-FlowDiagram | | +-ConceptualDiagram | | \-OrganizationalDiagram | +-BaseUMLDiagram | | +-ClassDiagram | | +-UMLPackageDiagram | | +-UseCaseDiagram | | +-SequenceDiagram | | +-ComponentDiagram | | +-ActivityDiagram | | +-StatechartDiagram | | +-DeploymentDiagram | | +-CommunicationDiagram | | +-InteractionOverviewDiagram | | +-ObjectDiagram | | \-CompositeDiagram | +-PhysicalDiagram | +-MultidimensionalDiagram | \-XMLDiagram +-ReportBaseClass | \-ReportParagraphContainer | \-BaseReport | \-BaseModelReport | +-IntraModelReport | \-MultiModelReport | +-BaseLinkObject | | +-ExtendedDependency | | +-ConditionalLink | | | +-MessageFlow | | | | +-Flow | | | | \-ResourceFlow | | | +-ControlFlow | | | \-FlowLink | | | +-Transition | | | \-ActivityFlow | | +-ReferenceJoin | | +-ViewReferenceJoin | | +-CubeDimensionAssociation | | +-BaseLogicalInheritanceLink | | | +-InheritanceLink | | | \-InheritanceLink | | +-AssociationLink | | +-RelationshipJoin | | +-DependencyLink | | +-BaseOperationTriggering | | | +-OperationTriggering | | | \-PhaseTriggering | | +-EventEmission | | | +-OperationEmission | | | \-PhaseEmission | | +-InnerLink | | +-InstanceLink | | \-AssociationClassLink | +-CompositionLink | | +-Permission | | +-FaultMessage | | +-DataAccess | | +-BaseDataMapping | | | +-EmissionDataMapping | | | \-ReceptionDataMapping | | +-RoleAssociation | | \-TraceabilityLink | +-BaseStructuralFeatureMapping | | +-DefaultStructuralFeatureMapping | | +-ExternalColumnMapping | | +-StructuralFeatureMapping | | | +-ColumnMapping | | | +-AttributeMapping | | | \-MeasureMapping | | +-BaseAttributeMapping | | | +-EntityAttributeMapping | | | +-AssociationAttributeMapping | | | \-InheritanceAttributeMapping | | +-ExternalBaseAttributeMapping | | | +-ExternalEntityAttributeMapping | | | +-ExternalAssociationAttributeMapping | | | \-ExternalInheritanceAttributeMapping | | +-ClassAttributeMapping | | +-AttributeMapping | | +-ColumnMapping | | +-AttributeMapping | | +-TableColumnMapping | | +-ClassAttributeMapping | | \-ExternalAttributeMapping | +-BaseMappingBehavioralFeatureMapping | | +-DefaultBehavioralFeatureMapping | | \-OperationMapping | +-Artifact | +-ListReportResultSet | +-BaseImpactAnalysisObject | | \-ImpactAnalysisObject | +-IndexColumn | +-EventScript | +-ImpactAnalysisCategory | +-DataAccess | +-Annotation | +-BaseInteractionFragment | | +-InteractionReference | | \-InteractionFragment | +-InteractionActivity | +-UserAllocation | \-Component | +-AnnotatedComponent | | +-AnyAttribute | | +-AttributeBase | | | +-Attribute | | | \-AttributeGroup | | +-Content | | | +-ComplexContent | | | \-SimpleContent | | +-Derivation | | | +-Extension | | | +-Restriction | | | +-SimpleTypeList | | | \-SimpleTypeUnion | | +-External | | | +-Import | | | +-Include | | | \-Redefine | | +-Facet | | | +-EnumerationFacet | | | +-MaxExclusiveFacet | | | +-MaxInclusiveFacet | | | +-MinExclusiveFacet | | | +-MinInclusiveFacet | | | +-NumericFacet | | | | +-FractionDigitsFacet | | | | +-LengthFacet | | | | +-MaxLengthFacet | | | | +-MinLengthFacet | | | | \-TotalDigitFacet | | | +-PatternFacet | | | \-WhitespaceFacet | | +-Field | | +-IdentityConstraint | | | +-Key | | | +-KeyRef | | | \-Unique | | +-Notation | | +-Particle | | | +-Group | | | +-Any | | | +-Element | | | \-GroupParticle | | +-Selector | | \-Type | | +-ComplexType | | \-SimpleType | +-Annotation | \-AnnotationItem | +-AppInfo | \-Documentation +-ExtendedBaseCollection | +-ExtendedCollection | \-ExtendedComposition +-GenerationTargetDefinition | +-ReportLanguage | +-ExtendedModelDefinition | +-UserProfile | +-DBMS | +-ProcessLanguage | +-AnalysisRule | +-ObjectLanguage | \-SchemaLanguage +-ObjectSelection +-PersistentSelectionManager +-PersistentSelection +-BaseTransformation | +-MDATransformation | \-Transformation | +-Partitioning | | +-HorizontalPartitioning | | \-VerticalPartitioning | \-TableCollapsing +-TransformationSource +-TransformationTarget | +-TargetTable | \-Partition +-XmlNamespaceBase | +-Namespace | +-XmlNamespace | \-XmlNamespace +-ArtifactFolder +-Settings +-BaseAttribute | +-InheritanceAttribute | +-AssociationAttribute | \-EntityAttribute +-DataStructureSortedColumn +-DataStructureJoin +-ImpactAnalysisImpactedModel +-AttributeValue +-TypeParameterValue \-FlowMappingParameter
I’m in the process of upgrading some of my xem files and one of the things I’m doing a lot of is using ExecuteCustomMethod to simplify both my global script library and a lot of my code. Many of the functions I write end up being accessible in the context menu both for the base object (e.g. Table), a package, and the model. In the past, I’ve either wrapped the common bits of code into the global tab or I’ve duplicated it into methods at the various levels.
Both of these approaches have problems. Putting everything in the global script tab leaves me with an enormous stack of code to go through when I’m making changes. On several occasions, I’ve managed to break a unit I wasn’t intending to change at all with a fat finger while doing my editing. It also separates the code from the object it’s working on, which for a complex XEM file makes maintenance a bit more difficult.
Copying the method code from the base object, to the package and model methods is easier. It keeps the volume of code in my global script tab more manageable and makes seeing what the method at each level is doing easier, but it leads to the inevitable slips where I update some copies but not all of them. Plus, as a former programmer, it’s hard to do without feeling guilty.
The solution (in case you didn’t read the post name) is ExecuteCustomMethod. It allows me to avoid the problems (and guilt) associated global methods and code duplication. I write my methods for an object, and then re-use them elsewhere by calling ExecuteCustomMethod.
If I want to put a method on a table
DoSomething, I can call that method elsewhere with:
Now I have all my code associated with tables in one place, and I can call them from anywhere without duplication. There are a few issues. I can’t return a value and I can’t pass parameters. There’s an easy solution to this problem. If I have to pass a parameter or get a a value back, I can always use a global (a model level ExtendedAttribute). This sounds like a bad idea, but for the types of things I would normally control with a parameter, and extended attribute may be a better idea anyway, since I can use it to store user preferences between executions. Return values are a little more problematic, but you’ve gotta do what you’ve gotta do.
Now I have most of my custom code associated with the object it acts on and my global script tab is reserved for functions or subroutines which need to return a value or aren’t object specific (my output function for example).
Best of luck, and I hope ExecuteCustomMethod saves you some time or trouble in the future too.