Moving on with my WPF series, we are now going to look at using StackPanels for a layout. A StackPanel is a layout that you can use to arrange child controls either vertically or horizontally. StackPanels are pretty simple to use and provide a nice way of organizing your layout without much effort. Using some creativity, one could build a very usable UI that someone can use for day to day operations.
A simple look at the StackPanel below will show a couple buttons and a label in a vertical layout.
#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" SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> <StackPanel > <Button Width = "100" Height = "100" Background = "White" Content = "Button1"/> <Label Width = "100" Height = "100" Background = "White" Content = "Label"/> <Button Width = "100" Height = "100" Background = "White" Content = "Button2"/> </StackPanel> </Window> "@ $reader=(New-Object System.Xml.XmlNodeReader $xaml) $Window=[Windows.Markup.XamlReader]::Load( $reader ) $Window.Showdialog() | Out-Null
Notice that the layout of the controls are based on the order that you place them from top to bottom. This is always true with the Orientation property of StackPanel is set to the default value of Vertical. We can reverse this layout if we set the Orientation to Horizontal and also the FlowDirection property to RightToLeft as in the example below.
#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" SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> <StackPanel Orientation = "Horizontal" FlowDirection = "RightToLeft" > <Button Width = "100" Height = "100" Background = "White" Content = "Button1"/> <Label Width = "100" Height = "100" Background = "White" Content = "Label"/> <Button Width = "100" Height = "100" Background = "White" Content = "Button2"/> </StackPanel> </Window> "@ $reader=(New-Object System.Xml.XmlNodeReader $xaml) $Window=[Windows.Markup.XamlReader]::Load( $reader ) $Window.Showdialog() | Out-Null
As you can see, the order of the layout has been reversed using the FlowDirection and Orientation properties. In fact, all of this can be done during runtime as well! The example below will show you that just by clicking a specific button, you can change the layout of the StackPanel .
#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" SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> <StackPanel x:Name="StackPanel" Margin = "50,50,50,50" Background = 'White'> <Button x:Name = "button1" Height = "75" Width = "150" Content = 'Right -> Left' Background="Yellow" /> <Button x:Name = "button2" Height = "75" Width = "150" Content = 'Left -> Right' Background="Yellow" /> <Label x:Name = "label1" Height = "75" Width = "300" Background="MidnightBlue" Foreground = "White" HorizontalContentAlignment = 'Center' VerticalContentAlignment = 'Center'/> <Button x:Name = "button3" Height = "75" Width = "150" Content = 'Horizontal' Background="Red" /> <Button x:Name = "button4" Height = "75" Width = "150" Content = 'Vertical' Background="Red" /> </StackPanel> </Window> "@ $reader=(New-Object System.Xml.XmlNodeReader $xaml) $Window=[Windows.Markup.XamlReader]::Load( $reader ) #Connect to Controls $button1 = $Window.FindName("button1") $button2 = $Window.FindName("button2") $button3 = $Window.FindName("button3") $button4 = $Window.FindName("button4") $label1 = $Window.FindName("label1") $StackPanel = $Window.FindName("StackPanel") $window.Add_Loaded({ $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Button1.Add_Click({ $StackPanel.FlowDirection = "RightToLeft" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Button2.Add_Click({ $StackPanel.FlowDirection = "LeftToRight" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Button3.Add_Click({ $StackPanel.Orientation = "Horizontal" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Button4.Add_Click({ $StackPanel.Orientation = "Vertical" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Window.Showdialog() | Out-Null
Clicking the Horizontal button:
Clicking Right-> Left button:
So as you can see, there really is not a lot of work in adjusting the look and feel of the StackPanel layout as long as it fits your plans.
Adding another child control to the StackPanel layout is pretty simple to do. You will need to first create the new child control and then you can either add it to the end of the stack or insert the control into a specified index of the collection of controls. First, lets take a look at how we can view all of the child controls in the StackPanel .
$StackPanel.Children | Select Name
Name
—-
button1
button2
label1
button3
button4
There is a lot more information available, but for the sake of space, I just wanted to list out the names of each control (also a good reason why you want to supply a name for each control )
Adding a control can be done like this:
$newLabel = New-Object System.Windows.Controls.Label $newLabel.Content = 'New Label' $newLabel.Background = Get-Random ('Green','Red','Black','Blue') $newLabel.Foreground = 'White' $newLabel.Name = 'NewLabel' #One of two ways that this can be done $StackPanel.AddChild($newLabel) #OR $StackPanel.Children.Add($newLabel)
Inserting a control into the StackPanel requires that you know where each control is at in the StackPanel before you actually insert the control. I wrote a small function to make it easier to figure this out below:
Function Get-UIElementLookup { $i=0 $Script:uiElement = [ordered]@{} $StackPanel.Children | ForEach {$uiElement[$_.Name]=$i;$i++} }
By the way, [ordered] is only available in PowerShell V3 and finally allows us to use an ordered hash table. Running it allows me to know exactly where I would like to place my new control.
Get-UIElementLookup $uiElement
Name Value
—- —–
button1 0
button2 1
label1 2
button3 3
button4 4
Now with this info, I can easily determine that I want to insert a label (named newLabel) right after label1.
$newLabel = New-Object System.Windows.Controls.Label $newLabel.Content = 'New Label' $newLabel.Background = Get-Random ('Green','Red','Black','Blue') $newLabel.Foreground = 'White' $newLabel.Name = 'NewLabel' $StackPanel.Children.Insert(3,$newLabel)
Ok, lets check out the order of the controls again.
Get-UIElementLookup $uiElement
Name Value
—- —–
button1 0
button2 1
label1 2
NewLabel 3
button3 4
button4 5
Just like changing the layout, this can all be done during runtime as well. Now you are probably thinking that if I can add a control, then I can certainly remove one as well. You would be very much correct with this question! And in fact, here is where it is nice to know exactly what position the controls are at. To do this, we will use the RemoveAt() method which requires the position of the control as its parameter.
Lets go ahead and remove that newLabel.
$StackPanel.Children.RemoveAt(3) Get-UIElementLookup $uiElement
Name Value
—- —–
button1 0
button2 1
label1 2
button3 3
button4 4
And its gone!
StackPanels can be nested underneath one another very easily. So you could have a horizontal StackPanel with a nested vertical StackPanel and even have nest StackPanels underneath those! It really does help to make a UI look better by nesting StackPanels and adjusting the Orientation
One other thing that I didn’t really cover in detail was hiding a control using the Visibility property on a control. Basically,as it says, it just makes the control invisible on the UI, but still remains in its current place on the layout. This can be done during runtime by changing the property from Visible to Hidden and back.
The code below will give you a little demo of using all of these things that I have covered and makes the changes while running. Its not perfect and you will recognize some of the things that I already showed you, but gives you a good idea of what StackPanels are capable of and perhaps can give you some ideas on things to try as well!
Clicking on various buttons will produce the results that is labeled on the button. You will also see that I added a Canvas that is nested underneath 2 StackPanels just to show the flexibility that is available to work with.
StackPanel Demo Code
Function Get-UIElementLookup { $i=0 $Script:uiElement = [ordered]@{} $StackPanel.Children | ForEach {$uiElement[$_.Name]=$i;$i++} } #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" SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> <StackPanel Orientation = 'Horizontal'> <Button x:Name = 'CloseButton' MinWidth = "400" Content = 'Giant Button of Closing' FontSize = '30' Background = 'Black' Foreground = 'White' FontWeight = 'Bold'/> <StackPanel x:Name="StackPanel" Margin = "50,50,50,50" Background = 'White'> <Button x:Name = "button1" Height = "75" Width = "150" Content = 'Right -> Left' Background="Yellow" /> <Button x:Name = "button2" Height = "75" Width = "150" Content = 'Left -> Right' Background="Yellow" /> <Label x:Name = "label1" Height = "75" Width = "300" Background="MidnightBlue" Foreground = "White" HorizontalContentAlignment = 'Center' VerticalContentAlignment = 'Center'/> <Button x:Name = "button3" Height = "75" Width = "150" Content = 'Horizontal' Background="Red" /> <Button x:Name = "button4" Height = "75" Width = "150" Content = 'Vertical' Background="Red" /> <StackPanel x:Name = 'childstackpanel' Orientation = 'Horizontal'> <Button x:Name = "button5" Height = "75" Width = "150" Content = 'Add New Label' Background="White" /> <Button x:Name = "button6" Height = "75" Width = "150" Content = 'Hide Button5' Background="White" /> <Canvas Height = '80' Width = '100'> <Label Content = "Label in canvas" Background = 'LightGreen'/> </Canvas> </StackPanel> </StackPanel> </StackPanel> </Window> "@ $reader=(New-Object System.Xml.XmlNodeReader $xaml) $Window=[Windows.Markup.XamlReader]::Load( $reader ) #Connect to Controls $button1 = $Window.FindName("button1") $button2 = $Window.FindName("button2") $button3 = $Window.FindName("button3") $button4 = $Window.FindName("button4") $button5 = $Window.FindName("button5") $button6 = $Window.FindName("button6") $CloseButton = $Window.FindName("CloseButton") $label1 = $Window.FindName("label1") $StackPanel = $Window.FindName("StackPanel") $window.Add_Loaded({ $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) $Script:newLabelExists = $False Get-UIElementLookup }) $Button1.Add_Click({ $StackPanel.FlowDirection = "RightToLeft" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Button2.Add_Click({ $StackPanel.FlowDirection = "LeftToRight" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Button3.Add_Click({ $StackPanel.Orientation = "Horizontal" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $Button4.Add_Click({ $StackPanel.Orientation = "Vertical" $label1.Content = ("Orientation: {0}`nFlowDirection: {1}" -f $StackPanel.Orientation,$StackPanel.FlowDirection) }) $button5.Add_Click({ If ($Script:newLabelExists) { $StackPanel.Children.RemoveAt($uiElement.NewLabel) $button5.Content = "Add New Label" $Script:newLabelExists = $False Get-UIElementLookup } Else { $newLabel = New-Object System.Windows.Controls.Label $newLabel.Content = 'New Label' $newLabel.Background = Get-Random ('Green','Red','Black','Blue') $newLabel.Foreground = 'White' $newLabel.Name = 'NewLabel' #randomly insert it into the StackPanel $StackPanel.Children.Insert((0..$StackPanel.Children.count | Get-Random),$newLabel) $button5.Content = "Remove New Label" $Script:newLabelExists = $True Get-UIElementLookup } }) $Button6.Add_Click({ Switch ($button5.Visibility) { 'Visible' { $button6.Content = 'Show Button5' $button5.Visibility = 'Hidden' } 'Hidden' { $button6.Content = 'Hide Button5' $button5.Visibility = 'Visible' } } }) $CloseButton.Add_Click({ $Window.Close() }) $Window.Showdialog() | Out-Null
But that is not all folks! I also wanted to give a more practical example of what you can do with a StackPanel as well. This might seem odd to those of you who just come across this article without starting at the beginning, but I am going to be using a label as my output window as opposed to a textbox or gridview/listview. Don’t worry, those examples will come in my articles that discuss those items.
In the meantime, the code below will give you a simple System Information window that displays various data about your local system based on the button that is pushed.
One example shown below is using the OS Details button.
System Information UI Code
#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="Computer Information" WindowStartupLocation = "CenterScreen" SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray" ResizeMode = "NoResize"> <StackPanel Orientation = 'Horizontal' > <StackPanel x:Name = "StackPanel" Margin = "5" Background = 'White'> <Button x:Name = "osButton" Height = "75" Width = "150" Content = 'OS Details' Background="Yellow" /> <Button x:Name = "diskspaceButton" Height = "75" Width = "150" Content = 'Disk Space' Background="Yellow" /> <Button x:Name = "biosButton" Height = "75" Width = "150" Content = 'BIOS' Background="Yellow" /> <Button x:Name = "netinfoButton" Height = "75" Width = "150" Content = 'Network Information' Background="Yellow" /> </StackPanel> <Label x:Name = 'label1' Width = "400" FontSize = '15' Background = 'Black' Foreground = 'White' FontWeight = 'Bold'/> </StackPanel> </Window> "@ $reader=(New-Object System.Xml.XmlNodeReader $xaml) $Window=[Windows.Markup.XamlReader]::Load( $reader ) #Connect to Controls $osButton = $Window.FindName("osButton") $diskspaceButton = $Window.FindName("diskspaceButton") $biosButton = $Window.FindName("biosButton") $netinfoButton = $Window.FindName("netinfoButton") $label1 = $Window.FindName("label1") $StackPanel = $Window.FindName("StackPanel") $osButton.Add_Click({ $data = Get-WmiObject -Class Win32_OperatingSystem | ForEach { New-Object PSObject -Property @{ Computername = $env:computername OS = $_.Caption Version = $_.Version SystemDirectory = $_.systemdirectory Serialnumber = $_.serialnumber InstalledOn = ($_.ConvertToDateTime($_.InstallDate)) LastReboot = ($_.ConvertToDateTime($_.LastBootUpTime)) } } $label1.Content = $data | Out-String }) $diskspaceButton.Add_Click({ $data = Get-WmiObject -Class Win32_LogicalDisk | ForEach { New-Object PSObject -Property @{ DeviceID = $_.DeviceID TotalSpace = ("{0} GB" -f ($_.Size /1GB)) FreeSpace = ("{0} GB" -f ($_.FreeSpace /1GB)) VolumeName = $_.VolumeName DriveType = $_.DriveType } } $label1.Content = $Data | Out-String }) $biosButton.Add_Click({ $data = Get-WmiObject -Class Win32_Bios| ForEach { New-Object PSObject -Property @{ BIOSStatus = $_.Status Version = $_.SMBIOSBIOSVersion Manufacturer = $_.Manufacturer SerialNumber = $_.SerialNumber ReleaseDate = ($_.ConvertToDateTime($_.ReleaseDate)) } } $label1.Content = $Data | Out-String }) $netinfoButton.Add_Click({ $netInfo = Get-WmiObject -Class win32_networkadapterconfiguration | Where { $_.IPAddress -match "(\d{1,3}\.){3}\d{1,3}" } $related = @($netInfo.GetRelated()) $Data = $netInfo | ForEach { New-Object PSObject -Property @{ IPAddress = ($_.IPAddress | Out-String) DefaultGateway = ($_.DefaultIPGateway | Out-String) MAC = $_.MACAddress Subnet = ($_.IPSubnet | Out-String) DNS = ($_.DNSServerSearchOrder | Out-String) DHCP = $_.DHCPServer ID = $related[0].NetConnectionID Speed = ("{0}" -f ($related[0].Speed)) } } $label1.Content = $Data | Out-String }) $Window.ShowDialog() | Out-Null
That is it for StackPanels. As always, if you have questions or would like to see something else related to this, just leave me a comment and I will see what I can do. My plan for the next article is to take a look at Textboxes.
Pingback: PowerShell and WPF: Textbox | Learn Powershell | Achieve More