Home > Software design >  Form takes focus
Form takes focus

Time:10-25

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...

CodePudding user response:

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.

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()

CodePudding user response:

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.
  • Related