Combine Uses and Where Used

UsesWhereUsed

I’ve recently been asked how to create a tab in Vault that represents both, the use and where used of an assembly component. Immediately I thought this could be a good example for Data Standard. Basically, when you select an assembly, Vault shows you the “Use” and “Where used” as separate windows. But what if you like to expand the use, click a sub-component and see where this component is used? So, in this post I’m going to create a new tab that combines the 2 views, like this:

UsesWhereUsed

The solution for achieving this is pretty simple. We just need a tab with 2 tree-views and a bit of code for populating the tree-views. In order to keep the tab performing fast, we will collect the needed information on the go. In other words, we will collect the children only when the tree gets expanded, and we will collect the parents only when a component gets selected.

Now, a tree-view is a bit complex, as it behaves like a nested list. A list where every element has a sub-list, and each sub-element has a sub-sub-list, etc. In our particular case we want to populate the sub-elements only when really needed. So, creating an according object with PowerShell would be possible, but quite complex.

I prefer to keep the simple things in PowerShell and place the more complex things in C#, and then use that code in PowerShell.

So, I’ve created a little DLL which contains one class. That class exposes a Name (string), an Icon (image) and a Children (list) and Parents (list) property. While the Name and Icon are obvious, the Children and Parents properties collect the file associations. The getter of these properties will only be called as the user expands the tree or clicks on a component in order to see the where-used. So, the data will be required little by little. This is how the DLL looks like:

public class TreeNode
{
  Connection _con = null;
  File _file = null;
  WebServiceManager _svc { get { return _con.WebServiceManager; } }

  public string Name { get { return _file.Name; } }

  public List<TreeNode> Children {
    get {
      List<TreeNode> children = new List<TreeNode>();
      FileAssocArray[] fileAssociations = _svc.DocumentService.GetLatestFileAssociationsByMasterIds(new long[] { _file.MasterId }, FileAssociationTypeEnum.None, false, FileAssociationTypeEnum.Dependency, false, false, false,false);
      if (fileAssociations.First().FileAssocs != null)
        foreach (var fileAssociation in fileAssociations.First().FileAssocs)
          children.Add(new TreeNode(fileAssociation.CldFile, _con));    return children;
    }
  }

  public List<TreeNode> Parents
  {
    get {
      List<TreeNode> parents = new List<TreeNode>();
      FileAssocArray[] fileAssociations = _svc.DocumentService.GetLatestFileAssociationsByMasterIds(new long[] { _file.MasterId }, FileAssociationTypeEnum.Dependency, false, FileAssociationTypeEnum.None, false, false, false,false);
      if (fileAssociations.First().FileAssocs != null)
        foreach (var fileAssociation in fileAssociations.First().FileAssocs)
          parents.Add(new TreeNode(fileAssociation.ParFile, _con));
      return parents;
    }
  }

  public BitmapImage Icon
  {
    get {
      var props = _con.PropertyManager.GetPropertyDefinitions("FILE", null, PropertyDefinitionFilter.IncludeAll);
      var def = props["EntityIcon"];
      var fileIter = new FileIteration(_con,_file);
      ImageInfo prop = _con.PropertyManager.GetPropertyValue(fileIter, def, null) as ImageInfo;
      System.IO.MemoryStream ms = new System.IO.MemoryStream();
      prop.GetImage().Save(ms, System.Drawing.Imaging.ImageFormat.Png);
      prop.Dispose();
      System.Windows.Media.Imaging.BitmapImage bImg = new System.Windows.Media.Imaging.BitmapImage();
      bImg.BeginInit();
      bImg.StreamSource = ms;
      bImg.EndInit();
      return bImg;
    }
  }

  public TreeNode(File file, Connection con)
  {
    _file = file;
    _con = con;
  }
}

The most complex part is the extraction of the file icon. As you can see, the constructor of this class requires a file (Vault file object) and a Vault connection.

So, the PowerShell implementation is pretty short, as we only have to gather from Vault the currently selected file and pass this file to the DLL including the current Vault connection. As we get the first node (root node) from the DLL, we simply set the ItemsSource of our left TreeView (Uses) to our root node. The user can now expand the Uses TreeView and more and more data will be loaded. The following code is stored in a separate PS1 file called UsesWhereUsed.ps1 located in the Data Standard Vault\addinVault folder.

 
Add-Type -Path 'C:\ProgramData\Autodesk\Vault 2015\Extensions\DataStandard\Vault\addinVault\UsesWhereUsed.dll'

function OnTabContextChanged_UsesWhereUsed
{
  $xamlFile = [System.IO.Path]::GetFileName($VaultContext.UserControl.XamlFile)
  if ($VaultContext.SelectedObject.TypeId.SelectionContext -eq "FileMaster" -and $xamlFile -eq "Uses - Where used.xaml")
  {
    $file = $vault.DocumentService.GetLatestFileByMasterId($vaultContext.SelectedObject.Id)
    $treeNode = New-Object UsesWhereUsed.TreeNode($file, $vaultConnection)
    $dsWindow.FindName("Uses").ItemsSource = @($treeNode)
  }
}

As you can see, the PowerShell code loads the DLL from the according folder, so have the DLL in such folder, and then offer a function OnTabContextChanged_UsesWhereUsed. We will have to add this function in the OnTabContextChanged function of the default.ps1 file, like this:

function OnTabContextChanged
{
  OnTabContextChanged_UsesWhereUsed
  ...
  ....
  ..
}

At the beginning we spoke about the tab. So, the file UsesWhereUsed.XAML shall be located under Data Standard\Vault\Configuration\File. The first TreeView for the Uses looks pretty simple:

<TreeView x:Name="Uses" Background="{x:Null}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Icon}"/>
<Label Content="{Binding Name}" Grid.Column="1" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

In order to display correctly the Icon and the Name, we define a HierarchicalDataTemplate with a little Grid in it. The HierarchicalDataTemplate offers the ability to define where the child elements shall come from, so you see the additional ItemsSource set to Children.

The TreeView for the WhereUsed looks pretty much the same, except for the binding of the HierarchicalDataTemplate that obviously points to the Parents, and the ItemsSource of the TreeView itself, which points to the selected item of the Uses TreeView.

<TreeView x:Name="WhereUsed" ItemsSource="{Binding SelectedItem.Parents, ElementName=Uses, Mode=OneWay}" Background="{x:Null}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Parents}">

In other words, when you select an element in the Uses TreeView (left side), the WhereUsed TreeView gets notified and filled with the Parents of the selected item. So, the 2 TreeView talks to each other by themselves.

Here you can download the complete package including the source code for the C# DLL https://github.com/coolOrange-Public/UsesWhereUsedTab/archive/master.zip

I hope that with this example, you see how easily XAML, PowerShell and C# can work together to achieve cool things with relatively few code.

This entry was posted in Data Standard, PowerShell, Vault API. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s