Friday, October 28, 2022

Notes About PowerShell: Adding a TreeView to the GUI, Part 1

Previously we built the Simplest PowerShell GUI, and then added a few features to it here.

The script below is that last more complex GUI (with a few edits). It presents a window frame, with an OK button object, and a textbox object that asks for a name to be entered. We'll build on this script to add a treeview object. A treeview allows you to browse a tree-like structure, such as the file system, or an Active Directory domain, or the Windows Registry. So fire up the ol' PowerShell ISE, copy and paste in the following code (making sure the quotation marks don't turn into "smart" quotation marks), and then save the file as something like "tinker.ps1". Then try running it to make sure it works.

# Initialize the PowerShell GUI
Add-Type -AssemblyName System.Windows.Forms
  
# Create a new window form.
$Win = New-Object System.Windows.Forms.Form
$Win.ClientSize = '490,460'                              # Size of window frame.
$Win.text = "My Parent Window"                           # Title of the Win frame.
$Win.BackColor = "#ffffff"                               # Background color of the Win frame.

# Create an OK button.
$buttonOK = New-Object 'System.Windows.Forms.Button'     # Create the button.
$buttonOK.Text = "OK"                                    # Puts text on the button.
$buttonOK.Location = '350, 240'
$buttonOK.Size = '60, 25'
$buttonOK.Anchor = 'Bottom, Right'                       # Keep button in relative bottom-right of Win.
$buttonOK.DialogResult = "OK"                            # "None|OK|Cancel|Abort|Retry|Ignore|Yes|No".
$Win.Controls.Add($buttonOK)                             # Add the button to the form.
$Win.AcceptButton = $buttonOK                            # Click = "Close form; I accept it as it now is."

# A textbox.
$NameBox = New-Object "System.Windows.Forms.Textbox"
$NameBox.Size = "175,25"
$NameBox.Location = "290,40"
$NameBox.Text = "Babushka"                               # Pre-fill the box.
$Win.Controls.Add($NameBox)
$Win.ActiveControl = $NameBox                            # Once form shows, select this box.
  # Put a label with the box.
$Label_NameBox = New-Object 'System.Windows.Forms.Label'
$Label_NameBox.Text = "Enter your name:"
$Label_NameBox.Size = "150, 25"
$Label_NameBox.Location = '290, 20'
$Win.Controls.Add($Label_NameBox)

# Display the form.
$Win.ShowDialog() 

# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK") {
    Write-Host("The OK button was pressed. The name in the box is `"$($NameBox.Text)`".")
} elseif ($Win.DialogResult -eq "Cancel") {
    Write-Host("The X was pressed. I`'m not going to tell you the name that is in the box.")
} # end of If

Now we're ready to create the empty treeview object. The text in bold below is what we're adding.

   ...
  # Put a label with the box.
$Label_NameBox = New-Object 'System.Windows.Forms.Label'
$Label_NameBox.Text = "Enter your name:"
$Label_NameBox.Size = "175, 23"
$Label_NameBox.Location = '10, 20'
$Win.Controls.Add($Label_NameBox)

# The TreeView object.
$TreeView = New-Object System.Windows.Forms.TreeView
$TreeView.Location = "10,40"
$TreeView.Size = "250,400"
$Win.Controls.Add($TreeView)                             # Add the tree view to the main window form.
  # Put a label with the object.
$Label_TreeView = New-Object System.Windows.Forms.Label
$Label_TreeView.Location = "10,20"
$Label_TreeView.Size = "150,25"
$Label_TreeView.Text = "My Tree Object:"
$Win.Controls.Add($Label_TreeView)                       # Add the label to the form.

# Display the form.
$Win.ShowDialog()
   ...

That's a lot of code to be scrolling up and down in, and it's pretty easy to get lost in it. It'd be good if we could break up this big monolithic piece of code into smaller, more manageable chunks.

Most of this code is involved in the actual GUI-building, so the breaking up into chunks won't do a great deal for us at this point, but it will help to compartmentalize the different functional parts. We'll break this code into two pieces, and put them in separate files. On the one hand, that's a disadvantage, because now you're dealing with multiple files for your project, but on the other hand, it's an advantage, in that it breaks up the project into more manageable bite-size pieces.

So click on Edit / Select All in your PowerShell ISE, then Edit / Copy, to copy all the text into your Clipboard. Now click on click on File / New to open up a new blank tab, and then Edit / Paste to paste the script into a new tab. Go to the very bottom of the file and select everything after the "$Win.ShowDialog()" line, and then Edit / Cut that selection into the Clipboard. Now save this tab as something like "tinker_GUI.ps1".

You now have two almost identical files, one named "tinker.ps1" that has the entire script, and one named "tinker_GUI.ps1" that has everything except the last few lines.

Now go back to the "tinker.ps1" tab and with all the text still selected, Edit / Paste.

Now you have transferred all the GUI-building pieces of the script to an external file named "tinker_GUI.ps1", and left just a little bit of code in the original file, "tinker.ps1".

But the original "tinker.ps1" no longer knows anything about the code in the external "tinker_GUI.ps1" file, so we'll have to tell it about the external file. Here now is what the "tinker.ps1" file should look like, with code in bold that you need to add:


# Offload GUI-building into a different file to decrease clutter here.
. "$PSScriptRoot\tinker_GUI.ps1"

# The computer is now looping forever waiting for user input.
# Once the input is the OK button or the Window's X (or
# keyboard equivalent), the window closes and the code below runs.
If ($Win.DialogResult -eq "OK") {
    Write-Host("The OK button was pressed. The name in the box is `"$($NameBox.Text)`".")
} elseif ($Win.DialogResult -eq "Cancel") {
    Write-Host("The X was pressed. I`'m not going to tell you the name that is in the box.")
} # end of If

Save the file, and try running it. Note that the "tinker.ps1" tab must be the active tab to run the program. If the active tab is tinker_GUI.ps1, that portion of code will run, but the tinker.ps1 portion of code won't run. In this case, it really doesn't matter all that much. That won't always be the case, though; you need to run the tab that contains the "main" piece of code.

This dot-sourcing trick essentially tells the first script to go find the referenced file and paste that file's contents in at this dot-source location. When the script runs, that second file's contents replace this line, so it's just like all that second file's contents are in this file, but without the eyeball-clutter of having those contents actually in this file.

Now we can focus just on the mechanics of working with the tree. We'll look at that in "Notes About PowerShell: Adding a TreeView to the GUI - Part 2".

No comments: