Un exemple de sélection d'éléments au travers d'une interface de type 'DataGrid'
#DynamoBIM #Revit #Python #DataTable #WPF
#AutodeskExpertElite #AutodeskCommunity
if this article is not in your language, use the Google Translate
widget ⬈ (bottom of page for Mobile version ⬇)
- Introduction
Dans cet article, nous allons explorer comment créer une interface
utilisateur en WPF (Windows Presentation Foundation) pour sélectionner des
éléments à l'aide d'un script Python dans Dynamo. Cette approche est
particulièrement utile lorsque vous travaillez avec des données complexes
et que vous souhaitez offrir à l'utilisateur une interface graphique
fluide pour interagir avec ces données.
L'exemple ci-dessous utilise PythonNet ou IronPython pour intégrer le WPF
dans Dynamo, permettant la sélection d'éléments au travers d'une
grille.
- Entrées du nœud Python
1. Liste des noms des colonnes : Il s'agit
d'une liste qui représente les en-têtes des colonnes de la grille WPF.
2. Données d'entrée : Ce sont des données sous
forme de liste de listes, au format
List[List[objet]]
. Chaque
sous-liste représente une ligne de données, avec la dernière colonne
contenant la valeur de sortie.
- Exemple de données :
[
[keyA, keyB, keyC, outValue],
[keyA, keyB, keyC, outValue],
[keyA, keyB, keyC, outValue]
]
3. Nom de la colonne de sortie : Il s'agit du
nom de la colonne qui contiendra les valeurs de sortie sélectionnées par
l'utilisateur.
4. Paramètre de visibilité : Un paramètre
booléen pour cacher ou afficher la colonne de sortie.
- Aperçu de l'interface utilisateur
code Python
__author__ = "Cyril POUPIN"
__license__ = "MIT license"
__version__ = "1.0.2"
import clr
import sys
import System
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
import traceback
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>
<!-- perform Single click checkbox selection in WPF DataGrid like DataGridView.Editmode = EditOnEnter on WinForm -->
<Style TargetType="DataGridCell">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="DataGridCell.IsReadOnly" Value="False" />
<Condition Property="DataGridCell.IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="IsEditing" Value="True" />
<Setter Property="Background" Value="LightGreen" />
</MultiTrigger>
</Style.Triggers>
</Style>
</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"
ItemsSource="{Binding}"
Grid.Column="0" Grid.Row="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="8,3,8,7"
SelectionUnit="Cell"
CanUserAddRows="False">
</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, tableData, outColumnName, hide_out_Column):
super().__init__()
self._tableData = tableData
self._outColumnName = outColumnName
self._hide_out_Column = hide_out_Column
#
xr = XmlReader.Create(StringReader(MainWindow.string_xaml))
self.winLoad = XamlReader.Load(xr)
self.outSelection = []
self.InitializeComponent()
def InitializeComponent(self):
try:
self.Content = self.winLoad.Content
#
self.dataGrid = LogicalTreeHelper.FindLogicalNode(self.winLoad, "dataGrid")
self.dataGrid.SelectedCellsChanged += self.DataGrid_CurrentCellChanged
#
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
#
# set DataContext to Enable Binding with input DataTable
self.dataGrid.DataContext = self._tableData.DefaultView
except Exception as ex:
print(traceback.format_exc())
def DataGrid_CurrentCellChanged(self, sender, e):
currentDataGrid = sender
try:
lst_Selected = [cell.Item["Selection"] for cell in currentDataGrid.SelectedCells if isinstance(cell.Item["Selection"], (bool, System.Boolean))]
print("currentDataGrid.SelectedCells", currentDataGrid.SelectedCells.Count, lst_Selected)
for idx, cell in enumerate(currentDataGrid.SelectedCells):
dataGridRowView =cell.Item
if currentDataGrid.CurrentCell.Column.DisplayIndex == 0 :
# use the last select value and get the reverse
dataGridRowView["Selection"] = not lst_Selected[-1]
# Refresh DataGrid
currentDataGrid.Items.Refresh()
except Exception as ex:
print(traceback.format_exc())
def OnLoad(self, sender, e):
print("UI loaded")
try:
if self._hide_out_Column:
out_column = next((c for c in self.dataGrid.Columns if str(c.Header) == self._outColumnName ), None)
if out_column is not None:
self.dataGrid.Columns.get_Item(out_column.DisplayIndex).MaxWidth = 0
self.dataGrid.Items.Refresh()
except Exception as ex:
print(traceback.format_exc())
def ButtonCancelClick(self, sender, e):
self.outSelection = []
self.winLoad.Close()
def ButtonOKClick(self, sender, e):
try:
# get result from input Data (Binding)
self.outSelection = [row[self._outColumnName] for row in self._tableData.Rows if row["Selection"] == True]
self.winLoad.Close()
except Exception as ex:
print(traceback.format_exc())
def get_DataTableFromList(header_array, data):
dt = DataTable("CustomData")
# Create columns
dt.Columns.Add("Selection", System.Boolean) # add Column selection
for item, value in zip(header_array, data[0]):
try:
type_value = value.GetType()
except:
type_value = type(value)
dt.Columns.Add(item, type_value)
# Add rows
for sublst_values in data:
sublst_values.insert(0, False) # for Column selection
dt.Rows.Add(*sublst_values)
return dt
header_array = IN[0]
lst_data = IN[1]
out_ColumnName = IN[2]
hide_out_Column = IN[3]
dt = get_DataTableFromList(header_array, lst_data)
objWindow = MainWindow(dt, out_ColumnName, hide_out_Column)
objWindow.winLoad.ShowDialog()
OUT = objWindow.outSelection
--
Quelques Explications
- Utilisation du Trigger
Trigger
dans le XAML active l'édition d'une cellule lorsqu'elle n'est
pas en lecture seule et que la souris la survole. Cela permet un
comportement de sélection en un clic, en activant immédiatement la cellule,
et change également le fond en vert clair pour indiquer la sélection.- Sélection multiple
DataGrid_CurrentCellChanged
permet de gérer la multiple
sélection. Elle vérifie l'état de la colonne "Selection" pour chaque cellule
sélectionnée et bascule la valeur entre sélectionnée ou non. La méthode met
ensuite à jour visuellement la grille avec Items.Refresh()
.- La libraire wpf d'IronPython
# load ironpython wpf
clr.AddReference("IronPython.Wpf")
import wpf
# some code
class MyWindow(Window):
string_xaml = # xaml content
def __init__(self):
xr = XmlReader.Create(StringReader(MyWindow.string_xaml))
wpf.LoadComponent(self, xr)
# ↓↓ No Need this ↓↓ because 'self.dataGrid' already exist
# self.dataGrid = LogicalTreeHelper.FindLogicalNode(self.winLoad, "dataGrid")
Ainsi, vous pouvez charger le XAML de manière similaire sans avoir à redéfinir
chaque élément UI.
- WPF dans Python + Dynamo
Contrairement à WinForm, lorsque vous utilisez WPF dans Dynamo, veillez à bien gérer toutes les
erreurs dans votre code Python pour éviter les plantages de l'application
hôte. Assurez-vous de toujours capturer les exceptions avec des blocs
try-except
.
merci pour le code.
RépondreSupprimerIl y avait un petit problème de mise en forme du code, c'est corrigé
Supprimer