Danny
Danny

Reputation: 410

Form takes focus

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

Answers (2)

mklement0
mklement0

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.

    • This, in turn, requires you to enter a loop in which you call [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.

    • This, in turn, requires implementing the subclass using ad-hoc-compiled C# code, via 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:

  • Note: Pressing Ctrl-C after launching the script will terminate it and close the form. The fact that this works is proof that the caller's window retained focus.
# 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

Theo
Theo

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
  • A top-level form is a window that has no parent form, or whose parent form is the desktop window. Top-level windows are typically used as the main form in an application.
  • A topmost form is a form that overlaps all the other (non-topmost) forms even if it is not the active or foreground form. Topmost forms are always displayed at the highest point in the z-order of the windows on the desktop. You can use this property to create a form that is always displayed in your application, such as a Find and Replace tool window.
  • An active form means that the form has the focus.

Upvotes: 0

Related Questions