posts - 104 , comments - 115 , trackbacks - 0

My Links

News

Disclaimer: Right here... Github: Code all the things! PS GUI Series: < Coming Soon! > PS Scripts: < Coming Soon! >

Tag Cloud

Archives

Post Categories

Blogs

Forums

Lync

Storage

Tools

Virtualization

Web comics

Tuesday, February 19, 2019

[Powershell Series] Creating Graphical User Interfaces – Working with rows and columns

This blog has move to: http://hornedowl.net/

Rows and columns are things I've recently started to dive in to since I was getting fairly annoyed when I was building larger user interfaces. They're pretty powerfull, but it took me a while to get my head around the capabilities… In essence you are setting up your grid element(s) to have predefined sizes, and drop working with the margin properties so whatever control you place in a row or column takes it up completely.

Also, it prevents your controls from changing position and size when you're resizing a window, as well as making alignment of controls easier. Pretty neat!

The start

So let's say we've built a nice little basic GUI to show some information.

Represented by the following XAML code:

<Window

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

x:Name="MainWindow"

Title="Rows and Columns"

Height="217.893"

Width="392.894"

>

 

<!-- Grid area -->

<Grid Margin="0,0,0,0">

<!-- A Label -->

<Label x:Name="lblLabel" Content="Label" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="52"/>

<!-- A Button -->

<!-- A Textbox -->

<TextBox x:Name="txtTextBox" HorizontalAlignment="Left" Height="20" Margin="67,14,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<Label x:Name="lblLabel0" Content="Label" HorizontalAlignment="Left" Margin="10,36,0,0" VerticalAlignment="Top" Width="52"/>

<Label x:Name="lblLabel1" Content="Label" HorizontalAlignment="Left" Margin="10,62,0,0" VerticalAlignment="Top" Width="52"/>

<Label x:Name="lblLabel2" Content="Label" HorizontalAlignment="Left" Margin="10,88,0,0" VerticalAlignment="Top" Width="52"/>

<TextBox x:Name="txtTextBox0" HorizontalAlignment="Left" Height="20" Margin="67,39,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<TextBox x:Name="txtTextBox1" HorizontalAlignment="Left" Height="20" Margin="67,64,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<TextBox x:Name="txtTextBox2" HorizontalAlignment="Left" Height="20" Margin="67,88,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<Label x:Name="lblLabel3" Content="Label" HorizontalAlignment="Left" Margin="202,10,0,0" VerticalAlignment="Top" Width="52"/>

<Label x:Name="lblLabel4" Content="Label" HorizontalAlignment="Left" Margin="202,35,0,0" VerticalAlignment="Top" Width="52"/>

<Label x:Name="lblLabel5" Content="Label" HorizontalAlignment="Left" Margin="202,64,0,0" VerticalAlignment="Top" Width="52"/>

<Label x:Name="lblLabel6" Content="Label" HorizontalAlignment="Left" Margin="202,88,0,0" VerticalAlignment="Top" Width="52"/>

<TextBox x:Name="txtTextBox3" HorizontalAlignment="Left" Height="20" Margin="254,14,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<TextBox x:Name="txtTextBox4" HorizontalAlignment="Left" Height="20" Margin="254,40,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<TextBox x:Name="txtTextBox5" HorizontalAlignment="Left" Height="20" Margin="254,66,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<TextBox x:Name="txtTextBox6" HorizontalAlignment="Left" Height="20" Margin="254,92,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

 

</Grid>

</Window>

Creating columns and rows

It's fairly easy to add in Columns and Rows in to your GUI, it's as simple as adding a few tags right under the <Grid> tag:

<Grid>

<Grid.ColumnDefinitions>

</Grid.ColumnDefinitions>

<Grid.RowDefinitions>

</Grid.RowDefinitions>

</Grid>

 

But this obviously doesn't create a row or column! It merely sets up the scaffolding for us to start defining them… In order to add a column or row we need to use the <RowDefinition Height=""/> or <ColumnDefinition Width=""/> tag.

 

In our case I want to have an edge of 10 pixels wide around our borders which would mean my top and bottom rows need a height of 10, and the left and right columns need a width of 10. For the labels I will use a width of 100 pixels, whilst the textboxes columns will get a width of 150. Since I don't want the labels on the right side to but up to the textboxes of the left side I will put in a column to separate them.

 

That would mean our grid code would look like this:

 

<Grid Margin="0,0,0,0">

<Grid.ColumnDefinitions>

<ColumnDefinition Width="10"/>

<ColumnDefinition Width="50"/>

<ColumnDefinition Width="100"/>

<ColumnDefinition Width="5"/>

<ColumnDefinition Width="50"/>

<ColumnDefinition Width="100"/>

<ColumnDefinition Width="*"/>

<ColumnDefinition Width="10"/>

</Grid.ColumnDefinitions>

<Grid.RowDefinitions>

<RowDefinition Height="10"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="*"/>

<RowDefinition Height="10"/>

</Grid.RowDefinitions>

</Grid>

 

Which looks like this in the GUI:

 

You've probably noticed I created a row and a column where the height and width property are set to "*". I call them filler rows/columns, since they will take up all the space that's still available in your window size.

 

Hey! Where did my controls go?!

Oh you noticed? Good! By default, all controls seem to live in Row 0, Column 0. So if you create columns and rows, your controls will still be there, you just won't be able to see them… But let's get that fixed now! Let's start with our very first label:

<Label x:Name="lblLabel" Content="Label" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="52"/>

There's a few properties we'll need to remove to meet our criteria of filling the space, and a few to add so it get's bound to a row and column

<Label x:Name="lblLabel" Grid.Row="1" Grid.Column="1" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

As you can see, I've added 2 tags: Grid.Row and Grid.Column. Both of these place your control in the respective values (and they start counting from 0!). I've also set the HorizontalAlignment and VerticalAlignment properties to stretch, so the label would take up all the space available, and removed my margin and width properties, since they're no longer needed… Rinse and repeat for all controls and you're gui will end up looking like this:

And the code:

<Window

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

x:Name="MainWindow"

Title="Rows and Columns"

Height="217.893"

Width="392.894"

>

 

<!-- Grid area -->

<Grid Margin="0,0,0,0">

<Grid.ColumnDefinitions>

<ColumnDefinition Width="10"/>

<ColumnDefinition Width="50"/>

<ColumnDefinition Width="100"/>

<ColumnDefinition Width="5"/>

<ColumnDefinition Width="50"/>

<ColumnDefinition Width="100"/>

<ColumnDefinition Width="*"/>

<ColumnDefinition Width="10"/>

</Grid.ColumnDefinitions>

<Grid.RowDefinitions>

<RowDefinition Height="10"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition Height="*"/>

<RowDefinition Height="10"/>

</Grid.RowDefinitions>

<!-- A Label -->

<Label x:Name="lblLabel" Grid.Row="1" Grid.Column="1" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<!-- A Button -->

<!-- A Textbox -->

<TextBox x:Name="txtTextBox" Grid.Row="1" Grid.Column="2" HorizontalAlignment="stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

<Label x:Name="lblLabel0" Grid.Row="2" Grid.Column="1" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<Label x:Name="lblLabel1" Grid.Row="3" Grid.Column="1" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<Label x:Name="lblLabel2" Grid.Row="4" Grid.Column="1" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<TextBox x:Name="txtTextBox0" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

<TextBox x:Name="txtTextBox1" Grid.Row="3" Grid.Column="2" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

<TextBox x:Name="txtTextBox2" Grid.Row="4" Grid.Column="2" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

<Label x:Name="lblLabel3" Grid.Row="1" Grid.Column="4" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<Label x:Name="lblLabel4" Grid.Row="2" Grid.Column="4" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<Label x:Name="lblLabel5" Grid.Row="3" Grid.Column="4" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<Label x:Name="lblLabel6" Grid.Row="4" Grid.Column="4" Content="Label" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

<TextBox x:Name="txtTextBox3" Grid.Row="1" Grid.Column="5" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

<TextBox x:Name="txtTextBox4" Grid.Row="2" Grid.Column="5" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

<TextBox x:Name="txtTextBox5" Grid.Row="3" Grid.Column="5" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

<TextBox x:Name="txtTextBox6" Grid.Row="4" Grid.Column="5" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Center" />

</Grid>

</Window>

 

But what if you have a control that needs to span different columns? For example a button?

 

 

By default, it will not show half of the button. But you can use the Grid.ColumnSpan property to overcome this nasty little problem…

<Button x:Name="btnButton" Grid.ColumnSpan="5" Content="Button" Grid.Column="1" HorizontalAlignment="stretch" Grid.Row="5" VerticalAlignment="center" />

And just so you know, there's a Grid.RowSpan as well!

<Button x:Name="btnButton" Grid.ColumnSpan="5" Content="Button" Grid.Column="1" HorizontalAlignment="stretch" Grid.Row="5" Grid.RowSpan="2" VerticalAlignment="stretch" />

Just know that when you're using these properties on a control, that control will seemingly merge the rows and columns that you tell it to span, and the vertical and horizontal alignment will apply to that "merged" column/row.

Also those blue lines don't show up in your actual tool when you launch it:

They're just there to help you visualize them

That's all folks!

And with that comes the end of this post in the series on Creating Graphical User Interfaces. I hope you enjoyed yourself, learned something new, or gained a greater understanding of our topic.
Until next time!

https://www.youtube.com/watch?v=gBzJGckMYO4


This blog has move to: http://hornedowl.net/

Posted On Tuesday, February 19, 2019 12:22 PM | Comments (0) | Filed Under [ Powershell #GUIBuilding #XAML #Rows #Columns #TimeToGetNeat ]

[Powershell Series] Creating Graphical User Interfaces – The basics

This blog has move to: http://hornedowl.net/

So, you've been writing PowerShell Scripts that interact with users and figured
"Hey, I really don't want to listen to the complaints that this it is archaic to use
a menu structure in the console" anymore, or perhaps you just want to learn something new .

This article aims to give you the basics to create a simple Windows Presentation Framework
form (using XAML) and presenting it to your user.

First things first… What is XAML?

XAML stands for eXtensible Application Markup Language, and it's Microsoft's variant of XML
for describing a Graphical User Interface. Previously you would use something like WinForm (shudder),
which would create the GUI in the same language that you would use to interact with the GUI.
WinForms doesn't scale well and is (in my opinion) horrible to work with.

The Editor of choice

There's quite a few editors you can use to create to make XAML windows (notepad being the most basic
one if you're feeling adventurous… ), but I prefer Visual Studio for my work. It helps that it comes with
my MSDN subscription, but there are other fine editors out there if you would choose to use a different
one. If you do decide to use Visual Studio, I highly recommend the PowerShell plug-in:
https://marketplace.visualstudio.com/items?itemName=AdamRDriscoll.PowerShellToolsforVisualStudio2017-18561

The good stuff

Your first window!

Yup, I use a Dark Theme … So what we see here is a number of different elements:

  1. The window
  2. The grid
  3. A label
  4. A textbox
  5. A button
  6. An image

Easy so far right? But how to we create these in XAML? By using the following XAML code:

<Window

 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="MainWindow"

Height="204.683"

Width="525">

 

<!-- Grid area -->

<Grid>

<!-- A Label -->

<Label Content="Label" HorizontalAlignment="Left" Margin="68,38,0,0" VerticalAlignment="Top" Width="197"/>

<!-- A Button -->

<Button Content="Button" HorizontalAlignment="Left" Margin="307,41,0,0" VerticalAlignment="Top" Width="75"/>

<!-- A Textbox -->

<TextBox HorizontalAlignment="Left" Height="23" Margin="127,41,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<!-- An Image -->

<Image HorizontalAlignment="Left" Margin="198,106,0,10" Width="58" Source="C:\Users\Marc\Downloads\Cylon_Head.png" />

</Grid>

</Window>

Great! But what the hell does all of it mean?

<Window> </Window>

This is where it all starts. It allows users to interact with the application, and try to break it. The window tag
consists of a client, and non-client area. The non-client area is implemented by WPF and includes the part of
a window that are common to most windows: A border, title bar, icon, Minimize, Maximize, restore, close
buttons, and of course a system menu

The client area is where app specific content is added. Meaning, that's the area number 2 and above in the
picture from earlier live.

Properties

A full list of all the properties can be found here:
https://docs.microsoft.com/en-us/dotnet/api/system.windows.window?view=netframework-4.7.2

Typically, there's a few you would use to customize the window most commonly:

  • Title: This property will define the main name of your GUI
  • Height: Surprisingly, this defines the height of the GUI
  • Width: Defines the width of the GUI
  • Background: Defines the color of the client area
  • Borderbrush: The color your border should have
  • BorderThickness: How large your border should get
  • FontFamily: Sets the font for the client area
  • FontSize: Sets the size of the fonts in the client area

For example, changing the XAML code from early to this:

<Window

 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="Just a demo"

Height="204.683"

Width="525"

Background="Red"

BorderBrush="Blue"

BorderThickness="4"

FontFamily="Algerian"

FontSize="24"

>

 

<!-- Grid area -->

<Grid>

<!-- A Label -->

<Label Content="Label" HorizontalAlignment="Left" Margin="21,31,0,0" VerticalAlignment="Top" Width="197"/>

<!-- A Button -->

<Button Content="Button" HorizontalAlignment="Left" Margin="307,41,0,0" VerticalAlignment="Top" Width="127"/>

<!-- A Textbox -->

<TextBox HorizontalAlignment="Left" Height="32" Margin="127,41,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<!-- An Textbox -->

<Image HorizontalAlignment="Left" Margin="198,106,0,10" Width="58" Source="C:\Users\Marc\Downloads\Cylon_Head.png"/>

</Grid>

</Window>

Would result in this:

All this building is fun and games, but how do we actually use it?

Actually calling it all from Powershell is quite simple. Generally you'll see 2 different approaches on this. One would be to define the XAML code in your PowerShell script as such:

[XML]$xaml=@"

<Window

 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="Just a demo"

Height="204.683"

Width="525"

Background="Red"

BorderBrush="Blue"

BorderThickness="4"

FontFamily="Algerian"

FontSize="24"

>

 

<!-- Grid area -->

<Grid>

<!-- A Label -->

<Label Content="Label" HorizontalAlignment="Left" Margin="21,31,0,0" VerticalAlignment="Top" Width="197"/>

<!-- A Button -->

<Button Content="Button" HorizontalAlignment="Left" Margin="307,41,0,0" VerticalAlignment="Top" Width="127"/>

<!-- A Textbox -->

<TextBox HorizontalAlignment="Left" Height="32" Margin="127,41,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<!-- An Textbox -->

<Image HorizontalAlignment="Left" Margin="198,106,0,10" Width="58" Source="C:\Users\Marc\Downloads\Cylon_Head.png"/>

</Grid>

</Window>

"@

 

Other like keeping the xaml code in a separate xaml file and import it:

[xml]$xaml = Get-Content -Path "$($VariableHash.FormsDir)\MainWindow.xaml"

 

Personally, that last one is the one I prefer. Full code to show your gui:

# Loading required assemblies    

Add-Type –assemblyName PresentationFramework # Required to show the GUI

 

# Loading XAML code    

[xml]$xaml = Get-Content -Path "<Path to XAML file>"

 

    # Adding proper namespace

    $manager = New-Object System.Xml.XmlNamespaceManager -ArgumentList $xaml.NameTable

$manager.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml");

        

# Loading in to XML Node reader

$Reader= New-Object System.Xml.XmlNodeReader $xaml

        

# Loading XML Node reader in to $SyncHash window property to launch later

$window = [Windows.Markup.XamlReader]::Load($reader)

 

# Show the window!

$window.ShowDialog()

Just one more thing…

Right now, you won't be able to do much with the above code. There are no names defined, nor event actions… So for each element that we want to interact with we should add a Name property:

<Window

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

x:Name="MainWindow"

Title="Just a demo"

Height="204.683"

Width="525"

Background="Red"

BorderBrush="Blue"

BorderThickness="4"

FontFamily="Algerian"

FontSize="24"

>

 

<!-- Grid area -->

<Grid>

<!-- A Label -->

<Label x:Name="lblLabel" Content="Label" HorizontalAlignment="Left" Margin="21,31,0,0" VerticalAlignment="Top" Width="197"/>

<!-- A Button -->

<Button x:Name="btnButton" Content="Button" HorizontalAlignment="Left" Margin="307,41,0,0" VerticalAlignment="Top" Width="127"/>

<!-- A Textbox -->

<TextBox x:Name="txtTextBox" HorizontalAlignment="Left" Height="32" Margin="127,41,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

<!-- An Textbox -->

<Image x:Name="imgImage" HorizontalAlignment="Left" Margin="198,106,0,10" Width="58" Source="C:\Users\Marc\Downloads\Cylon_Head.png"/>

</Grid>

</Window>

Now that you have them named, you can connect the controls to variables (otherwise you cannot use them…):

#region connecting controls

$MainWindow = $Window.FindName("MainWindow")

$label = $Window.FindName("lblLabel")

$TextBox = $Window.FindName("txtTextBox")

$Button = $Window.FindName("btnButton")

$Image = $Window.FindName("imgImage")

#endregion connecting controls

For a button click, use the following:

#region Event Handling

    $Button.Add_Click({

        # Do something

    })

#endregion Event Handling

Which would make the full code:

 

# Loading required assemblies    

Add-Type –assemblyName PresentationFramework # Required to show the GUI

 

# Loading XAML code    

[xml]$xaml = Get-Content -Path "$PSScriptRoot\WpfWindow1.xaml"

 

    # Adding proper namespace

    $manager = New-Object System.Xml.XmlNamespaceManager -ArgumentList $xaml.NameTable

$manager.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml");

        

# Loading in to XML Node reader

$Reader= New-Object System.Xml.XmlNodeReader $xaml

        

# Loading XML Node reader in to $SyncHash window property to launch later

$window = [Windows.Markup.XamlReader]::Load($reader)

 

#region connecting controls

$MainWindow = $Window.FindName("MainWindow")

$label = $Window.FindName("lblLabel")

$TextBox = $Window.FindName("txtTextBox")

$Button = $Window.FindName("btnButton")

$Image = $Window.FindName("imgImage")

#endregion connecting controls

 

#region Event Handling

    $Button.Add_Click({

        # Do something

    })

#endregion Event Handling

 

# Show the window!

$window.ShowDialog()

 

That's all folks!

And with that comes the end of this post in the series on Creating Graphical User Interfaces. I hope you enjoyed yourself, learned something new, or gained a greater understanding of our topic.
Until next time!

https://www.youtube.com/watch?v=gBzJGckMYO4

This blog has move to: http://hornedowl.net/

Posted On Tuesday, February 19, 2019 9:59 AM | Comments (0) | Filed Under [ Powershell #GUIBuilding #XAML ]

Powered by: