Reputation: 410
Add-Type -AssemblyName System.Windows.Forms
$Form = [System.Windows.Forms.Form]::new()
$Form.TopMost = $true
$Form.ShowDialog()
If I run this code from powershell.exe, the form does not take focus. But if I run this code from ISE, focus shifts to the form. Why this happens, and how to fix it? I want the form not to take focus away, as the powershell.exe does.
UPD
May be this page can help in this situation...
Upvotes: 2
Views: 366
Reputation: 437953
Using the .ShowDialog()
method invokes the form modally, which means that execution of your PowerShell script is blocked (unresponsive) until the form is closed.
Therefore, you must:
Use the .Show()
method to show the form non-modally, which ensures that your PowerShell script continues executing.
[System.Windows.Forms.Application]::DoEvents()
periodically so as to ensure that the form stays responsive.To also ensure that the form doesn't receive the focus when .Show()
is called, you must subclass the Forms
class so as to override the ShowWithoutActivation
property, as you have discovered.
Add-Type
.Caveat: If you additionally want to set .TopMost = $true
for the form, in order to show the form always on top of other windows, a workaround is required for reliable operation in various host environments - see bottom section.
To put it all together:
# Derive a custom form class from System.Windows.Forms.Form
# that doesn't activate itself when loaded.
Add-Type -ReferencedAssemblies System.Windows.Forms, System.ComponentModel.Primitives @'
public class MyForm: System.Windows.Forms.Form {
protected override bool ShowWithoutActivation { get { return true; } }
}
'@ -WarningAction Ignore
# Create an instance of the custom form class.
$form = [MyForm]::new()
# Show the form *non-modally*, with .Show() rather than
# .ShowDialog(), which is the prerequisite for not blocking this script.
$form.Show()
# Perform operations while the form is being shown.
try {
do {
# Process form events.
[System.Windows.Forms.Application]::DoEvents()
# Perform operations while the form is being displayed.
Start-Sleep -Milliseconds 200
Write-Host . -NoNewline
} while ($form.Visible)
} finally {
# Make sure that the form gets closed and disposed of.
$form.Dispose()
}
For the inverse use case, i.e. if you want to ensure that the form does receive the focus - which by default doesn't happen consistently - use the following:
Before calling $Form.ShowDialog()
, add a handler for the Load
event that ensures that the form receives the focus once loaded:
Add-Type -AssemblyName System.Windows.Forms
$form = [System.Windows.Forms.Form]::new()
# Ensure that the form receives the focus on loading.
# (Situationally, especially when run shortly after session startup,
# the form may otherwise end up without the focus.)
$form.add_Load({
$this.Activate()
})
$form.ShowDialog()
Workaround for making the form topmost:
For reasons unknown to me, setting a form's .TopMost
property to $true
can malfunction quietly, intermittently in the (obsolescent) ISE, on the first call in a session in Visual Studio Code (the ISE successor) and also in Windows Terminal.
The following should work around these problems. Note that the window may very briefly activate before the caller window is reactivated, but that shouldn't be noticeable in practice:
Add-Type -AssemblyName System.Windows.Forms
# Create a helper type for activating a window by hWnd (window handle)
Add-Type -Namespace Util -Name WinApi -MemberDefinition @'
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
'@
# Create an instance of the custom form class.
$form = [System.Windows.Forms.Form]::new()
# Get the caller's main window handle, to use it for reactivation later.
$thisHWnd = (Get-Process -Id $pid).MainWindowHandle
# Show the form *non-modally*, with .Show() rather than
# .ShowDialog(), which is the prerequisite for not blocking this script.
# Note: This *typically activates* the form (gives it the focus), though not consistently.
$form.Show()
# Perform operations while the form is being shown.
try {
# Set the workaround flags.
$makeTopMost = $true; $reactivateMe = $true
do {
# Process form events.
[System.Windows.Forms.Application]::DoEvents()
# Apply workarounds and reset the flags.
if ($reactivateMe) { $null =[Util.WinApi]::SetForegroundWindow($thisHWnd); $reactivateMe = $false }
if ($makeTopMost) { $form.TopMost = $true; $makeTopMost = $false }
# Perform operations while the form is being displayed.
Start-Sleep -Milliseconds 200
Write-Host ([Util.WinApi]::GetForegroundWindow() -eq $thisHWnd) -NoNewline
} while ($form.Visible)
} finally {
# Make sure that the form gets closed and disposed of.
$form.Dispose()
}
Upvotes: 3
Reputation: 61068
Not sure what you mean with "form does not take focus", but I'm guessing you want it to become the top-level window.
In that case, in addition to $Form.TopMost = $true
, also set the TopLevel property:
$form.TopLevel = $true
Upvotes: 0