Home > Software design >  PowerShell classes and .NET events
PowerShell classes and .NET events

Time:11-23

I am working on a PowerShell script with a small WPF GUI. My code is organzized in a class from which a singleton is created. I have read that $this inside an event handler script block points to the event sender and not to my containing class instance. How can I access my class instance from the event handler?

Ex.

class MyClass {

    $form  #Reference to the WPF form


    [void] StartAction([object] $sender, [System.Windows.RoutedEventArgs] $e) {
        
        ...
    }


    [void] SetupEventHandlers() {

        $this.form.FindName("BtnStartAction").add_Click({ 
            param($sender, $e) 
            # !!!! Does not work, $this is not my class instance but the event sender !!!!
            $this.StartAction($sender, $e) 
        })

    }


    [void] Run() {

        $this.InitWpf() #Initializes the form from a XAML file.

        $this.SetupEventHandlers()

        ...
    }
}


$instance = [MyClass]::new()
$instance.Run()

CodePudding user response:

  • Indeed, the automatic $this variable in a script block acting as a .NET event handler refers to the event sender.

  • If an event-handler script block is set up from inside a method of a PowerShell custom class, the event-sender definition of $this shadows the usual definition in a class method (referring to the class instance at hand).

There are two workarounds, both relying on PowerShell's dynamic scoping, which allows descendant scopes to see variables from ancestral scopes.

  • Use Get-Variable -Scope 1 to reference the parent scope's $this value (event-handler script blocks run in a child scope of the caller).
    [void] SetupEventHandlers() {

        $this.form.FindName("BtnStartAction").add_Click({ 
            param($sender, $e) 
            # Get the value of $this from the parent scope.
            (Get-Variable -ValueOnly -Scope 1 this).StartAction($sender, $e) 
        })

    }
  • Taking more direct advantage of dynamic scoping, you can go with Abdul Niyas P M's suggestion, namely to define a helper variable in the caller's scope that references the custom-class instance under a different name, which you can reference in - potentially multiple - event-handler script blocks set up from the same method:
    [void] SetupEventHandlers() {

        # Set up a helper variable that points to $this
        # under a different name.
        $thisClassInstance = $this

        $this.form.FindName("BtnStartAction").add_Click({ 
            param($sender, $e) 
            # Reference the helper variable.             
            $thisClassInstance.StartAction($sender, $e) 
        })

    }

Also note that, as of PowerShell 7.2, you cannot directly use custom-class methods as event handlers - this answer shows workarounds, which also require solving the $this shadowing problem.

CodePudding user response:

Try this sample

class MyClass {

    $form  #Reference to the WPF form


    [void] StartAction([System.Windows.RoutedEventArgs] $e) {
        #$this ...
    }


    [void] SetupEventHandlers() {

        $this.form.FindName("BtnStartAction").add_Click({ 
            param($sender, $e) 
            ([MyClass]$sender).StartAction($e)
        })

    }


    [void] Run() {
        $this.InitWpf()
        $this.SetupEventHandlers()
    }
}


$instance = [MyClass]::new()
$instance.Run()

Edit 1: Could you try creating a delegate referring to a dedicated method of the class like this?

class MyClass {

    $form  #Reference to the WPF form
    
    [void] StartAction() {
        #$this ...
    }


    [void] SetupEventHandlers() {
        $handler = [System.EventHandler]::CreateDelegate([System.EventHandler], $this, "Handler")
    
        $this.form.FindName("BtnStartAction").add_Click($handler)

    }

    [void] Handler ([System.Object]$sender, [System.EventArgs]$e) {
        $this.StartAction()
    }

    [void] Run() {
        $this.InitWpf()
        $this.SetupEventHandlers()
    }
}


$instance = [MyClass]::new()
$instance.Run()
  • Related