Spotlight on Controls: The Versatile Tag Property

May 23rd, 2013 by David Corrales
Last updated on May 23rd, 2013

 

You may not be aware that each WinForm Control has a Tag property which can be very useful in certain circumstances.

Why use the Tag Property?

Use the Tag property when you need to associate an object or store state information to a particular control. This ability to assign any object to the property, be it a control, value, psobject or scriptblock, is what makes the property so versatile.

Below are examples demonstrating how the Tag property can be used in GUI scripts.

 

Example 1:

The Tic Tac Plebius PowerShell game is a good example of how the Tag property can be utilized. The game uses the Tag property to keep track of which player pressed the button. Whenever a button is pressed, it calls the ToggleButton function which in turn assigns the button to the player (in other words it assigns an ‘X’ or ‘O’ to a space):

function ToggleButton ([System.Windows.Forms.Button]$button)
{
    if($button.Tag -eq $null)
    {
        #Write-Host $script:IsPlebiusTurn
        if($script:IsPlebiusTurn -eq $true)
        {
            $script:IsPlebiusTurn = $false
            $button.Image = $imagelistPlayerImages.Images[0]    
            $pictureboxPlayer.Image = $imagelistPlayerImages.Images[1]
            $button.Tag = "Plebius"
            $labelMessage.Text = "Player's Turn"
            $labelMessage.ForeColor = "Green"
        }
        else
        {
            $script:IsPlebiusTurn = $true
            $button.Tag = "SAPIEN"
            $button.Image = $imagelistPlayerImages.Images[1]    
            $pictureboxPlayer.Image = $imagelistPlayerImages.Images[0]
            $labelMessage.Text = "Player's Turn"
            $labelMessage.ForeColor = "Red"
        }
        
        CheckForWin
    }
}

 

Afterwards the game checks the button’s Tag property to see if the same player got three in a row. If so, that player is declared the winner.

function IsWinner ([string]$Player)
{
  
#Lets Check all eight combinations
  
if($buttonTL.Tag-eq $Player -and $buttonTM.Tag-eq $Player -and $buttonTR.Tag-eq $Player)
    {
      
$buttonTL.Tag= $buttonTM.Tag= $buttonTR.Tag= $true
      
return $true
    }

}

Example 2:

A practical use for the Tag property is when you are working with a TreeView control and you want to associate each node with an object. If you look at the ServicesTreeView sample (Spotlight on the TreeView Control), it uses the node’s Tag property to keep track of the original service object the node represents:

function Load-TreeServices
{
    #Disable Drawing
    $treeviewServices.BeginUpdate()
    
    $services = Get-Service
    $treeviewServices.Nodes.Clear()
    $Root = $treeviewServices.Nodes.Add("Services on $env:ComputerName")
    
    foreach($service in $services)
    {
        $node = $Root.Nodes.Add($service.DisplayName)    
        $node.Tag = $service
        $node.ToolTipText = $service.Name
        $node.ContextMenuStrip = $contextmenustripService
        if($service.Status -eq 'Running')
        {    $node.StateImageIndex = 0 }
        elseif($service.Status -eq 'Paused')
        {    $node.StateImageIndex = 2 }
        else
        {    $node.StateImageIndex = 1 }
    }
    
    $Root.Expand()    
    
    #Enable Drawing
    $treeviewServices.EndUpdate()
}

 

The service object is retrieved using the node’s Tag whenever the user wants to stop or start the service:

$treeviewServices_NodeMouseDoubleClick=[System.Windows.Forms.TreeNodeMouseClickEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.TreeNodeMouseClickEventArgs]
    $service = $_.Node.Tag
    
    if($service -ne $null)#Is there is a service object?
    {
        $service.Refresh() #Update the status
        
        if($service.Status -eq 'Running')
        {
            if($service.CanPauseAndContinue)
            {
                #$service.Pause()
                Write-Host 'Pause Service'
                $_.Node.StateImageIndex = 2
            }
            else
            {
                #$service.Stop()
                Write-Host 'Stop Service'
                $_.Node.StateImageIndex = 1
            }
        }
        elseif($service.Status -eq 'Paused')
        {
            #$service.Continue()
            Write-Host 'Continue Service'
            $_.Node.StateImageIndex = 0
        }
        else
        {
            #$service.Start()    
            Write-Host 'Start Service'
            $_.Node.StateImageIndex = 0
        }
    }
    
}

 

The Tag property makes it easy to access the object with which a particular control is associated. Without this property, it would be difficult to keep track of what each node represents. As you can see, this works great when you have a one to one association, but what if you need to associate more than one object?

This issue recently came up at the PowerShell Summit:

Example 3:

An attendee at the PowerShell Summit was having an issue with his GUI script / dashboard app. The GUI consisted of multiple (dynamically created) timer/label pairings in which each individual timer would periodically update the associated label with the results of a particular script. Since he was creating the controls dynamically, he couldn’t use individual reference variables such as $timer1 or $label1; instead he set a global variable to handle the controls. The problem was whenever he added a new set of controls the contents of the labels would no longer update with their individual information. This happened because he was overriding global variables each time he created a new pair of controls. In order to solve his problem, I introduced him to the Tag property. But there was a complication: each timer needed to update a specific label using a particular command. In other words,  he needed to keep track two objects but the Tag property only references one object. So what to do? In order to solve this problem, we created a psobject and assigned the associated objects to note properties. Then we assigned the psobject itself to the timer’s Tag property. This allowed us to track all the objects from a single location:

$timer.Tag = New-Object PSObject -Property @{"Label"=$label; "Script"=$script}

In the timer’s tick event we accessed the psobject directly via the Tag property:

$timer_Tick={
    #Update the associated label's text with the result of the script
    $this.Tag.Label.Text = Invoke-Command $this.Tag.Script | Out-String
}

Note: The $this variable references the invoker of the event. In this case, it’s the timer control.

And thus the Tag property saved the day!

 

The use of the Tag property is a technique not many scripters are aware of, but it can help make GUI scripting easier. Because you can store any object in the property, it is a very versatile and useful tool to have under your belt.

Remember, if you need to associate more than one object, utilize a psobject to reference multiple objects via its note properties.

 

Related Articles:

Tic Tac Plebius: A PowerShell Game

Spotlight on the TreeView

Spotlight on the ContextMenuStrip (expands upon services example)

 

 
[Google+]   [Facebook]   [LinkedIn]   [StumbleUpon]   [Digg]   [Reddit]   [Google Bookmark]  

Tags: , , ,