Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.


Info

Download the

Solution Example here:

solution CustomAssetTree.dbsln

Software Version: 10.1

.

Built with v10.

Summary

This solution example demonstrates how to create a

fully customized

personalized asset tree

with personalized icons and selected tags

.

Image Removed



Summary

This solution example demonstrates how to create

Technical Information

In this example, an asset tree is generated, starting with a root node labeled "Historian Tags." Custom icons are applied to each level of the tree, with different icons used for both individual tags and hierarchical levels. The tags displayed in this tree come from a specific data table within the historian database, and any modifications to the data are reflected dynamically in the tree structure.
The implementation steps are as follows:
1 - Asset Tree Initialization
The root of the tree is labeled "Historian Tags."
The method InitializeCustom() is used to set up the tree with custom icons for different asset levels.

Code Block
// Root name and icons
string rootName = "Historian Tags";
ImageBrush imageRoot = null;
ImageSource iconRoot = null;

// Initialize root icon
int imageID = await GetImageIDFromName("Resource1");
Stream stream = ConfigHelper.GetImage(imageID) as Stream;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();

// Assign icon to ImageBrush
imageRoot = new ImageBrush();
imageRoot.ImageSource = bitmapImage;
iconRoot = imageRoot.ImageSource;

// Initialize asset tree with root element
ElementItem tagRoot = new ElementItem(true, rootName, iconRoot, null, null);
control.InitializeCustom(new ElementItem[] { tagRoot }, this.GetObjectChildren);

The GetObjectChildren method plays a key role in managing and expanding the tree structure. It dynamically retrieves child elements from the historian or any associated asset, ensuring that the tree reflects any changes in the data.

2 - Custom Icons for Each Level:
Three types of icons are used: one for the root, another for levels, and one for individual tags.
These icons are retrieved using the GetImageIDFromName() method, which fetches the appropriate images from the project's resources.
ImageBrushes are created to hold these icons and are then applied to the tree elements accordingly.

Code Block
int resourceImageID = await GetImageIDFromName("Resource1");
Stream resourceStream = ConfigHelper.GetImage(resourceImageID) as Stream;
BitmapImage resourceBitmapImage = new BitmapImage();
resourceBitmapImage.BeginInit();
resourceBitmapImage.StreamSource = resourceStream;
resourceBitmapImage.EndInit();

imageLevel = new ImageBrush();
imageLevel.ImageSource = resourceBitmapImage;
iconLevel = imageLevel.ImageSource;
3 - Tag Parsing and Hierarchical Structure:
Tags are fetched from the HistorianTable table 1  in the database.
Any tag names that contain ".Value" are automatically cleaned up to remove this suffix.
Tags that are not prefixed with "Tag." are modified accordingly to ensure consistent naming conventions.
The parsed tags are then added to a hash set to track which tags have been loaded into the asset

with personalized labels, icons and tags.

Image Added


Technical Information

To customize asset trees with your own icons, you first need to add images to your solution. To do so, go to Displays → Images. You will be taken to a table containing all of your solution’s images. Click on “Import from a File“ and use the file browser to select the image you want. Once you are done, your Images table should look like this:

Image Added

With the images in our solution file, we prepare the tags we want to display in the asset tree. These tags are stored in Table1 of the Historian Database. The tags' names are modified to follow a convention, and then they are added to a hash set, to track which tags are loaded into the tree.

Code Block
DataTable table = await TK.ProjectDB.GetDataTableAsync("HistorianHistorianTags");
this.hashOfHistorian = new HashSet<string>();
 
foreach (DataRow row in table.Rows) {
    string tagName = TK.To<string>(row["TagName"]);
    
    // Remove ".Value" suffix if present
    if (tagName.EndsWith(".Value", StringComparison.CurrentCultureIgnoreCase)) {
        tagName = tagName.Remove(tagName.LastIndexOf('.'));
    }
    
    // Add "Tag." prefix if missing
    if (!tagName.StartsWith("Tag.", StringComparison.CurrentCultureIgnoreCase)) {
        tagName = "Tag." + tagName;
    }
    
    // Add tag to hash set
    this.hashOfHistorian.Add(tagName.ToLower());
}

4 - Tag Display in Tree:
As users expand the asset tree, the method GetObjectChildren() is called to fetch and display child elements (tags or levels) dynamically.
Tags with array structures (e.g., Tag[0], Tag[1]) are properly displayed in the tree with custom icons.


To customize the asset tree, we first access the Display CodeBehind and get the asset tree’s control. Then, we apply the folder icon we imported into the solution (“Resource1“) to the tree root, and initialize the tree:

Code Block
TAssetsTree control = this.CurrentDisplay.GetControl("asset") as TAssetsTree;

ImageSource iconRoot;
string rootName = "Historian Tags";

// Retrieves the folder image's ID from the solution's database using the image's name
int imageID = await TK.GetImageIDFromNameAsync("Resource1");
// Prepares to transfer the image as a stream of bytes
Stream stream = await TK.GetImageAsync(imageID) as Stream;
BitmapImage bitmapImage = new BitmapImage();
// Creates a bitmap image from the byte stream
// The #if #else macros ensure the procedure works fine on both WPF and HTML5
#if	HTML5_BROWSER
	bitmapImage.SetSource(stream);
#else
	bitmapImage.BeginInit();
	bitmapImage.StreamSource = stream;
	bitmapImage.EndInit();
#endif
iconRoot = bitmapImage;

// Creates the custom root of the asset tree
ElementItem tagRoot = new ElementItem(true, rootName, iconRoot, null, null);
control.InitializeCustom(new ElementItem[] {tagRoot}, this.GetObjectChildren);


GetObjectChildren is the method responsible for generating the tree structure. Initially, this method loads the images that will be applied to levels and tags, just like in the code snippet above. The rest of the code handles tree expansion when items are clicked. The code snippet below extracts an asset’s name and deals with clicks on level assets:

Code Block
private async Task<ElementItem[]> GetObjectChildren(object sender, ElementItem item) {
	// List of elements to append to the tree
	List<ElementItem> elements = new List<ElementItem>();
	// Formats the string representing the selected item
	string asset = (item.FullDisplayString ?? "").Replace('\\', '.').Replace('|', '.').Replace(".[", "[");
	if(asset.StartsWith("Historian Tags/"))
	{
		asset = asset.Substring(15);
	}
	else asset = "Tag";
	// Creates a reference to the object represented by the selected item
	ObjRef objRef = TK.ObjServer.DB.GetObjRef(asset, false);
    if (objRef != null) {
		
Code Block
private async Task<ElementItem[]> GetObjectChildren(object sender, ElementItem item) {
    List<ElementItem> elements = new List<ElementItem>();
    string asset = (item.FullDisplayString ?? "").Replace('\\', '.').Replace('|', '.').Replace(".[", "[");

    // Fetch children based on asset type
    ObjRef objRef = TK.ObjServer.DB.GetObjRef(asset, false);
    if (objRef != null) {
        if (objRef.IsArrayBase) {
            // Handle array-based tags
            int[] dimensions = objRef.Dimensions;
            for (int i = 0; i < dimensions.Length; i++) {
                string strArray = "[" + i + "]";
                ElementItem elementItem = new ElementItem(true, strArray, iconTag, null, null);
                elements.Add(elementItem);
            }
        } else if (objRef.RunObj is ListObj) {
            // Handle list-based tags
            foreach (RunObj runObj in objRef.RunObj as ListObj) {
                string fullName = runObj.GetName().ToLower();
                if (this.hashOfHistorian.Contains(fullName)) {
				ElementItem elementItem;
				if(runObj is ITagObj)
					elementItem = new ElementItem(false, runObj.GetSimpleName(), iconTag, null, null);
               ElementItem else
					elementItem = new ElementItem(true, runObj.GetSimpleName(), iconLevel, null, null);
                    elements.Add(elementItem);
                }
            }
        }
    }
    return elements.ToArray();
}

And lastly, the event SelectedAssetEvent() handles the user’s selection of a tag, updating the display and showing the selected tag's name and value.

Code Block
private void SelectedAssetEvent(object sender, string level, string asset) {
    string selectedTag = (level ?? "").Replace('\\', '.').Replace('|', '.').Replace(".[", "[");
    if (!string.IsNullOrEmpty(asset)) {
        selectedTag += "." + asset;
    }

    // Update the display with the selected tag
    this.CurrentDisplay.GetControl("SelectedTagText").Text = selectedTag;
}
}


Note that the GetObjectChildren method is longer than this. Please check the CodeBehind of this solution’s MainPage to see the full method, which includes code to interpret and display array tags.

Finally, to display the value of each tag, we open the asset tree’s settings and configure its Selected Asset field as follows:

Image Added

Then, we configure the Label containing the tag’s name and value:

Image Added


Reference Information

→ See Asset Tree for for more information.

→ See Displays Images to learn more about images.


In this section:

Page Tree
root@parent
spacesV10