Powershell - can INotifyPropertyChanged be implemented natively?
Asked Answered
S

3

3

Does anyone know if the INotifyPropertyChanged interface can be implemented on an object in Powershell natively, without building a C# class and using Add-Type to generate a new .NET assembly?

I've Googled everything I can think of and haven't been able to find a solution.

Thanks.

Stinko answered 16/2, 2014 at 17:3 Comment(2)
Is there any reason why you want to avoid C# and Add-Type? It seems like the best option to me.Inweave
Mainly I'm more comfortable with Powershell versus C# and I find it easier to debug the native Powershell script versus the C#. Thanks.Stinko
M
3

I was experimenting using Powershell to load my WPF prototypes, and faced the same situation, so far I found that the solution is very simple it consists of adding interface methods to the powershell class, the last thing to deal with was the CanExecuteChanged event, through the exception message it says the method add_CanExecuteChanged wasnt found, and the solution is simply to add both the event adder and remover, for e

 class Relay: System.Windows.Input.ICommand {
    [Action[object]]$command;
    
    Relay([Action[object]]$Command){
        $this.command = $Command;
    }

    [void]add_CanExecuteChanged ([System.EventHandler]$handler){}
    [void]remove_CanExecuteChanged ([System.EventHandler]$handler){}

    [bool]CanExecute([object]$arg) {return  $true; }
    [void]Execute ([object]$arg){ $this.command?.Invoke($arg); }
}

for the INotifyPropertyChanged the situation is different since it needs getters and setters, and a call to the PropertyChanged handler on setters, the properties in powershell can be implmented as fields in C# and behind the scenes powershell add get_* and set_, which is the case in the dotnet when adding properties in the ggenerated IL you found get_, set_* methods which represents the getters and setters, for example:

class Demo {
    [string]$MyProperty;
}

if you do a Get-Member -Force on an instance in the result you will find (get_MyProperty, set_MyProperty) methods, but when I tried to do so "for example like in java" the methods wont execute, but however I tried the binding without these methods and it works fine, here is my gist of the experiment, with bindings working in two way mode:

https://gist.github.com/mouadcherkaoui/7b0f32d9dbefa71102acdbb07299c9bb

and here is the source I modified, the repos it self contains a lot of good scripts:

https://github.com/SammyKrosoft/PowerShell/blob/master/How-To-Load-WPF-From-XAML.ps1

Best Regards.

Myriad answered 17/3, 2021 at 17:49 Comment(4)
Mouad, this is neat and works well (and thanks for sharing), except that if you change a Powershell class string property that is bound to a textbox through a command, for example, the textbox value will not update because the setter doesn't have a PropertyChanged handler as you mention above. If all properties are updated through the UI your example works, but it seems relying on C# classes with proper INotifyPropertyChanged implementation is still required in certain cases.Stinko
I found an thread about adding properties, for the INotifyPropertyChanged you should add both add_PropertyChanged and remove_PropertyChanged methods and a backing propertyChanged event handler, then you use the Add-Member cmdlet to add a property of type scriptblock: class MainVM: INotifyPropertyChanged { [NotifyPropertyChangedEventHandler]$propertyChanged }Myriad
` class MainVM: INotifyPropertyChanged { [NotifyPropertyChangedEventHandler]$propertyChanged add_PropertyChanged([NotifyPropertyChangedEventHandler]$handler){ $this.propertyChanged += $handler } remove_PropertyChanged([NotifyPropertyChangedEventandler]$handler){ $this.propertyChanged -= $handler } ........ the property part in the next comment } `Myriad
in conjuction with the solution in this thread: #40977972 I didn't try it yet!Myriad
K
3

Here's a class that uses native .NET events. It derives from PSCustomObject and implements the INotifyPropertyChanged interface.

I've also added a use case with an ObservableCollection and a WPF DataGrid control. The control will be able to register its event handler through the add_PropertyChanged and remove_... methods, so changes to properties from code behind will be reflected by the UI.

class MyNode : PSCustomObject, System.ComponentModel.INotifyPropertyChanged
{
  hidden [string]$Nickname
  [System.Collections.ArrayList]$PropertyChanged = @()
  
  add_PropertyChanged([System.ComponentModel.PropertyChangedEventHandler]$handler)
  {
    $this.psobject.PropertyChanged.Add($handler)
  }
  remove_PropertyChanged([System.ComponentModel.PropertyChangedEventHandler]$handler)
  {
    $this.psobject.PropertyChanged.Remove($handler)
  }
  RaisePropertyChanged([string]$propname)
  {
    if ($this.psobject.PropertyChanged)
    {
      $evargs = [System.ComponentModel.PropertyChangedEventArgs]::new($propname)
      $this.psobject.PropertyChanged.Invoke($this, $evargs) # invokes every member
    }
  }
  MyNode()
  {
    $this | Add-Member -Name Nickname -MemberType ScriptProperty -Value {
        return $this.psobject.Nickname
      } -SecondValue {
        param($value)
        $this.psobject.Nickname = $value
        $this.psobject.RaisePropertyChanged('Nickname')
      }
  }
}

Code behind for the DataGrid control:

$items = New-Object System.Collections.ObjectModel.ObservableCollection[MyNode]
$item = [MyNode]::new()
$item.psobject.add_PropertyChanged({
    param($sender, $evargs)
    write-host 'changed' $evargs.PropertyName
 })
 $items.Add($item)
 $WPFdataGrid.ItemsSource = $items

Xaml for the DataGrid:

<DataGrid x:Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding}">
  <DataGrid.Columns>
    <DataGridTextColumn Header="Nickname" Binding="{Binding Nickname, Mode=TwoWay}" Width="160">
    </DataGridTextColumn>
  </DataGrid.Columns>
</DataGrid>

The code works with v5.1 on Windows 7.

Klecka answered 2/6, 2023 at 6:15 Comment(3)
Works! Since I was looking for a Powershell native solution and so does the author of this question, this has to be declared as the right answer.Gaillardia
My Respect for providing a very elegant solution to such an old question. I was bending over backwards to work around this issue by looking up each and every item in the visualtree and manually binding to it. I allmost made me give up on an amazing idea. For me this opens the door to a fully configurable list of tools for my fellow team members. Chapeau!Gaillardia
Great to know it worked over there. I can only test with 5.1 on Win 7 right now. @GaillardiaKlecka
I
2

No. Consider PowerShell a CLI consumer language and not so much a producer language. That is you can construct and use most .NET types. However PowerShell doesn't natively provide a facility to create new .NET types much less types that implement interfaces. While you can create custom objects in PowerShell and use tricks to give those objects a type name that PowerShell understands, those tricks don't work with .NET libraries like WPF.

Illogicality answered 17/2, 2014 at 4:38 Comment(1)
What about now, 5 years later?Process

© 2022 - 2024 — McMap. All rights reserved.