Set column type instance for each level using wpf

Hi All, @c.poupin

Referring to my post Set footings Type using WPF - #7 by REDO10, I’m following the same logic to implement a tool that allows assigning a ColumnType instance to each created column at each level.

When I click the Add Type button to add an instance, new instances are add to LstTypes and this later is updated, as shown in the console print below. However, I’m facing an issue in the XAML part, where I’m struggling to correctly bind LstTypes to the ComboBoxes in each level tab within the DataGrid, where the added instances are not displayed in the view.

column

here my code:

Columns.7z (1.6 KB)

columns
import clr  
import sys
sys.path.append(r"C:\Users\nono\OneDrive\Desktop\Columns\lib")
import System
import math
from System import Array
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
from Autodesk.Revit.DB.Structure import StructuralType

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

clr.AddReference("IronPython.Wpf")
clr.AddReference('System.Core')
clr.AddReference('System.Xml')
clr.AddReference('PresentationCore')
clr.AddReference('PresentationFramework')
clr.AddReference('System.Windows.Forms')  # For MessageBox

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
from System.Windows import Window
from System.Windows.Forms import MessageBox

import wpf
import itertools
import traceback

from _mySymbols import ColumnType
from _myMaterials import ConcreteMaterial, get_or_create_concrete_material


class ViewModelBase(INotifyPropertyChanged):
    def __init__(self):
        self._property_changed = None

    def add_PropertyChanged(self, handler):
        self._property_changed = handler

    def remove_PropertyChanged(self, handler):
        self._property_changed = None

    def notify_property_changed(self, property_name):
        if self._property_changed:
            self._property_changed(self, PropertyChangedEventArgs(property_name))
        
class Columns_VM(ViewModelBase):
    def __init__(self, level, symbols):
        super(Columns_VM, self).__init__()
        self._level = level
        self._symbols = ObservableCollection[str](symbols)
        self._types = ObservableCollection[ColumnType]()

        
        default_type = ColumnType(0, 0)
        self._types.Add(default_type)
        self.notify_property_changed('LstTypes')

        self._selected_type = default_type
        self._assignments = {sym: default_type for sym in symbols}

    @property
    def Level(self):
        return self._level

    @property
    def Symbols(self):
        return self._symbols

    @property
    def LstTypes(self):
        return self._types

    @property
    def SelectedType(self):
        return self._selected_type

    @property
    def Assignments(self):
        return self._assignments

    @SelectedType.setter
    def SelectedType(self, value):
        self._selected_type = value
        self.notify_property_changed('SelectedType')

    def AddType(self, column_type):
        if column_type not in self._types:
            self._types.Add(column_type)
            self.notify_property_changed('LstTypes')

    def AssignTypeToSymbol(self, symbol, column_type):
        if symbol in self._assignments:
            self._assignments[symbol] = column_type
            self.notify_property_changed('Assignments')

def _grids_symbols():
    symbols = []
    grids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Grids) \
        .WhereElementIsNotElementType().ToElements()

    for gd_a, gd_b in itertools.combinations(grids, 2):
        symA = gd_a.Name
        symB = gd_b.Name
        if gd_a.Curve.Intersect(gd_b.Curve) == SetComparisonResult.Overlap:
            if symA.isalpha() and symB.isdigit():
                symbol = "{}-{}".format(symA, symB)
                symbols.append(symbol)
            else:
                symbol = "{}-{}".format(symB, symA)
                symbols.append(symbol)
                
    return sorted(symbols)

class MyWindow(Window):
    xaml_str = '''
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Columns"
    Height="Auto" Width="470"
    SizeToContent="Height"
    ResizeMode="NoResize"
    WindowStartupLocation="CenterScreen">

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- Dimension Input -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
            <Label Content="Dimensions (cm):" VerticalAlignment="Center"/>
            <TextBox x:Name="dim1_value" Width="50" Margin="5,0"/>
            <Label Content="x" VerticalAlignment="Center"/>
            <TextBox x:Name="dim2_value" Width="50" Margin="5,0"/>
            <Button Content="Add Type" Click="btnAdd_Click" Margin="10,0" Padding="10,2"/>
        </StackPanel>

        <!-- Level Tabs -->
        <TabControl x:Name="tab_control" Grid.Row="1">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Level.Name}"/>
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <DataGrid ItemsSource="{Binding Symbols}"
                                  AutoGenerateColumns="False"
                                  CanUserAddRows="False">
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="Grid Intersection"
                                                    Binding="{Binding}"
                                                    Width="*"/>
                                <DataGridTemplateColumn Header="Column Type" Width="2*">
                                    <DataGridTemplateColumn.CellTemplate>
                                        <DataTemplate>
                                            <ComboBox
                                                ItemsSource="{Binding LstTypes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                SelectedItem="{Binding Assignments[.], Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                DisplayMemberPath="Name"/>
                                        </DataTemplate>
                                    </DataGridTemplateColumn.CellTemplate>
                                </DataGridTemplateColumn>
                            </DataGrid.Columns>
                        </DataGrid>
                    </Grid>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>

        <!-- Action Buttons -->
        <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Content="Apply" Click="btnApply_Click" Width="80" Margin="0,0,10,0"/>
            <Button Content="Close" Click="btnClose_Click" Width="80"/>
        </StackPanel>
        </Grid>
    </Window>
    '''

    def __init__(self):
        wpf.LoadComponent(self, StringReader(MyWindow.xaml_str))
        self.DataLevels = ObservableCollection[Columns_VM]()
        levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels) \
            .WhereElementIsNotElementType().ToElements()

        for level in sorted(levels, key=lambda l: l.Elevation):
            self.symbols = _grids_symbols()
            vm = Columns_VM(level, self.symbols)
            self.DataLevels.Add(vm)

        self.DataContext = self
        self.tab_control.ItemsSource = self.DataLevels

    def btnAdd_Click(self, sender, e):
        try:
            d1 = float(self.dim1_value.Text)
            d2 = float(self.dim2_value.Text)
            new_type = ColumnType(d1, d2)

            for vm in self.DataLevels:
                vm.AddType(new_type)
                #vm.notify_property_changed('LstTypes')
                
            print("Final LstTypes:", [t.Name for t in self.DataLevels[0].LstTypes])        

            self.dim1_value.Clear()
            self.dim2_value.Clear()

        except ValueError:
            forms.alert("Please enter valid numeric values")

    def btnApply_Click(self, sender, e):
        pass

    def btnClose_Click(self, sender, e):
        self.Close()

column = MyWindow()
column.ShowDialog()

    
OUT = 0

Thanks.

I’m not with my laptop but check this line, maybe it will be necessary to implement the __eq__ or Equals method in your ColumnType() class

@c.poupin

This method already exist in ColumnType class as you can see below

class ColumnType(object):
    def __init__(self, d1, d2):
        self.d1 = d1
        self.d2 = d2
        if d1 > d2:
            self._dim = [d1, d2]
        else:
            self._dim = [d2, d1]

    @property
    def H(self):
        return self._dim[0]

    @property
    def W(self):
        return self._dim[1]

    @property
    def Name(self):
        if 0 == self.W == self.H:
            return "."
        else:
            return "{} x {} cm".format(self.W, self.H)

    def __eq__(self, other):
        return self.H == other.H and self.W == other.W

    def __hash__(self):
        return hash(self.Name)

Thanks.

Hi,

the problem is that the Symbols (Type String) elements don’t have the LstTypes property, so there’s a structural problem in your Model/ViewModel

1 Like

Hi @c.poupin
First of all, sorry for the late feedback. Your reply coincided with the Eid al-Fitr festival, and I was traveling, so I couldn’t use my computer.

I’ve taken note of your remarks, and now I see the source of the problem! I understand that I should introduce a collection that includes both Symbols and SelectedType from Columns_VM to properly manage the relationship between grid symbols and their corresponding column types, and to notify the view of any changes. Then, I should bind each property separately in the XAML layout, however, I’m still struggling with how to implement this?

Thanks.

Hi,

  • Try to access the property of the parent element with
    ItemsSource="{Binding DataContext.LstTypes, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"

  • you will probably also need to create a new SymbolGrid class (inherited from ViewModelBase) with a SelectedType property (getter + setter) .

1 Like

Hi @c.poupin

First of all, sorry for the late feedback!
I was ill for a few days, and it took me some time to understand and implement the RelativeSource markup extension in this encountered case.

I was finally able to display SymbolAssignment in the UI, initializing default_type for each symbol within each TabItem representing the Level property. However, things got more complicated for me when I realized I couldn’t persist the assigned ColumnType instances for a given Level.. the SelectedType values are lost whenever I switch between Level tabs.

updated code
import clr
import sys
import itertools
sys.path.append(r"C:\Users\nono\OneDrive\Desktop\Columns\lib")

# Revit API Imports
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import StructuralType

clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument

# WPF Imports
clr.AddReference("PresentationCore")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Windows.Forms")

from System.Windows.Markup import XamlReader
from System.IO import StringReader
from System.Windows import Window
from System.Windows.Forms import MessageBox
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
from System.Collections.ObjectModel import ObservableCollection
import wpf

from _mySymbols import ColumnType

class ViewModelBase(INotifyPropertyChanged):
    def __init__(self):
        self._property_changed = None

    def add_PropertyChanged(self, handler):
        self._property_changed = handler

    def remove_PropertyChanged(self, handler):
        self._property_changed = None

    def notify_property_changed(self, property_name):
        if self._property_changed:
            self._property_changed(self, PropertyChangedEventArgs(property_name))

class SymbolAssignment(ViewModelBase):
    def __init__(self, symbol, default_type):
        super(SymbolAssignment, self).__init__()
        self._symbol = symbol
        self._selected_type = default_type

    @property
    def Symbol(self):
        return self._symbol

    @property
    def SelectedType(self):
        return self._selected_type

    @SelectedType.setter
    def SelectedType(self, value):
        self._selected_type = value
        self.notify_property_changed("SelectedType")


class Columns_VM(ViewModelBase):
    def __init__(self, level, symbols):
        super(Columns_VM, self).__init__()
        self._level = level
        self._types = ObservableCollection[ColumnType]()
        self._assignments = ObservableCollection[SymbolAssignment]()

        default_type = ColumnType(0, 0)
        self._types.Add(default_type)
        for sym in symbols:
            self._assignments.Add(SymbolAssignment(sym, default_type))

    @property
    def Level(self):
        return self._level

    @property
    def LstTypes(self):
        return self._types

    @property
    def Assignments(self):
        return self._assignments

    def AddType(self, column_type):
        
        if column_type not in self._types:
            self._types.Add(column_type)
            self.notify_property_changed("LstTypes")

class MyWindow(Window):
    xaml_str = '''
    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Column Manager" Width="450" SizeToContent="Height"
            ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
        <Grid Margin="10">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            
            <!-- Input Panel -->
            <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
                <Label Content="Dimensions (cm):" VerticalAlignment="Center"/>
                <TextBox x:Name="dim1_value" Width="50" Margin="5,0"/>
                <Label Content="x" VerticalAlignment="Center"/>
                <TextBox x:Name="dim2_value" Width="50" Margin="5,0"/>
                <Button Content="Add Type" Click="btnAdd_Click" Margin="10,0" Padding="10,2"/>
            </StackPanel>
            
            <!-- Main Data Grid -->
            <TabControl x:Name="tabLevels" Grid.Row="1">
                <TabControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Level.Name}"/>
                    </DataTemplate>
                </TabControl.ItemTemplate>
                <TabControl.ContentTemplate>
                    <DataTemplate>
                        <DataGrid ItemsSource="{Binding Assignments}" 
                                  AutoGenerateColumns="False"
                                  x:Name="gridSymbols">
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="Intersection" 
                                                   Binding="{Binding Symbol}" 
                                                   Width="*"/>
                                <DataGridTemplateColumn Header="Column Type" Width="2*">
                                    <DataGridTemplateColumn.CellTemplate>
                                        <DataTemplate>
                                            <ComboBox 
                                                ItemsSource="{Binding DataContext.LstTypes, UpdateSourceTrigger=PropertyChanged,
                                                            RelativeSource={RelativeSource FindAncestor,
                                                            AncestorType={x:Type DataGrid}}}" 
                                                SelectedItem="{Binding SelectedType, Mode=TwoWay}"
                                                DisplayMemberPath="Name"/>
                                        </DataTemplate>
                                    </DataGridTemplateColumn.CellTemplate>
                                </DataGridTemplateColumn>
                            </DataGrid.Columns>
                        </DataGrid>
                    </DataTemplate>
                </TabControl.ContentTemplate>
            </TabControl>
            
            <!-- Action Buttons -->
            <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
                <Button Content="Apply" Click="btnApply_Click" Width="80" Margin="0,0,10,0"/>
                <Button Content="Close" Click="btnClose_Click" Width="80"/>
            </StackPanel>
        </Grid>
    </Window>
    '''

    def __init__(self):
        wpf.LoadComponent(self, StringReader(MyWindow.xaml_str))
        self.DataContext = self
        self.vm_levels = ObservableCollection[Columns_VM]()
        self._initialize_data()
        self.tabLevels.ItemsSource = self.vm_levels

    def _initialize_data(self):
        
        levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels) \
               .WhereElementIsNotElementType().ToElements()
        
        for level in sorted(levels, key=lambda l: l.Elevation):
            symbols = self._get_grid_intersections()
            self.vm_levels.Add(Columns_VM(level, symbols))

    def _get_grid_intersections(self):
        symbols = []
        grids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Grids) \
            .WhereElementIsNotElementType().ToElements()

        for gd_a, gd_b in itertools.combinations(grids, 2):
            symA = gd_a.Name
            symB = gd_b.Name
            if gd_a.Curve.Intersect(gd_b.Curve) == SetComparisonResult.Overlap:
                if symA.isalpha() and symB.isdigit():
                    symbol = "{}-{}".format(symA, symB)
                    symbols.append(symbol)
                else:
                    symbol = "{}-{}".format(symB, symA)
                    symbols.append(symbol)

        return sorted(symbols)

    def btnAdd_Click(self, sender, e):
        
        try:
            d1 = float(self.dim1_value.Text)
            d2 = float(self.dim2_value.Text)
            new_type = ColumnType(d1, d2)
            
            for vm in self.vm_levels:
                vm.AddType(new_type)
                
            self.dim1_value.Clear()
            self.dim2_value.Clear()
            
        except ValueError:
            MessageBox.Show("Please enter valid dimensions")

    def btnApply_Click(self, sender, e):
        pass

    def btnClose_Click(self, sender, e):
        
        self.Close()


column = MyWindow()
column.ShowDialog()

OUT = 0

Thanks.

Here’s a test using IronPython3 (wpf+MVVM), it probably still needs to be improved.

import clr  
import sys
import System
import math
from System import Array
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
from Autodesk.Revit.DB.Structure import StructuralType

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

clr.AddReference("IronPython.Wpf")
clr.AddReference('System.Core')
clr.AddReference('System.Xml')
clr.AddReference('PresentationCore')
clr.AddReference('PresentationFramework')
clr.AddReference('System.Windows.Forms')  # For MessageBox

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
from System.Windows import Window
from System.Windows.Forms import MessageBox

import wpf
import itertools
import traceback


class ColumnType(System.Object):
    def __init__(self, d1, d2):
        self.d1 = d1
        self.d2 = d2
        if d1 > d2:
            self._dim = [d1, d2]
        else:
            self._dim = [d2, d1]

    @property
    def H(self):
        return self._dim[0]

    @property
    def W(self):
        return self._dim[1]

    @property
    def Name(self):
        if 0 == self.W == self.H:
            return "."
        else:
            return "{} x {} cm".format(self.W, self.H)

    def __eq__(self, other):
        return self.H == other.H and self.W == other.W

    def __hash__(self):
        return hash(self.Name)
        

class ViewModelBase(INotifyPropertyChanged):
    def __init__(self):
        self._property_changed = None

    def add_PropertyChanged(self, handler):
        self._property_changed = handler

    def remove_PropertyChanged(self, handler):
        self._property_changed = None

    def notify_property_changed(self, property_name):
        if self._property_changed:
            self._property_changed(self, PropertyChangedEventArgs(property_name))
            
class SymbolD(ViewModelBase):
    def __init__(self, name):
        super().__init__()
        self.name = name
        self._selected_type = ColumnType(0, 0)

    @property
    def Name(self):
        return self.name
        
    @property
    def SelectedType(self):
        return self._selected_type

    @SelectedType.setter
    def SelectedType(self, value):
        self._selected_type = value
        self.notify_property_changed('SelectedType')

        
class Columns_VM(ViewModelBase):
    def __init__(self, level, symbols):
        #super(Columns_VM, self).__init__()
        super().__init__()
        self._level = level
        self._symbols = symbols
        self._types = ObservableCollection[ColumnType]()
        
        default_type = ColumnType(0, 0)
        self._types.Add(default_type)
        # add a type just for test
        #self._types.Add(ColumnType(5, 5)) 

        #self._selected_type = default_type
        #self._assignments = {sym: default_type for sym in symbols}

    @property
    def Level(self):
        return self._level

    @property
    def Symbols(self):
        return self._symbols

    @property
    def LstTypes(self):
        return self._types

    # @property
    # def SelectedType(self):
    #     return self._selected_type
    # 
    # @property
    # def Assignments(self):
    #     return self._assignments
    # 
    # @SelectedType.setter
    # def SelectedType(self, value):
    #     self._selected_type = value
    #     self.notify_property_changed('SelectedType')

    def AddType(self, column_type):
        if column_type not in self._types:
            self._types.Add(column_type)
            self.notify_property_changed('LstTypes')

    # def AssignTypeToSymbol(self, symbol, column_type):
    #     if symbol in self._assignments:
    #         self._assignments[symbol] = column_type
    #         self.notify_property_changed('Assignments')

def get_grids_symbols():
    symbols = ObservableCollection[SymbolD]()
    grids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Grids) \
        .WhereElementIsNotElementType().ToElements()

    for gd_a, gd_b in itertools.combinations(grids, 2):
        symA = gd_a.Name
        symB = gd_b.Name
        if gd_a.Curve.Intersect(gd_b.Curve) == SetComparisonResult.Overlap:
            if symA.isalpha() and symB.isdigit():
                symbol = "{}-{}".format(symA, symB)
                symbols.Add(SymbolD(symbol))
            else:
                symbol = "{}-{}".format(symB, symA)
                symbols.Add(SymbolD(symbol))
                
    return symbols

class MyWindow(Window):
    xaml_str = '''
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Columns"
    Height="Auto" Width="470"
    SizeToContent="Height"
    ResizeMode="NoResize"
    WindowStartupLocation="CenterScreen">

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- Dimension Input -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
            <Label Content="Dimensions (cm):" VerticalAlignment="Center"/>
            <TextBox x:Name="dim1_value" Width="50" Margin="5,0"/>
            <Label Content="x" VerticalAlignment="Center"/>
            <TextBox x:Name="dim2_value" Width="50" Margin="5,0"/>
            <Button Content="Add Type" Click="btnAdd_Click" Margin="10,0" Padding="10,2"/>
        </StackPanel>

        <!-- Level Tabs -->
        <TabControl x:Name="tab_control" Grid.Row="1" ItemsSource="{Binding}">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Level.Name}"/>
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <DataGrid ItemsSource="{Binding Symbols}"
                                  AutoGenerateColumns="False"
                                  CanUserAddRows="False">
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="Grid Intersection"
                                                    Binding="{Binding Path=Name}"
                                                    Width="*"/>
                                <DataGridTemplateColumn Header="Column Type" Width="2*">
                                    <DataGridTemplateColumn.CellTemplate>
                                        <DataTemplate>
                                            <!-- for Items Binding access the property of the parent element : DataGrid -->
                                            <ComboBox
                                                ItemsSource="{Binding DataContext.LstTypes, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
                                                SelectedItem="{Binding SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                                DisplayMemberPath="Name"/>
                                        </DataTemplate>
                                    </DataGridTemplateColumn.CellTemplate>
                                </DataGridTemplateColumn>
                            </DataGrid.Columns>
                        </DataGrid>
                    </Grid>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>

        <!-- Action Buttons -->
        <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Content="Apply" Click="btnApply_Click" Width="80" Margin="0,0,10,0"/>
            <Button Content="Close" Click="btnClose_Click" Width="80"/>
        </StackPanel>
        </Grid>
    </Window>
    '''

    def __init__(self):
        wpf.LoadComponent(self, StringReader(MyWindow.xaml_str))
        self.DataLevels = ObservableCollection[Columns_VM]()
        levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels) \
            .WhereElementIsNotElementType().ToElements()
        #
        for level in sorted(levels, key=lambda l: l.Elevation):
            # compute symbols per level
            level_symbols = get_grids_symbols()
            lvl_vm = Columns_VM(level, level_symbols)
            self.DataLevels.Add(lvl_vm)

        self.DataContext = self.DataLevels
        self.data_out = []

    def btnAdd_Click(self, sender, e):
        try:
            d1 = float(self.dim1_value.Text)
            d2 = float(self.dim2_value.Text)
            new_type = ColumnType(d1, d2)

            for vm in self.DataLevels:
                vm.AddType(new_type)
                #vm.notify_property_changed('LstTypes')
                
            print("Final LstTypes:", [t.Name for t in self.DataLevels[0].LstTypes])        

            self.dim1_value.Clear()
            self.dim2_value.Clear()

        except Exception as ex:
            print(traceback.format_exc())

    def btnApply_Click(self, sender, e):
        self.data_out = []
        for lvl_vm in self.DataLevels:
            data_grids_symbols = [[s.Name, s.SelectedType.Name, s.SelectedType.H, s.SelectedType.W] for s in lvl_vm.Symbols if not 0 == s.SelectedType.W == s.SelectedType.H]
            self.data_out.append([
                            lvl_vm.Level,
                            data_grids_symbols
                            ])

        self.Close()

    def btnClose_Click(self, sender, e):
        self.Close()

column = MyWindow()
column.ShowDialog()

OUT = column.data_out
3 Likes

You chose another approach without initial inheritance between the SymbolD and Columns_VM classes, where you later collect them in the view under self.DataLevels… it’s a simpler and improved approach!

After comparing your code to mine, I discovered my mistake: I had forgotten to set UpdateSourceTrigger=PropertyChanged in the ComboBox SelectedItem binding.

The issue was fixed by replacing:

SelectedItem="{Binding SelectedType, Mode=TwoWay}"
by
SelectedItem="{Binding SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

Thanks.