Skip to content

Instantly share code, notes, and snippets.

@BananaAcid
Last active September 2, 2022 17:20
Show Gist options
  • Save BananaAcid/d0ea1eea1eb0d3b98f4ebd525df67622 to your computer and use it in GitHub Desktop.
Save BananaAcid/d0ea1eea1eb0d3b98f4ebd525df67622 to your computer and use it in GitHub Desktop.
# (c) Nabil Redmann 2019
# based on https://stackoverflow.com/a/52416973/1644202
function LoadXamlFile
{
Param
(
[Parameter(Mandatory=$true)] $xamlFile
)
$xamlString = Get-Content -Path $xamlFile
$Form,$Elements = LoadXamString($xamlString)
return $Form, $Elements
}
function LoadXamString
{
Param
(
[Parameter(Mandatory=$true)] $xamlString
)
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
If (!$knownEvents) { $knownEvents = @(
# Some major events. There are way more.
# Window
"Initialized", "Loaded", "Unloaded", "Activated", "Closed", "Closing", "GotFocus", "LostFocus", "SizeChanged",
# Checkbox, Buttons etc
"Click", "Checked", "MouseDoubleClick", "MouseEnter", "MouseLeave", "MouseDown", "MouseUp", "MouseLeftButtonDown", "MouseLeftButtonUp", "MouseRightButtonDown", "MouseRightButtonUp", "MouseMove", "MouseWheel",
# Text
"KeyDown", "KeyUp", "PreviewKeyDown", "PreviewKeyUp"
) }
# store window class
$windowClass = [Regex]::Match($xamlString, '^<Window[^>]*x:Class="([^"]*)').Captures.Groups[1].Value
#===========================================================================
# fix XAML markup for powershell
#===========================================================================
$xamlString = $xamlString -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' -replace "x:Bind", "Binding"
[xml]$XAML = $xamlString
#===========================================================================
# storing events
#===========================================================================
$eventElements = @()
Foreach ($event in $knownEvents) {
Foreach ($node in $XAML.SelectNodes("//*[@$event]")) {
If (!$node.Name) { $node.AddAttribute("Name", "el_$(Get-Random)") }
$eventElements += @{
e = $node
ev = $event
fn = $node.$event
name = $node.Name
}
$node.RemoveAttribute($event)
}
}
#===========================================================================
#Read XAML
#===========================================================================
$reader = (New-Object System.Xml.XmlNodeReader $XAML)
try{ $Form = [Windows.Markup.XamlReader]::Load($reader) }
catch [System.Management.Automation.MethodInvocationException] {
Write-Warning "We ran into a problem with the XAML code. Check the syntax for this control..."
write-host $error[0].Exception.Message -ForegroundColor Red
}
catch{#if it broke some other way
Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."
}
#===========================================================================
# attaching click handlers
#===========================================================================
Write-Host "Form class: $windowClass "
Foreach ($evData in $eventElements) {
$fnName = $evData.fn
if ($windowClass) {
$fnName = "$windowClass.$fnName"
}
Write-Host "Linking event $($evData.ev) on element $($evData.name) -> function $fnName()"
$fns = Get-ChildItem function: | Where-Object { $_.Name -like $fnName } # function namespace.windowclassname.function_name($Sender, $EventArgs)
If (!$fns.Count) {
Write-Host ".. handler function does not exist: $fnName(`$Sender,`$EventArgs)" -ForegroundColor Red
}
else {
Invoke-Expression ('$Form.FindName($evData.name).Add_' + $evData.ev + '( $fns[0].ScriptBlock )')
}
}
#===========================================================================
# Store named elements to be acessable through $Elements
#===========================================================================
$Elements = @{}
$xaml.SelectNodes("//*[@Name]") | %{ $Elements[$_.Name] = $Form.FindName($_.Name) }
$Elements["_Window"] = $Form
return $Elements, $Elements._Window
}
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView SelectionMode="Multiple" x:Name="lvApps" Height="371" VerticalAlignment="Top">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64" AutomationProperties.Name="{Binding Name}">
<Ellipse Height="48" Width="48" VerticalAlignment="Center">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding ButtonImage}"/>
</Ellipse.Fill>
</Ellipse>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="12,0,0,0">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="doInstallAll" Content="install all" Width="75" VerticalAlignment="Center" Click="DoInstallAll_Click" />
</Grid>
</Window>
start powershell.exe -ExecutionPolicy Bypass -Command .\start.ps1;Read-Host "Press Enter to continue ..."
# import logic
. .\gui.framework.ps1
# define handler to be auto attached - window.class + ckick-handler --> before loading the GUI !
function WpfApp2.MainWindow.DoInstallAll_Click($Sender, $EventArgs) {
#write-host $Sender,$EventArgs
Write-Host "WORKS !"$Elements.lvApps.Items.Count # $Elements will be available at the time of execution
}
# load main gui
$Elements, $Form = LoadXamlFile .\gui.xaml
# add some element
$Elements.lvApps.AddChild([pscustomobject]@{Name='Ben';Description="sdsd 343"})
# show ui
$Form.ShowDialog() | out-null
# add handler manually (alternative)
$Elements.doInstall.Add_Click({
Write-Host "!"
})
#
# Use `Invoke-All` to start work intensive stuff in a seperate thread to not block the UI
#
. .\z.helper.ps1
$return = Start-Await-Job {
Write-Host "doing prog.."
sleep 3
return "..done"
}
Write-Host $return
# await a job
Add-Type -AssemblyName System.Windows.Forms
Function Await-Job {
Param
(
[Parameter(Mandatory=$true)] $job
)
while ($job.state -eq 'Running') {
[System.Windows.Forms.Application]::DoEvents() # keep form responsive
}
# will wait on job
#$consume = Wait-Job $job
# Captures and throws any exception in the job output -> '-ErrorAction stop' --- otherwise returns result
return Receive-Job $job -ErrorAction Continue
}
# start and await a job
Function Start-Await-Job {
Param
(
[Parameter(Mandatory=$true)] $scriptBlock
)
$job = Start-Job -ScriptBlock $scriptBlock
return Await-Job $job
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment