Auto close $form.ShowDialog() on selection of default selected text - winforms

I have a custom form and I need to auto close the window on selection of selected part of the text displayed on the that form window
Show-OAuthWindow shows the dialogue box for user consent and I want to avoid using showdialog() and select the auto-selected text on the window
Function Show-OAuthWindow {
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form -Property #{Width=600;Height=800}
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property #{Width=580;Height=780;Url=($url -f ($Scope -join "%20")) }
$DocComp = {
$Global:uri = $web.Url.AbsoluteUri
if ($Global:Uri -match "error=[^&]*|code=[^&]*") {$form.Close() }
}
$web.ScriptErrorsSuppressed = $true
$web.Add_DocumentCompleted($DocComp)
$form.Controls.Add($web)
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() | Out-Null
}

Related

Powershell Forms - How to add context menu event handler to prevent open unless item selected?

I have a form with a listview for which I have a right click context menu. I'd like the menu to only appear if the right clikc is above the selected item in the listview - at the moment it appears anywhere in the listview windows and even if nothing is selected.
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Text = "Foo"
$form.Size = '400,400'
$CMitemEnable = New-Object System.Windows.Forms.ToolStripMenuItem
$CMitemEnable.Text = 'Enable'
$CMitemDisable = New-Object System.Windows.Forms.ToolStripMenuItem
$CMitemDisable.Text = 'Disable'
$lvcontextmenu = New-Object System.Windows.Forms.ContextMenuStrip
$lvcontextmenu.ShowImageMargin = $false
$lvcontextmenu.Items.AddRange(#($CMitemEnable,$CMitemDisable))
$listviewbox = New-Object System.Windows.Forms.ListView
$listviewbox.View = [System.Windows.Forms.View]::Details
$listviewbox.Location = '15,20'
$listviewbox.Size = '340,150'
$listviewbox.Columns.Add('Host',180) | Out-Null
$listviewbox.FullRowSelect = $true
$listviewbox.MultiSelect = $false
# $listviewbox.ContextMenuStrip = $lvcontextmenu
$InfoText = New-Object System.Windows.Forms.TextBox
$InfoText.Location = '15,180'
$InfoText.Size = '340,120'
$InfoText.Multiline = $true
$InfoText.ScrollBars = "Vertical"
$InfoText.ReadOnly = $true
$form.Controls.AddRange(#($listviewbox,$InfoText))
$listviewbox.Items.AddRange(#('Foobar1','foobar2'))
$listviewbox.Add_MouseDown({
$listviewbox.contextmenustrip = $null
If($listviewbox.SelectedItems.Count){
$listviewbox.contextmenustrip = $lvcontextmenu
$InfoText.AppendText("`r`n" + ($this.SelectedItems[0].Text))
}
Else {
$InfoText.AppendText("`r`nNothing selected")
$listviewbox.contextmenustrip = $null
}
})
# Show form
$form.ShowDialog() | Out-Null
$form.Dispose()
I've not done context menus before and can't see for the life of me how to implement the event handler for this - there's examples for C etc but can someone point me in the right direction for Powershell please?
Considering the following points:
Don't set ContextMenuStrip for the ListView
Handle MouseClick event, so first the item will be selected, then your code run. (As an alternative, if you prefer mouse down, then you need to need to put your logic in BeginInvoke to make sure it will run after selection of item.)
Using GetBound method of the selected item and e.Location, check if the clicked location is inside the item rectangle
Show the ContextMenuStrip using Show by passing Cursor.Position
Here is a working example:
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Text ="Test"
$form.Controls.AddRange(#(
($listView1 = [System.Windows.Forms.ListView] #{
Dock = [System.Windows.Forms.DockStyle]::Fill;
FullRowSelect = $true;
View = [System.Windows.Forms.View]::Details;
})
))
$contextMenuStrip1 = [System.Windows.Forms.ContextMenuStrip]#{}
$listView1.Columns.Add("C1") | Out-Null
$listView1.Columns.Add("C2") | Out-Null
$listView1.Items.Add("Item1") | Out-Null
$listView1.Items.Add("Item2") | Out-Null
$contextMenuStrip1.Items.Add("Menu 1") | Out-Null
$contextMenuStrip1.Items.Add("Menu 2") | Out-Null
$listView1.Add_MouseClick({param($sender,$e)
if ($e.Button -eq [System.Windows.Forms.MouseButtons]::Right){
if ($listView1.FocusedItem.GetBounds(
[System.Windows.Forms.ItemBoundsPortion]::Entire).Contains($e.Location)){
$contextMenuStrip1.Show([System.Windows.Forms.Cursor]::Position)
}
}
})
$form.ShowDialog() | Out-Null
$form.Dispose()
$contextMenuStrip1.Dispose()
Maybe there is a cleaner way, but this works for me:
First, remove line 22 ($listviewbox.ContextMenuStrip = $lvcontextmenu)
and then change the Click-handler for the $listviewbox into:
$listviewbox.Add_Click({
If($this.SelectedItems.Count){
$this.ContextMenuStrip = $lvcontextmenu
$InfoText.AppendText("`r`n" + ($this.SelectedItems[0].Text))
}
Else {
$this.ContextMenuStrip = $null
$InfoText.AppendText("`r`nNothing selected")
}
})
The following event handler works as intended - a combination of hints from both of you, thanks.
$listviewbox.Add_MouseUp({
$listviewbox.contextmenustrip = $null
If($listviewbox.SelectedItems.Count){
$listviewbox.contextmenustrip = $lvcontextmenu
$InfoText.AppendText("`r`n" + ($this.SelectedItems[0].Text))
}
Else {
$InfoText.AppendText("`r`nNothing selected")
$listviewbox.contextmenustrip = $null
}
})

change tray icon based on event

I have code that will start process in tray (task bar). After right click on tray icon it will show menu. After clicking on first menu item winform window starts. This winform shows status about the notepad process. My goal is to change the tray icon based on the status of notepad (if notepad is running then show online.ico otherwise show offline.ico). If I understand correct then my code is starting/stopping System.Windows.Forms.Timer every time winform window is opened/closed and I'm not sure if this is the best possible approach. My guess is that I need to somehow start timer "outside" of OnMenuItem1ClickEventFn so it can somehow reload *.ico files. Following script is heavily inspired by this site:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Test-Notepad {
[bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}
function OnMenuItem1ClickEventFn () {
# Build Label object
$Label = New-Object System.Windows.Forms.Label
$Label.Name = "labelName"
$Label.AutoSize = $True
# Set and start timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
if ($Label){
$Label.Text = if (Test-Notepad) { "Notepad is running" } else { "Notepad is NOT running" }
}
})
$timer.Start()
# Build Form object
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
$Form.Add_Closing({ $timer.Dispose() }) # Dispose() also stops the timer.
$Form.Controls.Add($Label) # Add label to form
$form.ShowDialog()| Out-Null # Show the Form
}
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
$window.Close()
Stop-Process $pid
}
function create_taskbar_menu{
# Create menu items
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = $icon
$Main_Tool_Icon.Visible = $true
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Menu Item 2"
$MenuItem3 = New-Object System.Windows.Forms.MenuItem
$MenuItem3.Text = "Menu Item 3"
$MenuItem4 = New-Object System.Windows.Forms.MenuItem
$MenuItem4.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)
$MenuItem4.add_Click({OnMenuItem4ClickEventFn})
$MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}
$Current_Folder = split-path $MyInvocation.MyCommand.Path
# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll") | out-null
# Choose an icon to display in the systray
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
# $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
create_taskbar_menu
# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://learn.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()
# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
[void][System.Windows.Forms.Application]::Run($appContext)
EDITED: working solution based on #BACON answer
# Toggle following two lines
Set-StrictMode -Version Latest
# Set-StrictMode -Off
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Test-Notepad {
[bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}
function OnMenuItem1ClickEventFn () {
# Build Form object
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
$Form.Controls.Add($Label) # Add label to form
$form.ShowDialog()| Out-Null # Show the Form
}
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
[System.Windows.Forms.Application]::Exit()
}
function create_taskbar_menu{
# Create menu items
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Menu Item 2"
$MenuItem3 = New-Object System.Windows.Forms.MenuItem
$MenuItem3.Text = "Menu Item 3"
$MenuItem4 = New-Object System.Windows.Forms.MenuItem
$MenuItem4.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)
$MenuItem4.add_Click({OnMenuItem4ClickEventFn})
$MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}
$Current_Folder = split-path $MyInvocation.MyCommand.Path
# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll") | out-null
# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true
# Build Label object
$Label = New-Object System.Windows.Forms.Label
$Label.Name = "labelName"
$Label.AutoSize = $True
# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
if ($Label){
$Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
"Notepad is running", $onlineIcon
} else {
"Notepad is NOT running", $offlineIcon
}
}
})
$timer.Start()
create_taskbar_menu
# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://learn.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()
# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
[System.Windows.Forms.Application]::Run($appContext)
}
finally
{
foreach ($component in $timer, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
{
# The following test returns $false if $component is
# $null, which is really what we're concerned about
if ($component -is [System.IDisposable])
{
$component.Dispose()
}
}
Stop-Process -Id $PID
}
You already have code to set the icon used by the NotifyIcon...
$Main_Tool_Icon.Icon = $icon
...and to define the icons to use...
# Choose an icon to display in the systray
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
# $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
...and to periodically test if Notepad is running and respond appropriately...
$timer.Add_Tick({
if ($Label){
$Label.Text = if (Test-Notepad) { "Notepad is running" } else { "Notepad is NOT running" }
}
})
You just need to combine them with a couple additional tweaks...
Store each icon in its own variable with a descriptive name so its easy to switch between them.
$Main_Tool_Icon needs to be defined outside the scope of create_taskbar_menu so it can be accessed inside of OnMenuItem1ClickEventFn. I moved the creation and initialization to just before the call to create_taskbar_menu, but you could also initialize it inside of create_taskbar_menu or some other function.
That ends up looking like this...
# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true
create_taskbar_menu
...and this...
$timer.Add_Tick({
if ($Label){
# Change the text and icon with one test
$Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
"Notepad is running", $onlineIcon
} else {
"Notepad is NOT running", $offlineIcon
}
}
})
You'll see that an icon is selected based on the result of Test-Notepad both when $Main_Tool_Icon is initialized and when the Tick event is raised.
As for disposing $timer when $Form is closing...
$Form.Add_Closing({ $timer.Dispose() })
...that is almost an appropriate place to do that, however...
The Closing event is raised essentially when the form has been requested to close; it provides an opportunity for that request to be canceled. The Closed event would be more appropriate because it is raised when the form has actually been closed.
The documentation states that both the Closing and Closed events are obsolete. Use the FormClosed event instead.
Also, in OnMenuItem4ClickEventFn you are calling $window.Close() even though $window is not defined; I think you meant $Form.Close(). An alternative that I think is a bit cleaner would be to have clicking the Exit menu item merely exit the Windows Forms application loop...
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
[System.Windows.Forms.Application]::Exit()
}
...and then you can put your cleanup/teardown code in a finally block at the end of the script...
try
{
# This call returns when [System.Windows.Forms.Application]::Exit() is called
[System.Windows.Forms.Application]::Run($appContext)
}
finally
{
# $timer would also have to be defined at the script scope
# (outside of OnMenuItem1ClickEventFn) for this to work
$timer.Dispose()
# $Form, $Label, $Main_Tool_Icon, $onlineIcon, etc. would all be candidates for disposal...
# Exit the entire PowerShell process
Stop-Process $pid
}
The following is complete code that incorporates the changes mentioned above and works for me...
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Test-Notepad {
[bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}
function OnMenuItem1ClickEventFn () {
$timer.Start()
# Build Form object
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
$Form.Add_Closing({ $timer.Dispose() }) # Dispose() also stops the timer.
$Form.Controls.Add($Label) # Add label to form
$form.ShowDialog()| Out-Null # Show the Form
}
function OnMenuItem4ClickEventFn () {
$Main_Tool_Icon.Visible = $false
[System.Windows.Forms.Application]::Exit()
}
function create_taskbar_menu{
# Create menu items
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Menu Item 2"
$MenuItem3 = New-Object System.Windows.Forms.MenuItem
$MenuItem3.Text = "Menu Item 3"
$MenuItem4 = New-Object System.Windows.Forms.MenuItem
$MenuItem4.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem3)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem4)
$MenuItem4.add_Click({OnMenuItem4ClickEventFn})
$MenuItem1.add_Click({OnMenuItem1ClickEventFn})
}
$Current_Folder = split-path $MyInvocation.MyCommand.Path
# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# [System.Reflection.Assembly]::LoadFrom("Current_Folder\assembly\MahApps.Metro.dll") | out-null
# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/online.ico")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("$Current_Folder/icons/offline.ico")
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true
# Build Label object
$Label = New-Object System.Windows.Forms.Label
$Label.Name = "labelName"
$Label.AutoSize = $True
# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
if ($Label){
$Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
"Notepad is running", $onlineIcon
} else {
"Notepad is NOT running", $offlineIcon
}
}
})
create_taskbar_menu
# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://learn.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()
# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
[System.Windows.Forms.Application]::Run($appContext)
}
finally
{
foreach ($component in $timer, $form, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
{
# The following test returns $false if $component is
# $null, which is really what we're concerned about
if ($component -is [System.IDisposable])
{
$component.Dispose()
}
}
Stop-Process -Id $PID
}

FolderBrowserDialog bring to front

I have the following PowerShell function which works well, but the window opens up in the background behind the PowerShell ISE.
# Shows folder browser dialog box and sets to variable
function Get-FolderName() {
Add-Type -AssemblyName System.Windows.Forms
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property #{
SelectedPath = 'C:\Temp\'
ShowNewFolderButton = $false
Description = "Select Staging Folder."
}
# If cancel is clicked the script will exit
if ($FolderBrowser.ShowDialog() -eq "Cancel") {break}
$FolderBrowser.SelectedPath
} #end function Get-FolderName
I can see there's a .TopMost property that can be used with the OpenFileDialog class but this doesn't appear to transfer over to the FolderBrowserDialog class.
Am I missing something?
Hope this helps
Add-Type -AssemblyName System.Windows.Forms
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog
$FolderBrowser.Description = 'Select the folder containing the data'
$result = $FolderBrowser.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true }))
if ($result -eq [Windows.Forms.DialogResult]::OK){
$FolderBrowser.SelectedPath
} else {
exit
}
//Edit to comment
There are 2 variants (overloads) of the ShowDialog () method.
See documentation: http://msdn.microsoft.com/en-us/library/system.windows.forms.openfiledialog.showdialog%28v=vs.110%29.aspx
In the second variant, you can specify the window that should be the mother of the dialogue.
Topmost should be used sparingly or not at all! If multiple windows are topmost then which is topmost? ;-))
First try to set your window as a mother then the OpenfileDialog / SaveFileDialog should always appear above your window:
$openFileDialog1.ShowDialog($form1)
If that's not enough, take Topmost.
Your dialogue window inherits the properties from the mother. If your mother window is topmost, then the dialog is also topmost.
Here is an example that sets the dialogue Topmost.
In this example, however, a new unbound window is used, so the dialog is unbound.
$openFileDialog1.ShowDialog((New - Object System.Windows.Forms.Form - Property #{TopMost = $true; TopLevel = $true}))
A reliable way of doing this is to add a piece of C# code to the function.
With that code, you can get a Windows handle that implements the IWin32Window interface. Using that handle in the ShowDialog function will ensure the dialog is displayed on top.
Function Get-FolderName {
# To ensure the dialog window shows in the foreground, you need to get a Window Handle from the owner process.
# This handle must implement System.Windows.Forms.IWin32Window
# Create a wrapper class that implements IWin32Window.
# The IWin32Window interface contains only a single property that must be implemented to expose the underlying handle.
$code = #"
using System;
using System.Windows.Forms;
public class Win32Window : IWin32Window
{
public Win32Window(IntPtr handle)
{
Handle = handle;
}
public IntPtr Handle { get; private set; }
}
"#
if (-not ([System.Management.Automation.PSTypeName]'Win32Window').Type) {
Add-Type -TypeDefinition $code -ReferencedAssemblies System.Windows.Forms.dll -Language CSharp
}
# Get the window handle from the current process
# $owner = New-Object Win32Window -ArgumentList ([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# Or write like this:
$owner = [Win32Window]::new([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# Or use the the window handle from the desktop
# $owner = New-Object Win32Window -ArgumentList (Get-Process -Name explorer).MainWindowHandle
# Or write like this:
# $owner = [Win32Window]::new((Get-Process -Name explorer).MainWindowHandle)
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property #{
SelectedPath = 'C:\Temp\'
ShowNewFolderButton = $false
Description = "Select Staging Folder."
}
# set the return value only if a selection was made
$result = $null
If ($FolderBrowser.ShowDialog($owner) -eq "OK") {
$result = $FolderBrowser.SelectedPath
}
# clear the dialog from memory
$FolderBrowser.Dispose()
return $result
}
Get-FolderName
You can also opt for using the Shell.Application object with something like this:
# Show an Open Folder Dialog and return the directory selected by the user.
function Get-FolderName {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Select a directory.",
[string]$InitialDirectory = [System.Environment+SpecialFolder]::MyComputer,
[switch]$ShowNewFolderButton
)
$browserForFolderOptions = 0x00000041 # BIF_RETURNONLYFSDIRS -bor BIF_NEWDIALOGSTYLE
if (!$ShowNewFolderButton) { $browserForFolderOptions += 0x00000200 } # BIF_NONEWFOLDERBUTTON
$browser = New-Object -ComObject Shell.Application
# To make the dialog topmost, you need to supply the Window handle of the current process
[intPtr]$handle = [System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle
# see: https://msdn.microsoft.com/en-us/library/windows/desktop/bb773205(v=vs.85).aspx
$folder = $browser.BrowseForFolder($handle, $Message, $browserForFolderOptions, $InitialDirectory)
$result = $null
if ($folder) {
$result = $folder.Self.Path
}
# Release and remove the used Com object from memory
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($browser) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
return $result
}
$folder = Get-FolderName
if ($folder) { Write-Host "You selected the directory: $folder" }
else { "You did not select a directory." }
I just found an easy way to get PowerShell's IWin32Window value so forms can be modal. Create a System.Windows.Forms.NativeWindow object and assign PowerShell's handle to it.
function Show-FolderBrowser
{
Param ( [Parameter(Mandatory=1)][string]$Title,
[Parameter(Mandatory=0)][string]$DefaultPath = $(Split-Path $psISE.CurrentFile.FullPath),
[Parameter(Mandatory=0)][switch]$ShowNewFolderButton)
$DefaultPath = UNCPath2Mapped -path $DefaultPath;
$FolderBrowser = new-object System.Windows.Forms.folderbrowserdialog;
$FolderBrowser.Description = $Title;
$FolderBrowser.ShowNewFolderButton = $ShowNewFolderButton;
$FolderBrowser.SelectedPath = $DefaultPath;
$out = $null;
$caller = [System.Windows.Forms.NativeWindow]::new()
$caller.AssignHandle([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
if (($FolderBrowser.ShowDialog($caller)) -eq [System.Windows.Forms.DialogResult]::OK.value__)
{
$out = $FolderBrowser.SelectedPath;
}
#Cleanup Disposabe Objects
Get-Variable -ErrorAction SilentlyContinue -Scope 0 | Where-Object {($_.Value -is [System.IDisposable]) -and ($_.Name -notmatch "PS\s*")} | ForEach-Object {$_.Value.Dispose(); $_ | Clear-Variable -ErrorAction SilentlyContinue -PassThru | Remove-Variable -ErrorAction SilentlyContinue -Force;}
return $out;
}

Create a save button in winforms

Goal:
I am attempting to create a button that would save a file to desktop. The incoming file is fetched with an Invoke-WebRequest using the GET method. I want the save button to be in my pop-up window.
Here is an example:
Side note:
This code is sitting in a switch with a variable split three ways.
switch (...) {
p {
if ($second -match 'RegexMatch') {
$resource = $second
$fileResult = Invoke-WebRequest -Uri https://url.com/$resource/file -WebSession $currentsession
# End API Call
Write-Host
Write-Host '------------' -ForegroundColor Green
Write-Host 'FILE Results' -ForegroundColor Green
Write-Host '------------' -ForegroundColor Green
# Create Window
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form
$form.StartPosition = 'CenterScreen'
$form.KeyPreview = $true
$form.Add_KeyDown {
if ($_.Control -and $_.KeyCode -eq 'F') {
Add-Type -AssemblyName Microsoft.VisualBasic
$stringToFind = [Microsoft.VisualBasic.Interaction]::InputBox('Please enter your search terms', 'Find')
$pos = $textBox.Text.IndexOf($stringToFind)
if ($pos -ne -1) {
$textBox.SelectionStart = $pos
$textBox.SelectionLength = $stringToFind.Length
}
}
}
# Textbox
$textBox = New-Object -TypeName System.Windows.Forms.TextBox
$textBox.Dock = [Windows.Forms.DockStyle]::Fill
$textBox.ReadOnly =$true
$textBox.Multiline = $true
$textBox.ScrollBars = 'Vertical'
$textBox.Font = New-Object -TypeName System.Drawing.Font -ArgumentList ('Arial',12)
$textBox.ForeColor = 'White'
$textBox.Text = $fileResult
$textBox.BackColor = 'Black'
$textBox.ShortcutsEnabled = $true
$Form.Controls.Add($textBox)
# Button
$btn = New-Object -TypeName System.Windows.Forms.Button
$btn.Text = 'Finish'
$btn.DialogResult = 'Ok'
$btn.Dock = 'bottom'
$form.Controls.Add($btn)
if ($form.ShowDialog() -eq 'Ok') {
$tb.lines
}
} else {
Write-Host
Write-Warning -Message 'Please enter a valid FILE ID'
Write-Host
}
break
}
...
}
Purpose:
I want to add an option for the user to download the file for a closer look in a different application.
Question:
How would I begin to create a button utilizing winforms in powershell to save this file to disk?
Here is what I have tried:
$BtnSave=New-Object -TypeName System.Windows.Forms.Button
$BtnSave.Text='Save'
$BtnSave.Dock='bottom'
$btnSave.DialogResult='Ok'
$form.Controls.Add($BtnSave)
$BtnSave.Add_Click({
$SaveFileDialog = New-Object 'System.Windows.Forms.SaveFileDialog'
if ($SaveFileDialog.ShowDialog() -eq 'Ok')
{
$textBox.Text = $SaveFileDialog.FileName
Write-Information 'File Saved'
}
})
New Problem:
File is not saving to disk still, but the save file dialog does show up on click. In addition, using Switch -OutFile with my Invoke-WebRequest is shooting me an error.
Error:
Invoke-WebRequest : Missing an argument for parameter 'OutFile'. Specify a parameter of type 'System.String' and try again.
After adding a button where you want in the form, the Add_Click() method will allow you to handle its click event and run any scriptblock you want (when button is clicked).
At this point, the -OutFile argument for Invoke-WebRequest will help with saving the downloaded file to disk (pass it the desired path to the file).

How do I access the source of a GUI event in PowerShell?

I making a menu of checkable items in a PowerShell script, like this:
"Red", "Green", "Blue" | %{
$mi = new-object System.Windows.Forms.ToolStripMenuItem($_)
$mi.CheckOnClick = $true
$mi.add_CheckedChanged({
$name = # label of the menu item that was checked
doStuff $name
})
...
}
How do I access the menu item that was checked from the CheckedChanged event handler?
Since the label of a ToolStripMenuItem is stored in its Text property and since in a PowerShell event handler $this is bound to the event sender, the label of the menu item is available in the event handler as:
$this.Text
In PowerShell 2.0, you would use the $Sender automatic variable within the Action scriptblock passed into an event registration command such as Register-ObjectEvent e.g.:
PS> Add-Type -AssemblyName System.Windows.Forms
PS> $form = new-object system.windows.forms.form
PS> $button = new-object system.windows.forms.button -prop #{Text = "Click me"}
PS> $job = Register-ObjectEvent $button Click -Action `
{"Button with text: $($Sender.Text) clicked."}
PS> $form.Controls.Add($button)
PS> $form.ShowDialog()
Cancel
PS> Receive-Job $job
Button with text: Click me clicked.

Resources