2

Was wondering if there is a way to check what event closed the window, pretty much either clicking the red x in the top corner or if $form.Close() was called?

Each will automatically initiate the $form.Add_Closing({}) if I have it in my script, but I wanted to know which way of closing the window did this.

Badro Niaimi
  • 959
  • 1
  • 14
  • 28
ahosek
  • 55
  • 2
  • 8
  • 1
    [See the `Closing` event](https://learn.microsoft.com/en-us/dotnet/api/system.windows.window.closing?view=netframework-4.8) on the msdn. You can utilize the `CancelEventArgs` object by adding a `param` block to your scriptblock to match the signature. – Maximilian Burszley Nov 25 '19 at 17:04
  • 2
    https://stackoverflow.com/a/58790227/3110834 – Reza Aghaei Nov 25 '19 at 17:07
  • 1
    @mklement0 tbf, they didn't explicitly state one or the other *in* the question. [Relevant documentation](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.form.formclosing?view=netframework-4.8) – Maximilian Burszley Nov 25 '19 at 19:20
  • Thanks, @TheIncorrigible1, that is the right link, but to save others time: The event arguments of the `FormClosing` event do _not_ allow you to distinguish between whether the form was closed via a call to `.Close()` or by the user via the close button in title bar. – mklement0 Nov 25 '19 at 19:22

2 Answers2

5

The FormClosing event argument object's .CloseReason property doesn't allow you to distinguish between the .Close() method having been called on the form and the user closing the form via the title bar / window system menu / pressing Alt+F4 - all these cases equally result in the .CloseReason property reflecting enumeration value UserClosing.

However, you can adapt the technique from Reza Aghaei's helpful C# answer on the subject, by inspecting the call stack for a call to a .Close() method:

using assembly System.Windows.Forms
using namespace System.Windows.Forms
using namespace System.Drawing

# Create a sample form.
$form = [Form] @{
    ClientSize      = [Point]::new(400,100)
    Text            = 'Closing Demo'
}    

# Create a button and add it to the form.
$form.Controls.AddRange(@(
    ($btnClose = [Button] @{
        Text              = 'Close'
        Location          = [Point]::new(160, 60)
    })
))

# Make the button call $form.Close() when clicked.
$btnClose.add_Click({
  $form.Close()
})

# The event handler called when the form is closing.
$form.add_Closing({
  # Look for a call to a `.Close()` method on the call stack.
  if ([System.Diagnostics.StackTrace]::new().GetFrames().GetMethod().Name -ccontains 'Close') {
    Write-Host 'Closed with .Close() method.'
  } else {
    Write-Host 'Closed via title bar / Alt+F4.'
  }
})

$null = $form.ShowDialog() # Show the form modally.
$form.Dispose()            # Dispose of the form.

If you run this code and try various methods of closing the form, a message indicating the method used should print (.Close() call vs. title bar / Alt+F4).

Note:

  • Closing the form via buttons assigned to the form's .CancelButton and .SubmitButton properties that don't have explicit $form.Close() calls still causes .Close() to be called behind the scenes.

  • The code requires PowerShell v5+, but it can be adapted to earlier versions.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

Checking call stack works fine and you can rely on it.

Just for the sake of completeness, specially for cases that you find a C# example and you want to use it in PowerShell in an easy way, I'll share an example showing how you can handle WM_SYSCOMMAND as shown in my linked post, in PowerShell.

using assembly System.Windows.Forms
using namespace System.Windows.Forms
using namespace System.Drawing

# Create the C# derived Form
$assemblies = "System.Windows.Forms", "System.Drawing"
$code = @'
using System;
using System.Windows.Forms;
public class MyForm:Form
{
    public bool ClosedByXButtonOrAltF4 { get; private set;}
    public const int SC_CLOSE = 0xF060;
    public const int WM_SYSCOMMAND = 0x0112;
    protected override void WndProc(ref Message msg)
    {
        if (msg.Msg == WM_SYSCOMMAND && msg.WParam.ToInt32() == SC_CLOSE)
            ClosedByXButtonOrAltF4 = true;
        base.WndProc(ref msg);
    }
    protected override void OnShown(EventArgs e)
    {
        ClosedByXButtonOrAltF4 = false;
    }    
}
'@
Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp   

# Create an instance of MyForm.
$form = [MyForm] @{
    ClientSize      = [Point]::new(400,100)
    Text            = "Closing Demo"
}    

# Create a button and add it to the form.
$form.Controls.AddRange(@(
    ($btnClose = [Button] @{
        Text              = "Close"
        Location          = New-Object System.Drawing.Point 160, 60
    })
))

# Make the button call $form.Close() when clicked.
$btnClose.add_Click({
  $form.Close()
})

# The event handler called when the form is closing.
$form.add_Closing({
  if ($form.ClosedByXButtonOrAltF4) {
    Write-Host 'Closed via title bar / Alt+F4.'
  } else {
    Write-Host 'Closed with .Close() method.'
  }
})

$null = $form.ShowDialog()
$form.Dispose()
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398