Created
July 2, 2025 17:18
-
-
Save steviecoaster/bbde7a62fb357060586f824121da055c to your computer and use it in GitHub Desktop.
Example NavigationPane app using WinUIShell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace WinUIShell | |
[CmdletBinding()] | |
Param() | |
begin {} | |
process { | |
if (-not (Get-Module WinUIShell)) { | |
Import-Module WinUIShell | |
} | |
$resources = [Application]::Current.Resources | |
$win = [Window]::new() | |
$win.Title = 'Navigation View' | |
$win.SystemBackdrop = [DesktopAcrylicBackdrop]::new() | |
$win.AppWindow.TitleBar.PreferredTheme = 'UseDefaultAppMode' | |
$win.AppWindow.ResizeClient(1000, 520) | |
$frame = [Frame]::new() | |
$navigationView = [NavigationView]::new() | |
$navigationView.Content = $frame | |
$navigationView.PaneTitle = 'Menu' | |
$navigationView.ExpandedModeThresholdWidth = 800 | |
$navigationView.CompactModeThresholdWidth = 400 | |
$convertPageOnLoaded = { | |
param ($pageName, $page, $e) | |
if ($page.Content) { | |
# Cached instance is used so we reuse the content. | |
return | |
} | |
$title = [TextBlock]::new() | |
$title.Text = "This is $pageName" | |
$title.Style = $resources['TitleTextBlockStyle'] | |
$pressToClose = $false | |
$button = [Button]::new() | |
$button.HorizontalAlignment = 'Right' | |
$button.Content = 'Convert' | |
$button.Style = $resources['AccentButtonStyle'] | |
$button.AddClick({ | |
if ($script:pressToClose) { | |
$win.Close() | |
return | |
} | |
<# | |
# Don't let people click the button while a conversion is happening | |
$button.IsEnabled = $false | |
# Do the conversion | |
$convert = @('pkcs12', '-export', '-in', $('{0}' -f $txtCert.Text), '-inkey', $('{0}' -f $txtKey.Text), '-out', "$('{0}' -f $txtPfx.Text)", '-passout', "pass:$(([System.Net.NetworkCredential]::new($null,$($password.Password)).Password))") | |
try { | |
Start-Transcript C:\temp\attempt.log | |
& openssl @convert | |
# Update our status | |
$status.Text = '🎉 Done!' | |
Stop-Transcript | |
} | |
catch { | |
$status.Text = $error[0].Exception | |
Stop-Transcript | |
} | |
#> | |
$status.Text = 'You clicked!' | |
$button.Content = 'Close' | |
$script:pressToClose = $true | |
$button.IsEnabled = $true | |
}) | |
# Add a textbox for our original certificate file | |
$txtCert = [TextBox]::new() | |
$txtCert.Header = 'Certificate File (.cer or .cert)' | |
$txtCert.PlaceHolderText = '.cer or .cert accepted' | |
$txtCert.Margin = [Thickness]::new(0, 0, 0, 6) | |
#And one for the key file | |
$txtKey = [TextBox]::new() | |
$txtKey.Header = 'Key File' | |
$txtKey.PlaceHolderText = '.key file accepted' | |
$txtKey.Margin = [Thickness]::new(0, 0, 0, 6) | |
# Save the pfx file to here | |
$txtPfx = [TextBox]::New() | |
$txtPfx.Header = 'Pfx File' | |
$txtPfx.PlaceholderText = 'Save to...' | |
$txtPfx.Margin = [Thickness]::new(0, 0, 0, 6) | |
# The password field | |
$password = [PasswordBox]::new() | |
$password.Header = 'Export Password' | |
$password.Description = 'This will be the password on the private key for the pfx file.' | |
$password.Margin = [Thickness]::new(0, 0, 0, 6) | |
$status = [TextBlock]::new() | |
$status.Text = '' | |
$status.Margin = [Thickness]::new(0, 0, 0, 2) | |
# A stack panel to display everything in | |
$panel = [StackPanel]::new() | |
$panel.Margin = 16 | |
$panel.Spacing = 6 | |
#$panel.CanVerticallyScroll = $true | |
$panel.Children.Add($title) | |
$panel.Children.Add($txtCert) | |
$panel.Children.Add($txtKey) | |
$panel.Children.Add($txtPfx) | |
$panel.Children.Add($password) | |
$panel.Children.Add($status) | |
$panel.Children.Add($button) | |
$page.Content = $panel | |
} | |
$samplePageOnLoaded = { | |
$pressToClose = $false | |
$button = [Button]::new() | |
$button.HorizontalAlignment = 'Right' | |
$button.Content = 'Convert' | |
$button.Style = $resources['AccentButtonStyle'] | |
$button.AddClick({ | |
if ($script:pressToClose) { | |
$win.Close() | |
return | |
} | |
$status.Text = 'You clicked!' | |
$button.Content = 'Close' | |
$script:pressToClose = $true | |
$button.IsEnabled = $true | |
}) | |
$status = [TextBlock]::new() | |
$status.Text = 'This should change' | |
$status.Margin = [Thickness]::new(0, 0, 0, 2) | |
# A stack panel to display everything in | |
$panel = [StackPanel]::new() | |
$panel.Children.Add($status) | |
$panel.Children.Add($button) | |
$page.Content = $panel | |
} | |
$contentPageOnLoaded = { | |
param ($pageName, $page, $e) | |
if ($page.Content) { | |
# Cached instance is used so we reuse the content. | |
return | |
} | |
$title = [TextBlock]::new() | |
$title.Text = "This is $pageName" | |
$title.Style = $resources['TitleTextBlockStyle'] | |
$text = [TextBlock]::new() | |
$text.Text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac mi ipsum. Phasellus vel malesuada mauris. Donec pharetra, enim sit amet mattis tincidunt, felis nisi semper lectus, vel porta diam nisi in augue. Pellentesque lacus tortor, aliquam et faucibus id, rhoncus ut justo. Sed id lectus odio, eget pulvinar diam. Suspendisse eleifend ornare libero, in luctus purus aliquet non. Sed interdum, sem vitae rutrum rhoncus, felis ligula ultrices sem, in eleifend eros ante id neque.' | |
$text.Style = $resources['BodyTextBlockStyle'] | |
$button = [Button]::new() | |
$button.HorizontalAlignment = 'Right' | |
$button.Content = 'Click Me' | |
$button.Style = $resources['AccentButtonStyle'] | |
# Add a textbox for our original certificate file | |
$txtCert = [TextBox]::new() | |
$txtCert.Header = 'Certificate File (.cer or .cert)' | |
$txtCert.PlaceHolderText = '.cer or .cert accepted' | |
$txtCert.Margin = [Thickness]::new(0, 0, 0, 24) | |
# | |
$panel = [StackPanel]::new() | |
$panel.Margin = 32 | |
$panel.Spacing = 16 | |
$panel.Children.Add($title) | |
#$panel.Children.Add($text) | |
$panel.Children.Add($txtCert) | |
$panel.Children.Add($txtKey) | |
$panel.Children.Add($button) | |
$page.Content = $panel | |
} | |
$settingsPageOnLoaded = { | |
param ($pageName, $page, $e) | |
if ($page.Content) { | |
return | |
} | |
$toggle1 = [ToggleSwitch]::new() | |
$toggle1.IsOn = $true | |
$toggle1.Header = 'Badge Notification' | |
$toggle2 = [ToggleSwitch]::new() | |
$toggle2.Header = 'Banner Notification' | |
$toggle3 = [ToggleSwitch]::new() | |
$toggle3.Header = 'Brightness Control' | |
$panel = [StackPanel]::new() | |
$panel.Margin = 32 | |
$panel.Spacing = 16 | |
$panel.Children.Add($toggle1) | |
$panel.Children.Add($toggle2) | |
$panel.Children.Add($toggle3) | |
$page.Content = $panel | |
} | |
function Navigate($pageName) { | |
if ($frame.SourcePageName -eq $pageName) { | |
return | |
} | |
# $onLoaded = if ($pageName -eq 'Settings') { $settingsPageOnLoaded } else { $contentPageOnLoaded } | |
$onLoaded = switch ($pageName) { | |
'Settings' { $settingsPageOnLoaded } | |
'Convert' { $convertPageOnLoaded } | |
'Sample' { $samplePageOnLoaded} | |
} | |
$onLoadedArgumentList = $pageName | |
# Page instance is created only once per page name. Cached pages are used from the second navigation. | |
$cacheMode = [NavigationCacheMode]::Enabled | |
# You can change the transition animation by setting [DrillInNavigationTransitionInfo]::new() etc. | |
$transition = $e.RecommendedNavigationTransitionInfo | |
# Page instance is created internally and onLoaded script block is called. | |
$frame.Navigate($pageName, $transition, $cacheMode, $onLoaded, $onLoadedArgumentList) | Out-Null | |
} | |
# Called when NavigationViewItem is clicked. | |
$navigationView.AddItemInvoked({ | |
param($argumentList, $s, $e) | |
if ($e.IsSettingsInvoked) { | |
$pageName = 'Settings' | |
} | |
else { | |
$pageName = $e.InvokedItemContainer.Tag | |
} | |
Navigate $pageName | |
}) | |
# Called when Back button is clicked. | |
$navigationView.AddBackRequested({ | |
if (-not ($frame.CanGoBack)) { | |
return | |
} | |
$frame.GoBack() | |
}) | |
$frame.AddNavigated({ | |
param($argumentList, $s, $e) | |
# Property accesses are slow as they require communication with the server. | |
# Use temporary variables to keep property access as few as possible. | |
$pageName = $frame.SourcePageName | |
# Navigation also happens when back button is pressed. | |
# That's why we set these properties here instead of in the ItemInvoked handler. | |
$navigationView.IsBackEnabled = $frame.CanGoBack | |
$navigationView.Header = $pageName | |
if ($pageName -eq 'Settings') { | |
$menuItem = $navigationView.SettingsItem | |
} | |
else { | |
$menuItem = $menuItemMap[$pageName] | |
} | |
$navigationView.SelectedItem = $menuItem | |
}) | |
$separator = [NavigationViewItemSeparator]::new() | |
$navigationView.MenuItems.Add($separator) | |
$menuItemMap = @{} | |
$item1 = [NavigationViewItem]::new() | |
$item1.Content = 'Home' | |
$item1.Icon = [SymbolIcon]::new('Home') | |
$item1.Tag = 'Home' | |
$menuItemMap[$item1.Tag] = $item1 | |
$navigationView.MenuItems.Add($item1) | |
$item2 = [NavigationViewItem]::new() | |
$item2.Content = 'Favorite' | |
$item2.Icon = [SymbolIcon]::new('Favorite') | |
$item2.Tag = 'Favorite' | |
$menuItemMap[$item2.Tag] = $item2 | |
$navigationView.MenuItems.Add($item2) | |
$header = [NavigationViewItemHeader]::new() | |
$header.Content = 'Utilities' | |
$navigationView.MenuItems.Add($header) | |
$item3 = [NavigationViewItem]::new() | |
$item3.Content = 'Convert w/ OpenSSL' | |
$item3.Icon = [SymbolIcon]::new('XboxOneConsole') | |
$item3.Tag = 'Convert' | |
$menuItemMap[$item3.Tag] = $item3 | |
$navigationView.MenuItems.Add($item3) | |
$item4 = [NavigationViewItem]::new() | |
$item4.Content = 'Sample' | |
$item4.Icon = [SymbolIcon]::new('XboxOneConsole') | |
$item4.Tag = 'Sample' | |
$menuItemMap[$item4.Tag] = $item4 | |
$navigationView.MenuItems.Add($item4) | |
$win.Content = $navigationView | |
$win.Activate() | |
Navigate 'Home' | |
$win.WaitForClosed() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment