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
4 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.

@c.poupin

I refactored my code to match your approach and added some checks to avoid duplicating ColumnType instances. Then, to finalize my tool, I added a Delete button to allow deleting incorrectly typed ColumnType instances. For that, I chose to implement a RelayCommand, but I’m struggling to access the SelectedType from LstTypes in Columns_VM?

RelayCommand
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.Windows.Input import ICommand, CommandManager
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 RelayCommand(ICommand):
    def __init__(self, execute, can_execute=None):
        self._execute = execute
        self._can_execute = can_execute
        self._can_execute_changed_handlers = []

    def CanExecute(self, parameter):
        if self._can_execute is None:
            return True
        return self._can_execute(parameter)

    def Execute(self, parameter):
        self._execute(parameter)

    def add_CanExecuteChanged(self, handler):
        CommandManager.RequerySuggested += handler
        self._can_execute_changed_handlers.append(handler)

    def remove_CanExecuteChanged(self, handler):
        CommandManager.RequerySuggested -= handler
        self._can_execute_changed_handlers.remove(handler)

class SymbolD(ViewModelBase):
    def __init__(self, name):
        super(SymbolD, self).__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__()
        self._level = level
        self._symbols = symbols
        self._types = ObservableCollection[ColumnType]()
        self.DeleteCommand = RelayCommand(self.DeleteType, self.CanDeleteType)

        # Initialize with default type
        default_type = ColumnType(0, 0)
        self._types.Add(default_type)

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

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

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

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

    def DeleteType(self, param=None):
        if param and param in self._types:
            self._types.Remove(param)
            self.notify_property_changed("LstTypes")

    def CanDeleteType(self, param=None):
        return param is not None and param in self._types

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="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"/>
	            <Button Content="Delete" Margin="10,0" Padding="10,2"
	                    Command="{Binding DeleteCommand}"/>
	        </StackPanel>
	
	        <!-- Main Data Grid -->
	        <TabControl x:Name="tabLevels" Grid.Row="1" ItemsSource="{Binding}">
	            <TabControl.ItemTemplate>
	                <DataTemplate>
	                    <TextBlock Text="{Binding Level.Name}"/>
	                </DataTemplate>
	            </TabControl.ItemTemplate>
	            <TabControl.ContentTemplate>
	                <DataTemplate>
	                    <DataGrid ItemsSource="{Binding Symbols}"
	                                  AutoGenerateColumns="False"
	                                  x:Name="gridSymbols">
	                        <DataGrid.Columns>
	                            <DataGridTextColumn Header="Intersection"
	                                                   Binding="{Binding Path=Name}"
	                                                   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, UpdateSourceTrigger=PropertyChanged}"
	                                                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.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

    def btnAdd_Click(self, sender, e):

        try:
            d1 = float(self.dim1_value.Text)
            d2 = float(self.dim2_value.Text)
            new_type = ColumnType(d1, d2)
            type_exists = False
            
            for lvl_vm in self.DataLevels:
                if any(existing_type == new_type for existing_type in lvl_vm.LstTypes):
                    type_exists = True
                    break

            if type_exists:
                MessageBox.Show("This column type already exists, check your list")
            else:
                # Only add if it doesn't exist in any level
                for lvl_vm in self.DataLevels:
                    lvl_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):

        for item in self.DataLevels:
            print("Choosed columns for {} are : {}".format(
                item.Level.Name, [sym.SelectedType.Name for sym in item.Symbols]))

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


column = MyWindow()
column.ShowDialog()

OUT = 0

Why don’t you treat it in the MyWindow class as the btnAdd_Click method, it would be simpler in the current context

1 Like

@c.poupin

You mean using an Event Handler instead of RelayCommand? (I can’t use RelayCommand in this case)?

Thanks.

yes

1 Like

@c.poupin

If possible, and if I’m not bothering you, I would like to insist on implementing RelayCommand in this particular case and mastering its use in similar situations.

Thanks.

  • you generate and attanch an ICommand for each level when there’s only one button, so it can’t work properly

  • SelectedType is an instance of SymbolD and not ColumnType

  • Why treat the Add button with an eventhandler and the Remove button with an Icommand?

1 Like

Hi @c.poupin

I tried to address the issues based on your observations. However, I realized how difficult it is for me to implement ICommand properly in this context. Eventually, I abandoned that option for the moment and switched to the classic event handler for deletion…which turned out to be more complicated than I initially thought!

After several issues and tests, I ended up with the current implementation in my code, which still needs improvements!

Could you please test my updated code and let me know if you see any possible improvements, especially in the btnDelete_Click event?

Delete_event
import clr
import sys
import itertools

sys.path.append(r"C:\Users\nono\OneDrive\Desktop\Columns\lib")

# Revit API
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
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 SymbolD(ViewModelBase):
    def __init__(self, name):
        super(SymbolD, self).__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__()
        self._level = level
        self._symbols = symbols
        self._types = ObservableCollection[ColumnType]()
        
        self.default_type = ColumnType(0, 0)
        self._types.Add(self.default_type)
        
        for symbol in self._symbols:
            symbol.SelectedType = self.default_type
            
    @property
    def Level(self):
        return self._level

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

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

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


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="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>

	        <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"/>
	            <Button Content="Delete" Click="btnDelete_Click" Margin="10,0" Padding="10,2"/>
	        </StackPanel>

	        <TabControl x:Name="tabLevels" Grid.Row="1" ItemsSource="{Binding}">
	            <TabControl.ItemTemplate>
	                <DataTemplate>
	                    <TextBlock Text="{Binding Level.Name}"/>
	                </DataTemplate>
	            </TabControl.ItemTemplate>
	            <TabControl.ContentTemplate>
	                <DataTemplate>
	                    <DataGrid ItemsSource="{Binding Symbols}"
	                              AutoGenerateColumns="False">
	                        <DataGrid.Columns>
	                            <DataGridTextColumn Header="Intersection"
	                                                Binding="{Binding Path=Name}"
	                                                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, UpdateSourceTrigger=PropertyChanged}"
	                                            DisplayMemberPath="Name"/>
	                                    </DataTemplate>
	                                </DataGridTemplateColumn.CellTemplate>
	                            </DataGridTemplateColumn>
	                        </DataGrid.Columns>
	                    </DataGrid>
	                </DataTemplate>
	            </TabControl.ContentTemplate>
	        </TabControl>

	        <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.DataLevels = ObservableCollection[Columns_VM]()
        self.default_type = ColumnType(0, 0)

        levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels).WhereElementIsNotElementType().ToElements()
        for level in sorted(levels, key=lambda l: l.Elevation):
            level_symbols = get_grids_symbols()
            lvl_vm = Columns_VM(level, level_symbols)
            self.DataLevels.Add(lvl_vm)

        self.DataContext = 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)
            type_exists = False
            
            for lvl_vm in self.DataLevels:
                if any(existing_type == new_type for existing_type in lvl_vm.LstTypes):
                    type_exists = True
                    break

            if type_exists:
                MessageBox.Show("This column type already exists, check your list")
            else:
                for lvl_vm in self.DataLevels:
                    lvl_vm.AddType(new_type)
                self.dim1_value.Clear()
                self.dim2_value.Clear()

        except ValueError:
            MessageBox.Show("Please enter valid dimensions")

    def btnDelete_Click(self, sender, e):
        try:
            current_level = self.tabLevels.SelectedItem
            if current_level is None:
                return

            types_to_delete = set()
            for symbol in current_level.Symbols:
                if (hasattr(symbol, 'SelectedType') and 
                   symbol.SelectedType is not None and
                   hasattr(symbol.SelectedType, 'Name') and
                   symbol.SelectedType != ColumnType(0, 0)):
                    types_to_delete.add(symbol.SelectedType)

            if not types_to_delete:
                return

            default_type = ColumnType(0, 0)
            for level_vm in self.DataLevels:
                for symbol in level_vm.Symbols:
                    if (hasattr(symbol, 'SelectedType') and
                        symbol.SelectedType in types_to_delete):
                        symbol.SelectedType = default_type
                
                types_to_remove = [t for t in level_vm.LstTypes if t in types_to_delete]
                for t in types_to_remove:
                    level_vm.LstTypes.Remove(t)
                
                if types_to_remove:
                    level_vm.notify_property_changed("LstTypes")

        except Exception as ex:
            MessageBox.Show("Error during deletion: {str()}".format(ex))

    def btnApply_Click(self, sender, e):
        for item in self.DataLevels:
            print("Choosed columns for {} are : {}".format(item.Level.Name, [sym.SelectedType.Name for sym in item.Symbols]))

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


column = MyWindow()
column.ShowDialog()

OUT = 0

Thanks.

try this

    def btnDelete_Click(self, sender, e):
        try:
            # get all select Types
            all_symb_usedNames = set()
            for level_vm in self.DataLevels:
                for sym in level_vm.Symbols:
                    all_symb_usedNames.add(sym.SelectedType.Name)
            #
            print(("all_symb_usedNames", all_symb_usedNames))
            for lvl_vm in self.DataLevels:
                types_to_remove = [t for t in lvl_vm.LstTypes if t.Name not in all_symb_usedNames]
                for t in types_to_remove:
                    lvl_vm.LstTypes.Remove(t)
            # current_level = self.tabLevels.SelectedItem
            # if current_level is None:
            #     return
            # 
            # types_to_delete = set()
            # for symbol in current_level.Symbols:
            #     if (hasattr(symbol, 'SelectedType') and 
            #        symbol.SelectedType is not None and
            #        hasattr(symbol.SelectedType, 'Name') and
            #        symbol.SelectedType != ColumnType(0, 0)):
            #         types_to_delete.add(symbol.SelectedType)
            # 
            # if not types_to_delete:
            #     return
            # 
            # default_type = ColumnType(0, 0)
            # for level_vm in self.DataLevels:
            #     for symbol in level_vm.Symbols:
            #         if (hasattr(symbol, 'SelectedType') and
            #             symbol.SelectedType in types_to_delete):
            #             symbol.SelectedType = default_type
            #     
            #     types_to_remove = [t for t in level_vm.LstTypes if t in types_to_delete]
            #     for t in types_to_remove:
            #         level_vm.LstTypes.Remove(t)
            #     
            #     if types_to_remove:
            #         level_vm.notify_property_changed("LstTypes")
            # 
        except Exception as ex:
            MessageBox.Show("Error during deletion: {str()}".format(ex))
1 Like

For Information.

For those working (or study) with PythonNet3, here’s the equivalent version with this Python engine (PythonNet3 + WPF + MVVM)

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("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 itertools
import traceback

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

    @clr.clrproperty(System.Double)
    def H(self):
        return self._dim[0]

    @clr.clrproperty(System.Double)
    def W(self):
        return self._dim[1]

    @clr.clrproperty(System.String)
    def Name(self):
        if 0 == self.W == self.H:
            return System.String(".")
        else:
            return System.String("{} x {} cm".format(self.W, self.H))
            
            
class SymbolD(INotifyPropertyChanged):
    __namespace__ = "SymbolD_tEfYX0DHEq"
    def __init__(self, name):
        super().__init__()
        self.name = name
        self._selected_type = ColumnType(0, 0)
        self._property_changed_handlers = []

    @clr.clrproperty(System.String)
    def Name(self):
        return self.name
        
    def get_SelectedType(self):
        return self._selected_type
    def set_SelectedType(self, value):
        self._selected_type = value
        self.OnPropertyChanged('SelectedType')
    SelectedType = clr.clrproperty(System.Object, get_SelectedType, set_SelectedType)
    
    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 Columns_VM(INotifyPropertyChanged):
    __namespace__ = "Columns_VM_tEfYX0DHEs"
    def __init__(self, level, symbols):
        super().__init__()
        self._level = level
        self._symbols = symbols
        self._types = ObservableCollection[ColumnType]()
        
        default_type = ColumnType(0, 0)
        self._types.Add(default_type)
        self._property_changed_handlers = []

    @clr.clrproperty(DB.Level)
    def Level(self):
        return self._level

    @clr.clrproperty(ObservableCollection[SymbolD])
    def Symbols(self):
        return self._symbols

    @clr.clrproperty(ObservableCollection[ColumnType])
    def LstTypes(self):
        return self._types

    def AddType(self, column_type):
        self._types.Add(column_type)
        self.OnPropertyChanged('LstTypes')

    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)

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 x:Name="buttonAdd" Content="Add Type" 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 x:Name="buttonApply" Content="Apply" Width="80" Margin="0,0,10,0"/>
            <Button x:Name="buttonClose" Content="Close" Width="80"/>
        </StackPanel>
        </Grid>
    </Window>
    '''

    def __new__(cls, *args):
      reader = System.Xml.XmlReader.Create(System.IO.StringReader(MyWindow.xaml_str))
      window = System.Windows.Markup.XamlReader.Load(reader)
      window.__class__ = cls
      return window
      
    def __init__(self):
        super().__init__()
        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 = []
        self.InitializeComponent()
        
    def InitializeComponent(self):
        #
        try:
            self.dim1_value = LogicalTreeHelper.FindLogicalNode(self, "dim1_value")
            self.dim2_value = LogicalTreeHelper.FindLogicalNode(self, "dim2_value")
            self.tab_control = LogicalTreeHelper.FindLogicalNode(self, "tab_control")
            
            self.buttonApply = LogicalTreeHelper.FindLogicalNode(self, "buttonApply")
            self.buttonApply.Click += self.btnApply_Click
            #
            self.buttonClose = LogicalTreeHelper.FindLogicalNode(self, "buttonClose")
            self.buttonClose.Click += self.btnClose_Click
            #
            self.buttonAdd = LogicalTreeHelper.FindLogicalNode(self, "buttonAdd")
            self.buttonAdd.Click += self.btnAdd_Click

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

    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)
            
            self.dim1_value.Clear()
            self.dim2_value.Clear()
            # refresh the UI on all Tabs because we have add a Type in each ComboBox
            self.tab_control.Items.Refresh()

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

    def btnApply_Click(self, sender, e):
        try:
            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()
            
        except Exception as ex:
            print(traceback.format_exc())

    def btnClose_Click(self, sender, e):
        try:
            self.Close()
        except Exception as ex:
            print(traceback.format_exc())

column = MyWindow()
column.ShowDialog()

OUT = column.data_out
1 Like

I tried your code, but there’s an inversion issue I can’t fix. as you can see in the image below, the type to delete is kept, while the types to keep are deleted !?

Thanks.

Currently the code deletes all unused types, as I understand it

if you want to delete a specific type, I personally won’t do it from the Datagrid.

1 Like