1

I have following code which is (almost) working as expected:

Add-Type -AssemblyName System.Windows.Forms

class MyForm : System.Windows.Forms.Form {
    MyForm($mystuff) {
        #Do-Stuff
        $this.Add_Load( $this.MyForm_Load )
    }

    $MyForm_Load = {
        $mlabel = [System.Windows.Forms.Label]::new()
            $mlabel.Name = "label"
            $mlabel.Text = "disabled"

        $mbutton = [System.Windows.Forms.Button]::new()
            $mbutton.Name = "button"
            $mbutton.Location = [System.Drawing.Point]::new(100,100)
            $mbutton.Add_Click( $this.mbutton_click )

        $this.Controls.Add($mlabel)
        $this.Controls.Add($mbutton)

    # ----------------------------------------------
    # Now $this.controls has something. We can now access it.
    # ----------------------------------------------
        if ($this.controls["label"].text -eq "enabled"){
            $mbutton.text = "disable"
        }else{
            $mbutton.text = "enable"
        }
    }

    $mbutton_click = {
        if ($this.Parent.Controls["label"].Text -eq "enabled"){
            $this.Parent.Controls["label"].Text = "disabled"
            $this.Parent.Controls["button"].Text = "enable"
        }
        else{
            $this.Parent.Controls["label"].Text = "enabled"
            $this.Parent.Controls["button"].Text = "disable"
        }
    }
}

$foo = [MyForm]::new("test")
$foo.ShowDialog()

but when I replace following section:

$mbutton_click = {
    if ($this.Parent.Controls["label"].Text -eq "enabled"){
        $this.Parent.Controls["label"].Text = "disabled"
        $this.Parent.Controls["button"].Text = "enable"
    }
    else{
        $this.Parent.Controls["label"].Text = "enabled"
        $this.Parent.Controls["button"].Text = "disable"
    }
}

For this (missing Parent):

$mbutton_click = {
    if ($this.Controls["label"].Text -eq "enabled"){
        $this.Controls["label"].Text = "disabled"
        $this.Controls["button"].Text = "enable"
    }
    else{
        $this.Controls["label"].Text = "enabled"
        $this.Controls["button"].Text = "disable"
    }
}

Then my script stops working and I see following error on console:

The property 'Text' cannot be found on this object. Verify that the property exists and can be set.

Why$MyForm_Load works without Parent but $mbutton_click requires Parent? Isn't both $MyForm_Load and $mbutton_click part of same object? How does Parent works in System.Windows.Forms?

Wakan Tanka
  • 7,542
  • 16
  • 69
  • 122
  • 1
    Inside the `$mbutton_click`, the variable `$this` refers to the button itself. Since the button is placed directly on the form, you need to use `.Parent` to reference the form (and its oter controls) – Theo Dec 11 '19 at 15:58

2 Answers2

2

That's because in the event handler $this is bound to the sender of the event (the button in this case) instead of your class instance. So something like this should also work:

$mbutton_click = {
    if ($this.Text -ne "enable") {
        $this.Parent.Controls["label"].Text = "disabled"
        $this.Text = "enable"
    }
    else{
        $this.Parent.Controls["label"].Text = "enabled"
        $this.Text = "disable"
    }
}
mhu
  • 17,720
  • 10
  • 62
  • 93
1

Like @mhu says, it's because one is bound to the Form Load event vs an individual control object.

A Form is a class. That means a Form has:

Properties

Properties are objects in the class that can be referenced. They can be simple strings like .Name, or complex like .Controls which return a complex Control.ControlCollection object.

Methods:

Calling a method calls a single function definition that is fully defined at compile time. Calling a method e.g. MyForm.ShowDialog() calls that individual .ShowDialog() function.

Events:

Sometimes we want to do something, but we can't fully define the method call at compile time. But, at the same time, we really like the convenience of calling something that is defined like a method. This is where Event's can be used.

First. We think of a method call for something useful that we want to happen, like MyForm.Load(), and that's all we have to define at compile time. Right now we don't know what we want to do. We know that we want to be able to load a form, but we don't know what it will do or look like. So we put this in as a placeholder that we can call.

After some thought, we figure out what we want to do, and how we want things to look like and we build a function that does something useful. We then subscribe this function to an Event. This is like connecting it.

In the first case:

MyForm($mystuff) {
    $this.Add_Load( $this.MyForm_Load )
}

We are registering MyForm_Load to the MyForm.Load event:

MyForm.Load -> MyForm_Load

This means that when we call MyForm.Load() it will call the connected function MyForm_Load that we wrote, and will execute it as if we wrote it as a real method at compile time.

Therefore inside MyForm_Load, $this refers to the MyForm Form object. i.e. No .parent needed, because you are the form.

Therefore to access the MyForm.Controls property, you can access it directly.

MyForm.Load -> MyForm_Load
MyForm.Controls

The second:

$MyForm_Load = {
    $mlabel = [System.Windows.Forms.Label]::new()
        $mlabel.Name = "label"
        $mlabel.Text = "disabled"

    $mbutton = [System.Windows.Forms.Button]::new()
        $mbutton.Name = "button"
        $mbutton.Location = [System.Drawing.Point]::new(100,100)
        $mbutton.Add_Click( $this.mbutton_click )

    $this.Controls.Add($mlabel)
    $this.Controls.Add($mbutton)
}

Adds Controls to the Form.Controls object:

MyForm.Load -> MyForm_Load
MyForm.Controls 
           |-> mlabel  
           |-> mbutton

The mbutton control has a click event attached:

$MyForm_Load = {
...
    $mbutton.Add_Click( $this.mbutton_click )
...
}

$mbutton_click = {
...
        $this.Parent.Controls["label"].Text = "disabled"
...
}

So it now looks like:

MyForm.Load -> MyForm_Load
MyForm.Controls
           |-> mlabel.Text
           |-> mbutton.Click -> mbutton_click

So to go from MyForm_Load to mlabel.Text is:

 $this  .Controls["label"] .Text 
(MyForm).Controls[(mlabel)].Text

Whereas from mbutton_click, the mbutton doesn't have any controls inside it. You have to go "up" a level to the form to get the mlabel control:

 $this   .Parent  .Controls["label"] .Text 
(mbutton).(MyForm).Controls[(mlabel)].Text
HAL9256
  • 12,384
  • 1
  • 34
  • 46