PowerShell And WPF: Canvas

In a previous article, I demonstrated adding a button to your created window and using it. I also mentioned that only 1 button could have been added due to the window treating that as a single child and that only 1 child control can exist under the window in your UI. To get around this issue, you can use a number of layouts such as a canvas, grid or StackPanel, to name a few that can allow you to add more than one control object to your UI. Each one has its own strengths and weaknesses as far as when and where to use each one. Sometimes you might use multiple layouts and other times you might just use one.

For this article, as you can tell, I will be discussing the use of a Canvas as the layout of choice. Personally, I have not used a canvas in my UI builds, but that doesn’t mean that I won’t dive in to understand better how to use a canvas and also to show you one either!

Canvases are a basic layout that is unique to the other layouts because of the precise location of each control by specifying the coordinates on the canvas to place each of the controls. Besides that, there really isn’t a whole lot to the canvas that can’t be done better with other layouts. But being that I want to show off each control, I will look at some of the events,methods and properties of the Canvas, some of which will be similar to what I have showed with the Window control. So with that, lets load up the XAML and see our initial UI.

#Build the GUI
[xml]$xaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    Width = "800" Height = "600" ShowInTaskbar = "True">
        <Canvas x:Name="Canvas">
            <Button x:Name = "Button1" Height = "75" Width = "100" Content = 'Button1' ToolTip = "Button1"
            Background="Red" Canvas.Left = '50' Canvas.Top = '50'/>
            <Button x:Name = "Button2" Height = "75" Width = "100" Content = 'Button2' ToolTip = "Button2"
            Background="Yellow" Canvas.Left = '50' Canvas.Bottom = '50'/>
            <Button x:Name = "Button3" Height = "75" Width = "100" Content = 'Button3' ToolTip = "Button3"
            Background="Green" Canvas.Right = '50' Canvas.Bottom = '50'/>
            <Button x:Name = "Button4" Height = "75" Width = "100" Content = 'Button4' ToolTip = "Button4"
            Background="Blue" Canvas.Right = '50' Canvas.Top = '50'/>
        </Canvas>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

#Connect to Control
$button1 = $Window.FindName("Button1")
$button2 = $Window.FindName("Button2")
$button3 = $Window.FindName("Button3")
$button4 = $Window.FindName("Button4")
$Canvas = $Window.FindName("Canvas")


 
$Window.ShowDialog() | Out-Null

image

Here you have the Window that is using a Canvas as its layout. With the Canvas, I can now add multiple controls (buttons in this case) that I wouldn’t have been able to do with just a window. And another important thing is that I can also dictate where each button will reside at by using the coordinates on the Canvas for each button. The trick is to remember that you cannot have both Top and Bottom or Left and Right specified as only the first one specified will be used to place the control. The last thing worth knowing is that when you specify the value for either of the locations, it is telling you how far away from that direction will be padded away from that location.

Let me show this off a little by making some adjustments on the fly with each button. This is also important because you can’t use any of the child control properties (the button in this case) to control the new location of the child control during runtime. You will need to call [System.Windows.Controls.Canvas] with the SetLeft/SetRight/SetTop/SetBottom static method. This static method requires both the Control object and the new value to move the control to. In this case, I will push button1 which will bring everything into the center another 25.

##Events
#Button1 click event
$button1.Add_Click({
    #Move Button1
    $locationY = [System.Windows.Controls.Canvas]::GetTop($button1)    
    $locationX = [System.Windows.Controls.Canvas]::GetLeft($button1)
    [System.Windows.Controls.Canvas]::SetTop($button1,($locationY + 25))
    [System.Windows.Controls.Canvas]::SetLeft($button1,($locationX + 25))

    #Move Button2
    $locationY = [System.Windows.Controls.Canvas]::GetBottom($button2)    
    $locationX = [System.Windows.Controls.Canvas]::GetLeft($button2)
    [System.Windows.Controls.Canvas]::SetBottom($button2,($locationY + 25))
    [System.Windows.Controls.Canvas]::SetLeft($button2,($locationX + 25))

    #Move Button3
    $locationY = [System.Windows.Controls.Canvas]::GetBottom($button3)    
    $locationX = [System.Windows.Controls.Canvas]::GetRight($button3)
    [System.Windows.Controls.Canvas]::SetBottom($button3,($locationY + 25))
    [System.Windows.Controls.Canvas]::SetRight($button3,($locationX + 25))

    #Move Button4
    $locationY = [System.Windows.Controls.Canvas]::GetTop($button4)    
    $locationX = [System.Windows.Controls.Canvas]::GetRight($button4)
    [System.Windows.Controls.Canvas]::SetTop($button4,($locationY + 25))
    [System.Windows.Controls.Canvas]::SetRight($button4,($locationX + 25))
})

 

First click:

image

After 6 more clicks:

image

Each click brings the buttons closer and closer together until one overlaps the other.

So what can we do with a control that overlaps another on the canvas? Well, we can set it so the control on the bottom is overlapping the top one instead by adjusting the Z index of each control. In the example below, whenever you click on the button on the bottom, it will come to the top instead of being below the other button.

#Button click event
$button1.Add_Click({
    [System.Windows.Controls.Canvas]::SetZIndex($button1,1)
    [System.Windows.Controls.Canvas]::SetZIndex($button2,0)
})

$button2.Add_Click({
    [System.Windows.Controls.Canvas]::SetZIndex($button1,0)
    [System.Windows.Controls.Canvas]::SetZIndex($button2,1)
})
$button3.Add_Click({
    [System.Windows.Controls.Canvas]::SetZIndex($button3,1)
    [System.Windows.Controls.Canvas]::SetZIndex($button4,0)
})

$button4.Add_Click({
    [System.Windows.Controls.Canvas]::SetZIndex($button4,1)
    [System.Windows.Controls.Canvas]::SetZIndex($button3,0)
})

 

Before:

image

After:

image

Probably not the coolest example, but when you think about it, you can hide controls underneath each other if they are the same size. Perfect if you want to hide specific buttons or textboxes and make them appear on specific actions in the UI. Also important is that while I just used 0 and 1 to specify each index, for each control you want to stack on top of each other will require and extra Z index to be added (0,1,2 for 3 controls, etc…). Note: The Z index is not Canvas specific and can be applied to other layouts as well in their own way.

For something a little more fun, this next code will bring up one button that will move to a random location when you highlight it with your mouse. It is nothing really complex but shows something else that you can do with a canvas.

Function Set-Canvas {
    [cmdletbinding()]
    Param (
        [string]$Top,
        [string]$Bottom,
        [string]$Left,
        [string]$Right,
        $Control                       
    )
    Process {
        If ($PSBoundParameters['Top']) {
            [System.Windows.Controls.Canvas]::SetTop($Control,$Top)
            Write-Host ("Moving {0} to X: {1}" -f $Control.name, $Top)
        }
        If ($PSBoundParameters['Bottom']) {
            [System.Windows.Controls.Canvas]::SetBottom($Control,$Bottom)
            Write-Host ("Moving {0} to X: {1}" -f $Control.name, $Bottom)
        }
        If ($PSBoundParameters['Left']) {
            [System.Windows.Controls.Canvas]::SetLeft($Control,$Left)
            Write-Host ("Moving {0} to Y: {1}" -f $Control.name, $Left)
        }
        If ($PSBoundParameters['Right']) {
            [System.Windows.Controls.Canvas]::SetRight($Control,$Right)
            Write-Host ("Moving {0} to Y: {1}" -f $Control.name, $Right)
        }                       
    }
}
 
#Build the GUI
[xml]$xaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    Width = "800" Height = "600" ShowInTaskbar = "True">
        <Canvas x:Name="Canvas">
            <Button x:Name = "Button" Height = "75" Width = "100" Content = 'Push Me' ToolTip = "This is a button"
            Background="Green"/>
        </Canvas>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
 
#Connect to Control
$button = $Window.FindName("Button")
$Canvas = $Window.FindName("Canvas")
 
##Events

#Make the button move somewhere else
$button.Add_MouseEnter({
    Set-Canvas -Bottom (Get-Random -input (0..(564 - $button.ActualHeight))) -Right (Get-Random -input (0..(784 - $button.ActualWidth))) -Control $Button
})
 
$Window.ShowDialog() | Out-Null

 

Summary

As you can see, using a Canvas provides one possibly way of adding more than one control to your UI. You can set the control to an explicit location on the canvas by specifying a coordinate on the canvas. Moving a control around at runtime requires a little more work, but can be accomplished. In my opinion, there are better layouts that can be used to configure the layout of the controls, such as using a Grid or StackPanel. Regardless, this is just another possibility to use with your UI.

I am going to skip saying what control is next because I am not quite sure which one I will tackle next.

About Boe Prox

Microsoft Cloud and Datacenter MVP working as a SQL DBA.
This entry was posted in powershell, WPF and tagged , , , . Bookmark the permalink.

6 Responses to PowerShell And WPF: Canvas

  1. Daniel Petcher says:

    Derp!
    Reading further down into the posting, I see that you’ve anticipated me.

  2. Daniel Petcher says:

    Thanks to Pierre Cloutier for pointing-out the typos in the Connect to Control section. There’s another slight oddity in the first example, though.

    The event section uses the same variable for both horizontal and vertical button position:
    $location = [System.Windows.Controls.Canvas]::GetTop($button1)
    $location = [System.Windows.Controls.Canvas]::GetLeft($button1)
    The second line’s GetLeft call will overwrite the value of the first line’s GetTop call. Since they’re the same number all around, it makes no difference in the program’s function, but if you were to start playing with the location offsets in the two following lines, you’d quickly notice that only the number from the GetLeft line makes any difference. For more granular control over the button position, you could try this in each section:
    $locationY = [System.Windows.Controls.Canvas]::GetTop($button1)
    $locationX = [System.Windows.Controls.Canvas]::GetLeft($button1)
    [System.Windows.Controls.Canvas]::SetTop($button1,($locationY + 25))
    [System.Windows.Controls.Canvas]::SetLeft($button1,($locationX + 25))

    I casually read this series of blog postings a couple of weeks ago in front of the TV, but I gave-up on them when I ran into a few items that didn’t work on my system. After scouring the Internet, I’ve found nothing that suits my learning and coding goals anywhere near as well as your approach. I’m grateful to “pay it forward” with a few edits here and there. You’ve done the hard work of initially understanding it for me; now I just need to absorb it all into my own brain.

  3. Pierre Cloutier says:

    Very instructive!
    Found a little typo:
    #Connect to Control

    $button2 = $Window.FindName(“button2”)
    It should be:
    $button2 = $Window.FindName(“Button2”)
    same for $button3 and $button4

  4. Nice post. Request for next post: Listview/Gridview!

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