Un article assez technique qui ne sera peut-être pas souvent utilisé dans Dynamo, mais je tenais à partager un exemple de MVVM via IronPython avec Dynamo.
#MVVM #IronPython #DynamoBIM
L'architecture MVVM (Model View ViewModel) est un modèle de conception
logicielle qui permet de séparer les différentes responsabilités d'une
application en trois couches distinctes :
le modèle (Model),
la vue (View) et
le ViewModel (ViewModel).
- Le modèle est responsable de la gestion des données de l'application. Il peut s'agir de données stockées dans une base de données, dans des fichiers ou de données générées dynamiquement. Le modèle expose des méthodes pour accéder et modifier ces données.
- La vue est responsable de l'interface utilisateur de l'application. Elle est responsable de l'affichage des données du modèle et de la gestion des interactions avec l'utilisateur. La vue est généralement mise en œuvre en utilisant des technologies telles que XAML ou HTML.
- Le ViewModel est responsable de la logique métier de l'application. Il est responsable de la communication entre la vue et le modèle. Le ViewModel contient des propriétés qui sont liées aux éléments de la vue et expose des commandes qui sont déclenchées par les événements de la vue. Le ViewModel est également responsable de la validation des données et de la gestion des erreurs.
L'un des avantages de l'architecture MVVM c'est qu'elle permet aux développeurs de travailler sur des parties distinctes de l'application sans affecter les autres parties.
Cependant, l'architecture MVVM peut être plus complexe que d'autres modèles de conception. Elle peut par ailleurs nécessiter des compétences supplémentaires pour les développeurs qui ne sont pas familiers avec ce modèle.
Un élément clé pour la création d'un MVVM (WPF) est l'interface
INotifyPropertyChanged
.
Cette interface permet de notifier la vue (View) lorsqu'une propriété
dans le ViewModel (ViewModel) a été modifiée, de sorte que la vue puisse
être mise à jour en conséquence.
Lorsque le ViewModel modifie une propriété, il déclenche un événement
PropertyChanged
pour notifier la vue qu'une propriété a été modifiée. La vue écoute cet
événement et met à jour son interface utilisateur en réaction.
Maintenant, voyons comment implémenter tout ceci avec IronPython, prenons pour exemple un mapping de familles Revit
-
Le code xaml représente la
Vue
-
La classe DataModel représente le
Model
(Donnée d'entrée) au travers d'une ObservableCollection[Type]. Ainsi, on modifiera directement cette collection au travers du MVVM
-
La classe MyDataViewModel constitue la
Data
(classe qui hérite de la classe ViewModel et de l'interface INotifyPropertyChanged
....
code 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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Height="600"
Width="600"
ResizeMode="NoResize"
Title="A"
WindowStartupLocation="CenterScreen"
Topmost="True"
SizeToContent="Width">
<Grid Margin="10,0,10,10">
<Label x:Name="selection_label" Content="Select Item" HorizontalAlignment="Left" Height="30"
VerticalAlignment="Top"/>
<Button x:Name="button_select"
Content="Select"
HorizontalAlignment="Center"
Height="26" Margin="0,63,0,0"
VerticalAlignment="Bottom"
Width="200"
Click="ButtonClick"/>
<DataGrid x:Name="dataGrid"
AutoGenerateColumns="False"
Margin="10,30,10,30"
BorderThickness="1"
RowHeaderWidth="0"
CanUserSortColumns="True"
CanUserResizeColumns = "False"
VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="FamilyName"
Binding="{Binding FamilyName}"
IsReadOnly="True"
Width="250"/>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"
IsReadOnly="True"
Width="100"/>
<DataGridTemplateColumn Header="Param">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="Combobox"
ItemsSource="{Binding LstValue}"
SelectedItem="{Binding SelectValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="200"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
import clr
import sys
import System
from System.Collections.Generic import List, KeyValuePair
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
doc = DocumentManager.Instance.CurrentDBDocument
sys.path.append(r'C:Program Files (x86)IronPython 2.7Lib')
sys.path.append(r'C:Program Files (x86)IronPython 2.7DLLs')
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
try:
clr.AddReference("IronPython.Wpf")
clr.AddReference('System.Core')
clr.AddReference('System.Xml')
clr.AddReference('PresentationCore')
clr.AddReference('PresentationFramework')
clr.AddReferenceByPartialName("WindowsBase")
except IOError:
raise
from System.IO import StringReader
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
from System.ComponentModel import INotifyPropertyChanged
from System.ComponentModel import PropertyChangedEventArgs
try:
import wpf
import time
except ImportError:
raise
class XamlLoader(Window):
def __init__(self, xaml_str):
self.ui = wpf.LoadComponent(self, StringReader(xaml_str))
self.ui.Title = "Select Types"
def ButtonClick(self, sender, e):
self.DialogResult = True
self.Close()
def __getattr__(self, item):
"""Maps values to attributes.
Only called if there *isn't* an attribute with this name
"""
return self.ui.FindName(item)
class ViewModelBase(INotifyPropertyChanged):
"""
base view model class that implements the INotifyPropertyChanged interface
"""
def __init__(self):
self.propertyChangedHandlers = []
#
# Define a method to raise the PropertyChanged event
def RaisePropertyChanged(self, propertyName):
# Create a PropertyChangedEventArgs object with the name of the changed property
args = PropertyChangedEventArgs(propertyName)
for handler in self.propertyChangedHandlers:
# Invoke each of the registered property changed handlers with the ViewModelBase instance and the event arguments
handler(self, args)
#
# Define a method to add a property changed handler
def add_PropertyChanged(self, handler):
self.propertyChangedHandlers.append(handler)
#
# Define a method to remove a property changed handler
def remove_PropertyChanged(self, handler):
self.propertyChangedHandlers.remove(handler)
class MyDataViewModel(ViewModelBase):
"""
a view model class that inherits from ViewModelBase
"""
def __init__(self):
ViewModelBase.__init__(self)
self._Name = ""
self._FamilyName = ""
# define a attribute to store the selected value from conbobox (binding)
self._SelectValue = ""
self._LstValue = ObservableCollection[System.String]()
# Define all getters and setters properties
@property
def Name(self):
return self._Name
@Name.setter
def Name(self, value):
self._Name = value
# Raise the PropertyChanged event with the name of the changed property
self.RaisePropertyChanged("Name")
@property
def FamilyName(self):
return self._FamilyName
@FamilyName.setter
def FamilyName(self, value):
self._FamilyName = value
# Raise the PropertyChanged event with the name of the changed property
self.RaisePropertyChanged("FamilyName")
@property
def SelectValue(self):
return self._SelectValue
@SelectValue.setter
def SelectValue(self, value):
self._SelectValue = value
# Raise the PropertyChanged event with the name of the changed property
self.RaisePropertyChanged("SelectValue")
@property
def LstValue(self):
return self._LstValue
@LstValue.setter
def LstValue(self, lst_value):
self._LstValue = ObservableCollection[System.String](lst_value)
# Raise the PropertyChanged event with the name of the changed property
self.RaisePropertyChanged("LstValue")
class DataModel(XamlLoader):
"""
class to get/set Data
"""
def __init__(self, xaml):
super(DataModel, self).__init__(xaml)
#
used_DoorsTypeIds = set(list(FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors)
.WhereElementIsNotElementType()
.Select(lambda x : x.GetTypeId())))
#
self.Net_dict_DType = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors)
.WhereElementIsElementType()
.Select(lambda x : KeyValuePair[System.String, DB.Element](x.FamilyName +":"+ Element.Name.GetValue(x), x))
.ToDictionary(lambda kvp: kvp.Key, lambda kvp: kvp.Value)
#
# Create an ObservableCollection of MyDataViewModel objects
self.data = ObservableCollection[MyDataViewModel]()
for doorTypeId in used_DoorsTypeIds:
doorType = doc.GetElement(doorTypeId)
en = MyDataViewModel()
en.Name = Element.Name.GetValue(doorType)
en.FamilyName = doorType.FamilyName
# set list for combobox
en.LstValue = self.Net_dict_DType.Keys
self.data.Add(en)
# set wpf grid DataContext
self.ui.DataContext = self.data
xaml = IN[0]
objData = DataModel(xaml)
objData.ui.ShowDialog()
dict_DType = objData.Net_dict_DType
OUT = [[x.SelectValue, dict_DType[x.SelectValue] if dict_DType.ContainsKey(x.SelectValue) else None] for x in objData.data]
- Un autre exemple un peu plus simple
import clr
import sys
import System
from System.Collections.Generic import List
from System.Collections.ObjectModel import ObservableCollection
from System.Threading import Thread, ThreadStart, ApartmentState
sys.path.append(r'C:\Program Files (x86)\IronPython 2.7\Lib')
sys.path.append(r'C:\Program Files (x86)\IronPython 2.7\DLLs')
try:
clr.AddReference("IronPython.Wpf")
clr.AddReference('System.Core')
clr.AddReference('System.Xml')
clr.AddReference('PresentationCore')
clr.AddReference('PresentationFramework')
clr.AddReferenceByPartialName("WindowsBase")
except IOError:
raise
from System.IO import StringReader
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
try:
import wpf
except ImportError:
raise
class XamlLoader(Window):
LAYOUT = '''
<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"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Height="600"
Width="600"
ResizeMode="NoResize"
Title="A"
WindowStartupLocation="CenterScreen"
Topmost="True"
SizeToContent="Width">
<Grid Margin="10,0,10,10">
<Label x:Name="selection_label" Content="Select Item" HorizontalAlignment="Left" Height="30"
VerticalAlignment="Top"/>
<Button x:Name="button_select" Content="Select" HorizontalAlignment="Center" Height="26" Margin="0,63,0,0" VerticalAlignment="Bottom" Width="200" Click="ButtonClick"/>
<DataGrid x:Name="dataGrid"
AutoGenerateColumns="False"
Margin="10,30,10,30"
BorderThickness="1"
RowHeaderWidth="0"
CanUserSortColumns="True"
CanUserResizeColumns="False"
VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Key" Binding="{Binding Key}" IsReadOnly="True" Width="250"/>
<DataGridTemplateColumn Header="Param">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding LstValue}" SelectedItem="{Binding SelectValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="200"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>'''
def __init__(self, dataCollection):
self.ui = wpf.LoadComponent(self, StringReader(XamlLoader.LAYOUT))
self.ui.Title = "Select Types"
self.data = dataCollection
self.ui.DataContext = self.data
def ButtonClick(self, sender, e):
self.DialogResult = True
self.Close()
class ViewModelBase(INotifyPropertyChanged):
def __init__(self):
self.propertyChangedHandlers = []
# Define a method to raise the PropertyChanged event
def RaisePropertyChanged(self, propertyName):
# Create a PropertyChangedEventArgs object with the name of the changed property
args = PropertyChangedEventArgs(propertyName)
for handler in self.propertyChangedHandlers:
# Invoke each of the registered property changed handlers with the ViewModelBase instance and the event arguments
handler(self, args)
# Define a method to add a property changed handler
def add_PropertyChanged(self, handler):
self.propertyChangedHandlers.append(handler)
# Define a method to remove a property changed handler
def remove_PropertyChanged(self, handler):
self.propertyChangedHandlers.remove(handler)
class MyDataViewModel(ViewModelBase):
def __init__(self):
ViewModelBase.__init__(self)
self._Key = None
# define a variable to store the selected value from conbobox (Binding)
self._SelectValue = ""
self._LstValue = ObservableCollection[System.String]()
# Define all getters and setters properties and Raise the PropertyChanged event with the name of the changed property (setter)
@property
def Key(self):
return self._Key
@Key.setter
def Key(self, value):
self._Key = value
self.RaisePropertyChanged("Key")
@property
def SelectValue(self):
return self._SelectValue
@SelectValue.setter
def SelectValue(self, value):
self._SelectValue = value
self.RaisePropertyChanged("SelectValue")
@property
def LstValue(self):
return self._LstValue
@LstValue.setter
def LstValue(self, lst_value):
self._LstValue = ObservableCollection[System.String](lst_value)
self.RaisePropertyChanged("LstValue")
def appThread():
appThread.form = None
keys = ["Type1","Type2","Type3","Type4"]
lst_Mtrl = ["a","b","c","d","e","f"]
#
# Create an ObservableCollection of MyDataViewModel objects
data = ObservableCollection[MyDataViewModel]()
for key_ in keys:
en = MyDataViewModel()
en.Key = key_
en.LstValue = lst_Mtrl
data.Add(en)
xaml = XamlLoader(data)
# update function attribute
appThread.form = xaml
xaml.ui.ShowDialog()
if System.Diagnostics.Process.GetCurrentProcess().ProcessName == "DynamoSandbox":
Application.Current.Dispatcher.Invoke(appThread)
else:
appThread()
OUT = [[data.Key, data.SelectValue] for data in appThread.form.data]
- un autre bel exemple d'implémentation MVVM pyRevit-ViewRange
0 commentaires:
Enregistrer un commentaire