15 févr. 2025

[Dynamo += Python] PythonNet série : le pattern MVVM (WPF)

 


Appliquer le pattern MVVM(WPF) avec Dynamo+PythonNet, c'est possible ?

#DynamoBIM #Python #WPF #MVVM     #AutodeskExpertElite #AutodeskCommunity 

if this article is not in your language, use the Google Translate widget ⬈ (bottom of page for Mobile version )


Pour savoir ce qu'est le pattern MVVM (Model View ViewModel) je vous renvoie à mon article précédent sur le sujet.

https://voltadynabim.blogspot.com/2023/03/dynamo-pythonwpf-avec-ironpython-et-mvvm.html


L'utilisation du MVVM passe par la création d'une classe qui hérite de l'interface INotifyPropertyChanged, cela tombe bien, nous avons vu comment implémenter une classe d'interface avec PythonNet dans un précédent article.

https://voltadynabim.blogspot.com/2024/10/dynamo-python-pythonnet-et-les.html

Il est donc possible, avec PythonNet, de définir une classe appelable depuis le CLR

Voici un Exemple d'une classe CLR (.Net)



class Temperature:
    def __new__(cls, *args, **kwargs):
        cls.args = args
        cls.__namespace__ = "Temperature_tEfYX0DHE"
        try:
            module_type = __import__(cls.__namespace__)
            return module_type._Temperature(*cls.args)
        except ImportError as ex:        
            class _Temperature(System.IComparable):
                __namespace__ = cls.__namespace__
                def __init__(self, init_value):
                    self.__t = System.Int32(init_value)
                    self.__init_value = System.Int32(init_value)
                    super().__init__()

                def CompareTo(self, obj):
                    if obj is None:
                        return System.Int32(1)
                    else:
                        return self.Celsius.CompareTo(obj.Celsius)
                        
                # add custom property
                @clr.clrproperty(System.Int32)
                def InitValue(self):
                    return self.__init_value
                        
                # define getter and setter
                def get_Celsius(self):
                    return self.__t
                def set_Celsius(self, value: System.Int32):
                    self.__t = System.Int32(value)
                    
                Celsius = clr.clrproperty(System.Int32, get_Celsius, set_Celsius)
                
                # add custom method
                @clr.clrmethod(System.Int32)
                def ToFahrenheit(self):
                    return (self.Celsius * 1.8) + 32
                    
                @clr.clrmethod(System.Int32, System.Int32)
                def Multiply(self, x):
                    return self.Celsius * x
                    
            return _Temperature(*cls.args)
Cependant, à partir la dernière version du moteur Dynamo PythonNet3 (v1.1.0+) la Team Dynamo a rendu la syntaxe bien plus simple.

     
class Temperature(System.IComparable):
    __namespace__ = "Temperature_tEfYX0DHE"
    def __init__(self, init_value):
        self.__t = System.Int32(init_value)
        self.__init_value = System.Int32(init_value)
        super()
    def CompareTo(self, obj):
        if obj is None:
            return System.Int32(1)
        else:
            return self.Celsius.CompareTo(obj.Celsius)
            
    # add custom property
    @clr.clrproperty(System.Int32)
    def InitValue(self):
        return self.__init_value
            
    # define getter and setter
    def get_Celsius(self):
        return self.__t
    def set_Celsius(self, value: System.Int32):
        self.__t = System.Int32(value)
        
    Celsius = clr.clrproperty(System.Int32, get_Celsius, set_Celsius)
    
    # add custom method
    @clr.clrmethod(System.Int32)
    def ToFahrenheit(self):
        return (self.Celsius * 1.8) + 32
        
    @clr.clrmethod(System.Int32, System.Int32)
    def Multiply(self, x):
        return self.Celsius * x
                    

Résumé de la syntaxe, les mots clés :

__namespace__ = "MyNameSpace_"  : crée un nouveau type géré qui peut être utilisé à partir du framework .Net

@clr.clrmethod(<input_Type>, <output_Type>) : définit une méthode CLR

@clr.clrproperty(<output_Type>) : définit une propriété CLR (seulement le getter), équivalent au décorateur Python @property

MyProperty = clr.clrproperty(<Type>, get_method, set_method) : définit une propriété CLR ( getter et setter), équivalent à la méthode Python property()


Dans le cadre d'une classe qui héritera de l'interface  INotifyPropertyChanged, on rajoutera les méthodes liées à OnPropertyChanged

  • Exemple avec un interverrouillage de 2 combobox


.......

import clr
import sys
import System
from System.Collections.Generic import List, Dictionary
from System.Collections.ObjectModel import ObservableCollection
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
import System.Windows.Controls 
from System.Windows.Controls import *
import System.Windows.Controls.Primitives 
from System.Collections.Generic import List
from System.IO import StringReader
from System.Xml import XmlReader
from System.Windows import LogicalTreeHelper 
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs

import time
import traceback


class ViewModel(INotifyPropertyChanged):
    __namespace__ = "ViewModel_wOvcjxcQvgxpY" # rename it each edition
    def __init__(self, lstItems):
        super().__init__()
        self._lstItems = ObservableCollection[System.Object](lstItems)
        self._txtValue = System.String("")
        self._itemA = self._lstItems.get_Item(0) if lstItems else None
        self._itemB = self._lstItems.get_Item(1) if len(lstItems) > 1 else None
        self._property_changed_handlers = []
        
    # define getter and setter
    def get_Items(self):
        return self._lstItems
    def set_Items(self, value):
        self._lstItems = value
        self.OnPropertyChanged("Items")
    # apply clrproperty 
    Items = clr.clrproperty(ObservableCollection[System.Object], get_Items, set_Items)
    
    def get_TxtValue(self):
        return self._txtValue
    def set_TxtValue(self, value):
        self._txtValue = value
        self.OnPropertyChanged("TxtValue")
    # apply clrproperty 
    TxtValue = clr.clrproperty(System.String, get_TxtValue, set_TxtValue)
        
    def get_ItemA(self):
        return self._itemA
    def set_ItemA(self, value):
        if value == self._itemB: 
            self.ItemB = None
        self._itemA = value
        self.OnPropertyChanged("ItemA")
    # apply clrproperty 
    ItemA = clr.clrproperty(System.String, get_ItemA, set_ItemA)
        
        
    def get_ItemB(self):
        return self._itemB
    def set_ItemB(self, value):
        if value == self._itemA :
            self.ItemA = None
        self._itemB = value
        self.OnPropertyChanged("ItemB")
    # apply clrproperty 
    ItemB = clr.clrproperty(System.String, get_ItemB, set_ItemB)
    
    def OnPropertyChanged(self, property_name):
        event_args = PropertyChangedEventArgs(property_name)
        for handler in self._property_changed_handlers:
            handler(self, event_args)

    # Implementation of add/remove_PropertyChanged
    def add_PropertyChanged(self, handler):
        #print(handler)
        if handler not in self._property_changed_handlers:
            self._property_changed_handlers.append(handler)

    def remove_PropertyChanged(self, handler):
        #print(handler)
        if handler in self._property_changed_handlers:
            self._property_changed_handlers.remove(handler)
                       

class MainForm(Window):
    string_xaml = '''
        <Window 
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                Title="IronPython WPF Form" Height="200" Width="300">
                <Grid Margin="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
        
                    <!-- ComboBox 1 -->
                    <Label Grid.Row="0" Grid.Column="0" Content="Select Option 1:" VerticalAlignment="Center"/>
                    <ComboBox Grid.Row="0" Grid.Column="1" 
                        Name="ComboBox1" Margin="5" VerticalAlignment="Center"
                        ItemsSource="{Binding Items}"
                        SelectedItem="{Binding ItemA, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        
                    <!-- ComboBox 2 -->
                    <Label Grid.Row="1" Grid.Column="0" Content="Select Option 2:" VerticalAlignment="Center"/>
                    <ComboBox Grid.Row="1" Grid.Column="1" 
                        Name="ComboBox2" Margin="5" VerticalAlignment="Center"
                        ItemsSource="{Binding Items}"
                        SelectedItem="{Binding ItemB, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        
                    <!-- Submit Button -->
                    <Button Grid.Row="2" Grid.ColumnSpan="2" Content="Submit" 
                            Name="SubmitButton" Margin="5" Width="80" Height="30" 
                            VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
                </Grid>
        </Window>'''
        
    def __new__(cls, *args):
      reader = System.Xml.XmlReader.Create(System.IO.StringReader(MainForm.string_xaml))
      window = System.Windows.Markup.XamlReader.Load(reader)
      window.__class__ = cls
      return window
  
    def __init__(self, lstvalue):
        super().__init__()
        self.lstvalue = lstvalue
        # out data
        self.vm = ViewModel(lstvalue)
        self.DataContext = self.vm
        self.InitializeComponent()
        
    def InitializeComponent(self):
        #
        self.button = LogicalTreeHelper.FindLogicalNode(self, "SubmitButton")
        self.button.Click += self.ButtonClick
        
    def ButtonClick(self, sender, e):
        try:
            self.Close()
        except Exception as ex:
            print(traceback.format_exc())
            
lstValues = IN[0]
out = []
my_window = MainForm(lstValues)
my_window.ShowDialog()
out.append(my_window.vm.ItemA)
out.append(my_window.vm.ItemB)

OUT = out

  • Exemple avec un mapping d'ElementType / SousProjet via une DataGrid (ComboBox Column)



.


import clr
import sys
import System
from System.Collections.ObjectModel import ObservableCollection
#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

#Get Important vars
doc = DocumentManager.Instance.CurrentDBDocument
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application
sdkNumber = int(app.VersionNumber)

clr.AddReference('System.Data')
from System.Data import *

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
import System.Windows.Controls 
from System.Windows.Controls import *
from System.IO import StringReader
from System.Xml import XmlReader
from System.Windows import LogicalTreeHelper 
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs

import time
import traceback
import itertools


class ViewModel(INotifyPropertyChanged): # INotifyPropertyChanged
    __namespace__ = "ViewModel_jhggsbUbwQpY" # rename it each edition class
    def __init__(self, elem_type, lst_Workset):
        super().__init__()
        self._elem_type = elem_type 
        self._SelectValue = lst_Workset[0] # set default workset
        self._lst_Workset = ObservableCollection[DB.Workset](lst_Workset)
        #
        self._property_changed_handlers = []
        self.PropertyChanged = None
        
    @clr.clrproperty(DB.Element)
    def ElementType(self):
        return self._elem_type
    
    @clr.clrproperty(System.String)
    def Name(self):
        return self._elem_type.get_Name()
        
    @clr.clrproperty(System.String)
    def FamilyName(self):
        return self._elem_type.FamilyName
        
    def get_SelectValue(self):
        return self._SelectValue
    def set_SelectValue(self, value):
        if self._SelectValue != value:
            self._SelectValue = value
            self.OnPropertyChanged("SelectValue")
    # Add SelectValue as a clr property
    SelectValue = clr.clrproperty(DB.Workset, get_SelectValue, set_SelectValue)
       
    @clr.clrproperty(ObservableCollection[DB.Workset])
    def LstWorkset(self):
        return self._lst_Workset
        
    def OnPropertyChanged(self, property_name):
        event_args = PropertyChangedEventArgs(property_name)
        for handler in self._property_changed_handlers:
            handler(self, event_args)

    # Implementation of add/remove_PropertyChanged
    def add_PropertyChanged(self, handler):
        if handler not in self._property_changed_handlers:
            self._property_changed_handlers.append(handler)

    def remove_PropertyChanged(self, handler):
        if handler in self._property_changed_handlers:
            self._property_changed_handlers.remove(handler)
    


class MainWindow(Window):
    string_xaml = '''
<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="Selection"
        Height="700" MinHeight="700"
        Width="700" MinWidth="780"
        x:Name="MainWindow">
        <Window.Resources>
        </Window.Resources>
        <Grid Width="auto" Height="auto">
            <Grid.RowDefinitions>
                <RowDefinition Height="30" />
                <RowDefinition />
                <RowDefinition Height="60" />
            </Grid.RowDefinitions>
            <Label
                x:Name="label1"
                Content="Selection"
                Grid.Column="0" Grid.Row="0"
                HorizontalAlignment="Left" VerticalAlignment="Bottom"
                Margin="8,0,366.6,5"
                Width="415" Height="25" />
            <DataGrid
                x:Name="dataGrid"
                AutoGenerateColumns="False"
                ItemsSource="{Binding}" 
                Grid.Column="0" Grid.Row="1"
                HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                Margin="8,3,8,7"
                SelectionUnit="Cell"
                CanUserAddRows="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Family Name" Binding="{Binding FamilyName}" Width="*" />
                    <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*" />
                    <DataGridTextColumn Header="Category" Binding="{Binding ElementType.Category.Name}" Width="*" />
                    <DataGridTemplateColumn Header="Workset">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <ComboBox x:Name="Combobox"
                                    ItemsSource="{Binding LstWorkset}" 
                                    DisplayMemberPath="Name"
                                    SelectedItem="{Binding SelectValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                    Width="200"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
            <Button
                x:Name="buttonCancel"
                Content="Annuler"
                Grid.Column="0" Grid.Row="2"
                HorizontalAlignment="Left" VerticalAlignment="Bottom"
                Margin="18,13,0,10"
                Height="30" Width="120">
            </Button>
            <Button
                x:Name="buttonOK"
                Content="OK"                
                Grid.Column="0" Grid.Row="2"
                HorizontalAlignment="Right" VerticalAlignment="Bottom"
                Margin="0,12,22,10"
                Height="30" Width="120">
            </Button>
        </Grid>
</Window>'''
  
    def __init__(self, lst_wkset, lst_elems):
        super().__init__()
        self._lst_wkset = lst_wkset
        self._lst_elems = lst_elems
        self._set_elemTypeId = set(x.GetTypeId() for x in lst_elems if isinstance(x, FamilyInstance))
        self._lst_elemType = [doc.GetElement(xId) for xId in self._set_elemTypeId if xId != ElementId.InvalidElementId]
        #
        #sort _lst_elemType by Name    
        self._lst_elemType= sorted(self._lst_elemType, key = lambda x : x.FamilyName)
        #
       # Create an ObservableCollection of MyDataViewModel objects
        self.data = ObservableCollection[System.Object]()
        for elem in self._lst_elemType:
            self.data.Add(ViewModel(elem, self._lst_wkset))
        #
        self.pairLst = []
        #
        xr = XmlReader.Create(StringReader(MainWindow.string_xaml))
        self.winLoad = XamlReader.Load(xr) 
        self.InitializeComponent()
        
    def InitializeComponent(self):
        try:
            #self.winLoad.Resources["DataSetResource"] = self.dtSet
            self.Content = self.winLoad.Content
            #
            self.dataGrid = LogicalTreeHelper.FindLogicalNode(self.winLoad, "dataGrid")
            #
            self.buttonCancel = LogicalTreeHelper.FindLogicalNode(self.winLoad, "buttonCancel")
            self.buttonCancel.Click += self.ButtonCancelClick
            #
            self.buttonOK = LogicalTreeHelper.FindLogicalNode(self.winLoad, "buttonOK")
            self.buttonOK.Click += self.ButtonOKClick
            #
            self.winLoad.Loaded += self.OnLoad
            #
            self.dataGrid.DataContext = self.data #self._tableDataType.DefaultView
            self.winLoad.DataContext = self.data
            # Set DataContext for the DataSet
        except Exception as ex:
            print(traceback.format_exc())
              
    def OnLoad(self, sender, e):
        print("UI loaded")

    def ButtonCancelClick(self, sender, e):
        self.outSelection = []
        self.winLoad.Close()
        
    def ButtonOKClick(self, sender, e):
        try:
            # get result from input Data (Binding)
            self.pairLst = [[x.ElementType, x.SelectValue] for x  in self.data]
            self.winLoad.Close()
        except Exception as ex:
            print(traceback.format_exc())
            
lst_Elements = UnwrapElement(IN[0])
lst_Wkset = FilteredWorksetCollector(doc).OfKind(WorksetKind.UserWorkset).ToWorksets()
objWindow = MainWindow(lst_Wkset, lst_Elements)
objWindow.winLoad.ShowDialog()

OUT = objWindow.pairLst  




Remarques et Astuces :
  • Vous pouvez utiliser l'interface NotificationObject de Dynamo.Core ce qui simplifie la syntaxe (uniquement possible avec PythonNet3)

  • Ce n'est pas obligatoire, mais je vous conseille de renommer le __namespace__de votre classe ViewModel à chaque fois que vous modifiiez celle-ci

  • Si votre classe hérite de System.Windows.Window et que vous souhaitez que l'objet généré par XamlReader.Load(xr) corresponde à votre instance de classe (self), 2 solutions :

    • redéfinir la classe avec le constructeur __new__ (uniquement possible avec PythonNet3)
pour rappel la méthode  __init__ n'est pas le constructeur
    • copier les propriétés de l'objet généré par XamlReader.Load(xr) à votre instance de classe (self) 

  • Vous pouvez utiliser la librairie DotNetProjects.Wpf.Extended.Toolkit (Un fork OpenSource de wpftoolkit) qui est disponible dans le contexte Dyamo 
    https://github.com/dotnetprojects/WpfExtendedToolkit

  1. Exemple d'utilisation du constructeur __new__

import clr
import System
from System.Threading import Thread, ThreadStart, ApartmentState

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
clr.AddReference("DynamoCore")
from System.Windows import LogicalTreeHelper
from Dynamo.Core import NotificationObject

class ViewModel(NotificationObject):
  __namespace__ = "ViewModel_zlcg" # rename it each edition class
  def __init__(self):
    super().__init__()
  
  _text = "Hieee"  
  def get_Text(self):
    return self._text
  def set_Text(self, value):
    self._text = value
    self.RaisePropertyChanged("Text")
  Text = clr.clrproperty(str, get_Text, set_Text)
          
  
class MyWindow(System.Windows.Window):
    xaml = '''
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="82" Width="350">
      <StackPanel Orientation="Horizontal" Margin="10">
        <Label Content="Text:"/>
        <Label Content="{Binding Text}"/>
        <TextBox Margin="10 0" Width="150" 
            Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
        <Button x:Name="Button" Content="Reverse" />
      </StackPanel>
    </Window>'''
    
    def __new__(cls):
      reader = System.Xml.XmlReader.Create(System.IO.StringReader(cls.xaml))
      window = System.Windows.Markup.XamlReader.Load(reader)
      window.__class__ = cls
      return window
    
    def __init__(self):
      super().__init__()
      self.vm = ViewModel()
      self.DataContext = self.vm
      LogicalTreeHelper.FindLogicalNode(self, "Button").Click += self.buttonClick
      
    def buttonClick(self, sender, e):
      self.DataContext.Text = self.DataContext.Text[::-1]
            
def main():   
    try:         
        window = MyWindow()
        window.ShowDialog()
        # print result
        print(window.vm.Text)
    except Exception as ex:
        print(ex)

# create a STA thread to excecute in DynamoSandBox
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
.
  1. Exemple de copie des propriétés de l'objet généré par XamlReader.Load(xr) à votre instance de classe (self) 
    
    import clr
    import System
    from System.Threading import Thread, ThreadStart, ApartmentState
    
    clr.AddReference("System.Reflection")
    from System.Reflection import BindingFlags
    
    clr.AddReference("System.Xml")
    clr.AddReference("PresentationFramework")
    clr.AddReference("PresentationCore")
    clr.AddReference("System.Windows")
    clr.AddReference("DynamoCore")
    from System.Windows import LogicalTreeHelper
    from Dynamo.Core import NotificationObject
    
    
    class ViewModel(NotificationObject):
        __namespace__ = "MyViewModel" # rename it each edition class
        def __init__(self):
            super().__init__()
        _text = "Hieee"  
       
        def get_Text(self):
            return self._text
        def set_Text(self, value):
            self._text = value
            self.RaisePropertyChanged("Text")
        Text = clr.clrproperty(str, get_Text, set_Text)
      
    
      
    class MyWindow(System.Windows.Window):
        xaml = '''
        <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Height="82" Width="350">
            <StackPanel Orientation="Horizontal" Margin="10">
                <Label Content="Text:"/>
                <Label Content="{Binding Text}"/>
                <TextBox Margin="10 0" Width="150" 
                Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
                <Button x:Name="Button" Content="Reverse" />
            </StackPanel>
        </Window>'''
        
        
        def __init__(self):
            super().__init__()
            xr = System.Xml.XmlReader.Create(System.IO.StringReader(MyWindow.xaml))
            root = System.Windows.Markup.XamlReader.Load(xr)
            # copy attributes from the root window to self
            for prop_info in root.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance):
                if prop_info.CanWrite:
                    try:
                        setattr(self, prop_info.Name, prop_info.GetValue(root)) 
                    except Exception as ex:
                        print(ex, f"{prop_info.Name=}")
            self.vm = ViewModel()
            self.DataContext = self.vm
            LogicalTreeHelper.FindLogicalNode(self, "Button").Click += self.buttonClick
          
        def buttonClick(self, sender, e):
          self.DataContext.Text = self.DataContext.Text[::-1]
                
    def main():   
        try:         
            window = MyWindow()
            window.ShowDialog()
            # print result
            print(window.vm.Text)
        except Exception as ex:
            print(ex)
            
    # create a STA thread to excecute in DynamoSandBox
    thread = Thread(ThreadStart(main))
    thread.SetApartmentState(ApartmentState.STA)
    thread.Start()
    thread.Join()
    

    .
    1. Exemple d'utilisation de la librairie DotNetProjects.Wpf.Extended.Toolkit 
    ici, nous utilisons l'équivalent du System.Windows.Forms.NumericUpDown
     

    .
    
    import clr
    import System
    from System.Threading import Thread, ThreadStart, ApartmentState
    
    clr.AddReference("System.Xml")
    clr.AddReference("PresentationFramework")
    clr.AddReference("PresentationCore")
    clr.AddReference("System.Windows")
    clr.AddReference("DynamoCore")
    from System.Windows.Markup import XamlReader, XamlWriter
    from System.Windows import LogicalTreeHelper
    from Dynamo.Core import NotificationObject
    
    clr.AddReference("DotNetProjects.Wpf.Extended.Toolkit")
    
    
    class ViewModel(NotificationObject):
        __namespace__ = "ViewModel_zlcxfQzcQg" ViewModel
        def __init__(self):
            super().__init__()
            self._numeric_value = "1564"
              
        def get_NumericValue(self):
            return self._numeric_value
        def set_NumericValue(self, value):
            if self._numeric_value != value:
                self._numeric_value = value
                self.OnPropertyChanged("NumericValue")
        # Add SelectValue as a clr property
        NumericValue = clr.clrproperty(System.String, get_NumericValue, set_NumericValue)
           
    
    
    class MyWindow(System.Windows.Window):
        xaml = '''
        <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
            Height="200" Width="350">
            <StackPanel Orientation="Vertical" Margin="10">
                <xctk:IntegerUpDown Name="myUpDownControl" Margin="10" Value="{Binding NumericValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                <Button x:Name="Button" Content="Close" Margin="10"/>
            </StackPanel>
        </Window>'''
        
        
        def __init__(self):
            super().__init__()
            xr = System.Xml.XmlReader.Create(System.IO.StringReader(MyWindow.xaml))
            root = System.Windows.Markup.XamlReader.Load(xr)
            # copy attributes from the root window to self
            for prop_info in root.GetType().GetProperties():
                if prop_info.CanWrite:
                    try:
                        setattr(self, prop_info.Name, prop_info.GetValue(root)) 
                    except Exception as ex:
                        print(ex, f"{prop_info.Name=}")
            self.vm = ViewModel()
            self.DataContext = self.vm
            LogicalTreeHelper.FindLogicalNode(self, "Button").Click += self.buttonClick
    
        def buttonClick(self, sender, e):
            self.Close()
                
    def main():   
        try:      
            window = MyWindow()
            window.ShowDialog()
            globals()['results'] = window.vm.NumericValue
        except Exception as ex:
            print(ex)
    
    results = None
    # create a STA thread to excecute in DynamoSandBox
    thread = Thread(ThreadStart(main))
    thread.SetApartmentState(ApartmentState.STA)
    thread.Start()
    thread.Join()
    
    OUT = results
    
    Dans un prochain article, nous essaierons de voir la possibilité de rajouter une interface IValueConverter 





    « Si tu as une pomme, que j'ai une pomme, et que l'on échange nos pommes, nous aurons chacun une pomme. Mais si tu as une idée, que j'ai une idée et que l'on échange nos idées, nous aurons chacun deux idées. »
    George Bernard Shaw
    .

    0 commentaires:

    Enregistrer un commentaire