Wednesday, June 9, 2010

Things I learned from the Scripting Games 2010 #5

In this final entry in the ‘Things I learned from the Scripting Games’ series, we’ll go over two different yet related bits of information.  The first is displaying balloon tips in the notification area (System Tray).  The second is displaying log-like feedback in a form generated in PowerShell.  Because they both relate to presenting information or feedback to the user, and because it’s been awhile since I posted anything…and because I promised someone I would try real hard to update my PDFCreator post this week, both subjects fit in one post today.

Balloon Tips

Often times when we need to present the user with information in a graphical way, the messagebox is sufficient.  However, because the messagebox is synchronous by default (that is, the script pauses execution until the messagebox is closed) and there isn’t an easy way to create an asynchronous messagebox, they don’t fit every scenario.  Sometimes we just want to display some information to the user and move on.  Enter balloon tips.

For me, the most challenging part of making a balloon tip function wherever the script is running is the fact that it requires an icon.  How, exactly, is one to deliver an icon when (especially during the scripting games) all we have at our disposal is our PowerShell script file?  Let’s get our object setup and then we’ll answer that question.  First things first, load the forms assemblies:

[void][reflection.assembly]::Load("System.Windows.Forms, `
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

Now we can create a new NotifyIcon object, and setup the text for the balloon tip:

$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
$notifyIcon.BalloonTipTitle = "Balloon Title"
$notifyIcon.BalloonTipText =  "Balloon Text"

So, about that icon…turns out we can extract an icon from a file to use for the NotifyIcon object.  What file with icons should every relevant Windows system have?  Explorer.exe!

$notifyIcon.Icon = [System.drawing.icon]::ExtractAssociatedIcon(`
"$env:windir\explorer.exe")

OK, so now we have everything setup, let’s display that balloon tip.  First we have to make the NotifyIcon visible, then we can display the balloon tip for the number of milliseconds we specify:

$notifyIcon.Visible = $true
$notifyIcon.ShowBalloonTip(3000)

There we go.  Presenting information to the user in a way that doesn’t pause script execution, done.

Display Scrolling Text in a Form Window

Coming up with a “how many times would I actually use this” example for this particular technique has proven difficult.  Maybe it’s because I don’t have much experience creating GUIs designed for end users.  But that said, a wall (or box) of scrolling text seems so deceptively simple that it took me a while to figure out how to accomplish the task.

In advanced event 10 of the scripting games, one element was to present the user with progress of files being moved.  Instantly the picture of the nullsoft installer with it’s wall of text during the install sprung into my mind.  Achieving a similar effect isn’t too involved.  Here’s how we do it.

First we’re going to setup 3 objects: a form, a textbox, and a button.

$form = new-object windows.forms.form
$form.Size = New-Object System.Drawing.Size(270,125)

$txtBox = new-object windows.forms.textbox
$txtbox.Multiline = $true
$txtBox.WordWrap = $false
$txtBox.size = New-Object System.Drawing.Size(250,60)
$txtBox.ScrollBars = [System.Windows.Forms.ScrollBars]::Vertical

$btn = new-object windows.forms.button
$btn.Location = new-object system.drawing.point(0,60)
$btn.Text = "Go!"

$form.Controls.Add($txtBox)
$form.Controls.Add($btn)

So we’ve assigned some size and location information to the controls and added the controls to the form, which is standard business so far.  Two properties of the textbox control are important for this particualr task: Multiline & ScrollBars.  We make sure to set the multiline property to true, and assign vertical scrollbars to the textbox.  I also like to disable wordwrap, but that’s just me.

Next let’s setup the scriptblock that will run when we click the button:

$button_OnClick = {
    1..10 | % {
        $txtBox.Lines += $_
        $txtBox.Select($txtBox.Text.Length, 0)
        $txtBox.ScrollToCaret()
        $form.Update()
        start-sleep 1
    }
}
$btn.add_Click($button_OnClick)

So this is pretty simple.  We create a variable ($button_OnClick) and assign it a scriptblock in which we have a small loop.  We take the array created by the 1..10 range, and for each number we:

  1. Add the number to the lines array property of the textbox
  2. Move the caret (cursor) to the last character in the textbox
  3. Scroll the textbox view to the caret
  4. Update the form
  5. Pause for a second

We add that scriptblock to the click event of the button by using the hidden method add_Click($button_onClick).  As a side note in case you’re wondering, you use the –Force parameter of Get-Method to display hidden members.  Lastly let’s show the form:

$form.showdialog()

Give that button a push!  Sure it’s a simplistic example, but it can be used to pretty good effect.  The $form.update() method caught me by surprise, probably because I’m pretty green with this GUI business.  To see what happens without calling the update method, comment out or delete the $form.Update() line and run $form.showdialog() again.  It’s like the whole form freezes while the loop runs!  Bad jujus.

To see a more complete example you can check out my submission for Event 10 of the Scripting Games here.