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
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:
After 6 more clicks:
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:
After:
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.
Derp!
Reading further down into the posting, I see that you’ve anticipated me.
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.
Good catches! I’ve made some updates based on your recommendations to this post. Thanks!
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
Nice post. Request for next post: Listview/Gridview!
Thanks! I do have plans to write about both of those soon, but I have a couple of other posts lined up now prior to Listview/Gridview.