Event Handlers to change selection from Combobox

Hi All, @c.poupin

I’m implementing a UI using WPF to create grids in Revit. To allow users to switch between numeric values (‘1, 2, 3, …’) and alphabetic values (‘A, B, C, …’) for grid names, I used a ComboBox, as shown below.

However, I’m struggling to set up event handlers to manage the selection for each grid orientation (X and Y) and to prevent the user from selecting the same values for both X and Y.

Grid script + xaml layout
import clr    
import sys
import System

clr.AddReference("IronPython.Wpf")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")

from System.IO import StringReader
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
import System.Windows.Controls 
from System.Windows.Controls import StackPanel, DockPanel, TextBlock, TextBox, Button, Separator
import wpf

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument



class Grids(Window):
    LAYOUT = '''
        <Window 
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Grids"
            Height="Auto"
            Width="300"
            SizeToContent ="Height" 
            ResizeMode="NoResize"
            WindowStartupLocation="CenterScreen">
            <StackPanel Margin="10">
                <!--Input LX-->
                <StackPanel Margin="0,0,0,10">
                    <TextBlock Text ="Longueur Total suivant X (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
                    <DockPanel Width="200" HorizontalAlignment="Left" >
                        <TextBlock Text ="   Lx =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
                        <TextBox x:Name="input_Lx" Width="75" Margin="0,0,5,0" />
                        <TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
                    </DockPanel>
                </StackPanel>
                
                <!--Input LY-->
                <StackPanel Margin="0,0,0,10">
                    <TextBlock Text ="Longueur Total suivant Y (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
                    <DockPanel Width="200" HorizontalAlignment="Left" >
                        <TextBlock Text ="   Ly =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
                        <TextBox x:Name="input_Ly" Width="75" Margin="0,0,5,0" />
                        <TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
                    </DockPanel>
                </StackPanel>
                
                <!--Grid Extension -->
                <StackPanel Margin="0,0,0,10">
                    <TextBlock Text ="Extension des Axes X, Y (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
                    <DockPanel Width="200" HorizontalAlignment="Left" >
                        <TextBlock Text ="   e =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
                        <TextBox x:Name="input_extension" Width="75" Margin="0,0,5,0" />
                        <TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
                    </DockPanel>
                </StackPanel>
                
                <!--Spacing along X axis -->
                <StackPanel Margin="0,0,0,10">
                    <TextBlock Text ="Espacement suivant X (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
                    <DockPanel Width="200" HorizontalAlignment="Left" >
                        <TextBlock Text ="   dx =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
                        <TextBox x:Name="input_dx" Width="75" Margin="0,0,5,0" />
                        <TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
                    </DockPanel>
                </StackPanel>
                
                <!--Spacing along Y axis -->
                <StackPanel Margin="0,0,0,10">
                    <TextBlock Text ="Espacement suivant Y (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
                    <DockPanel Width="200" HorizontalAlignment="Left" >
                        <TextBlock Text ="   dy =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
                        <TextBox x:Name="input_dy" Width="75" Margin="0,0,5,0" />
                        <TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
                    </DockPanel>
                </StackPanel>
                
                <!--bubbles symbol -->
                <StackPanel>
                    <TextBlock Text="Choix de symbols pour Lx, Ly:" FontWeight="Bold"/>
                    <!-- Symbol Type for Lx -->
                    <DockPanel Margin="0,0,0,10">
                        <TextBlock Text="Type de symbole Lx:" Width="120"/>
                        <ComboBox x:Name="dropdown_symbol_x" Width="150" HorizontalAlignment="Right" >
                            <ComboBoxItem Content="1 2 3 ..." />
                            <ComboBoxItem Content="A B C ..." />
                        </ComboBox>
                    </DockPanel>
                
                    <!-- Symbol Type for Ly -->
                    <DockPanel Margin="0,0,0,10">
                        <TextBlock Text="Type de symbole Ly:" Width="120"/>
                        <ComboBox x:Name="dropdown_symbol_y" Width="150" HorizontalAlignment="Right" >
                            <ComboBoxItem Content="1 2 3 ..." />
                            <ComboBoxItem Content="A B C ..." />
                        </ComboBox>
                    </DockPanel>
                
                </StackPanel>
                
                <!--Separator, OK and Cancel Button-->
                <Separator Margin="0,0,0,10"/>
                <DockPanel HorizontalAlignment="Center">
                    <Button Content="OK" Width="75" Height="25" Margin="0,0,10,0" Click="OK_Clicked" />
                    <Button Content="Cancel" Width="75" Height="25" Click="Cancel_Clicked" />
                </DockPanel>            
            </StackPanel>
        </Window>'''
    def __init__(self):
        wpf.LoadComponent(self, StringReader(Grids.LAYOUT))
        # Symbol sets
        self.all_symbols_numeric = ['1', '2', '3', '4', '5']  # Numeric
        self.all_symbols_alpha = ['A', 'B', 'C', 'D', 'E']  # Alphabetic
        """I'm struggling with how to handle the event triggered 
by a user selection to correctly populate the symbols Sx and Sy in self.data 
while preventing the user from selecting the same symbols for both Sx and Sy"""
        
        self.selected_symbol_x = None
        self.selected_symbol_y = None
        self.data = {}
        self.ShowDialog()

    def _collect_input_data(self):
        """Collect and convert input data from the UI"""
        try:
            self.data = {
                "Lx": _input_to_meters(self.input_Lx.Text),
                "Ly": _input_to_meters(self.input_Ly.Text),
                "e": _input_to_meters(self.input_extension.Text),
                "dx": _input_to_meters(self.input_dx.Text),
                "dy": _input_to_meters(self.input_dy.Text),
                "Sx": [self.select_symbol_x.SelectedItem],
                "Sy": [self.select_symbol_y.SelectedItem]
            }
        except ValueError:
            TaskDialog.Show('error', 'Invalid input detected')
                

    def OK_Clicked(self, sender, e):
        self._collect_input_data()
        # self.data
        if not self.data:
            return
        self.Close()

    def Cancel_Clicked(self, sender, e):
        self.Close()
def _input_to_meters(value):
    """Convert a textual value to meters."""
    return UnitUtils.ConvertToInternalUnits(float(value), UnitTypeId.Meters)



UI = Grids()
OUT = UI

Thanks.

hi, with 2 radio buttons and an inverted if condition between them can be
it’s a track

Sincerely
christian.stan

2 Likes

@christian.stan

I dont want to use radio button…As shown in the image above, I want to retain the content describing numeric and alphabetic symbols as they are in the dropdown, while using the actual symbol values in the code-behind

Thanks.

Hi,

why not limit yourself to a single combox (X axis “Lx”) and automatically determine by deduction the value for the Y axis (“Ly”)

In addition you can show in an TextBox the value for “Ly”

otherwise to answer the question strictly I would consider working on the MVVM pattern rather than events

2 Likes

here an example with MVVM

code (IronPython3)

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

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

clr.AddReference("IronPython.Wpf")
import wpf

class ViewModel(INotifyPropertyChanged):
    def __init__(self, lstItems):
        super().__init__()
        self._lstItems = ObservableCollection[System.String](lstItems)
        try:
            self.PropertyChanged = None
        except:
            pass
        self._txtValue = ""
        self._itemA = self._lstItems[0] if lstItems else None
        self._itemB = self._lstItems[1] if len(lstItems) > 1 else None
        self._property_changed_handlers = []
        
    # define getter and setter
    
    @property
    def Items(self):
        return self._lstItems
    @Items.setter
    def Items(self, value):
        self._lstItems = value
        self.OnPropertyChanged("Items")
    
    @property
    def TxtValue(self):
        return self._txtValue
    @TxtValue.setter
    def TxtValue(self, value):
        self._txtValue = value
        self.OnPropertyChanged("TxtValue")
        
    @property
    def ItemA(self):
        return self._itemA
    @ItemA.setter
    def ItemA(self, value):
        if value == self._itemB: 
            self.ItemB = None
        self._itemA = value
        self.OnPropertyChanged("ItemA")
        
    @property
    def ItemB(self):
        return self._itemB
    @ItemB.setter
    def ItemB(self, value):
        if value == self._itemA :
            self.ItemA = None
        self._itemB = value
        self.OnPropertyChanged("ItemB")
    
    def OnPropertyChanged(self, property_name):
        event_args = PropertyChangedEventArgs(property_name)
        for handler in self._property_changed_handlers:
            handler(self, event_args)

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

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

class MainForm(Window):
    string_xaml = '''
        <Window 
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 Title="IronPython WPF Form" Height="200" Width="300">
                <Grid Margin="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
        
                    <!-- TextBox -->
                    <Label Grid.Row="0" Grid.Column="0" Content="Enter Text:" VerticalAlignment="Center"/>
                    <TextBox Grid.Row="0" Grid.Column="1" 
                        Name="InputTextBox" Margin="5" VerticalAlignment="Center"
                        Text="{Binding TxtValue, UpdateSourceTrigger=PropertyChanged}" />
        
                    <!-- ComboBox 1 -->
                    <Label Grid.Row="1" Grid.Column="0" Content="Select Option 1:" VerticalAlignment="Center"/>
                    <ComboBox Grid.Row="1" Grid.Column="1" 
                        Name="ComboBox1" Margin="5" VerticalAlignment="Center"
                        ItemsSource="{Binding Items}"
                        SelectedItem="{Binding ItemA, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        
                    <!-- ComboBox 2 -->
                    <Label Grid.Row="2" Grid.Column="0" Content="Select Option 2:" VerticalAlignment="Center"/>
                    <ComboBox Grid.Row="2" Grid.Column="1" 
                        Name="ComboBox2" Margin="5" VerticalAlignment="Center"
                        ItemsSource="{Binding Items}"
                        SelectedItem="{Binding ItemB, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        
                    <!-- Submit Button -->
                    <Button Grid.Row="3" Grid.ColumnSpan="2" Content="Submit" 
                            Name="SubmitButton" Margin="5" Width="80" Height="30" 
                            VerticalAlignment="Bottom" HorizontalAlignment="Center" 
                            Click="ButtonClick"/>
                </Grid>
        </Window>'''
  
    def __init__(self, lstvalue):
        super().__init__()
        self.lstvalue = lstvalue
        wpf.LoadComponent(self, StringReader(MainForm.string_xaml))
        # set DataContext
        self.vm = ViewModel(lstvalue)
        self.DataContext = self.vm
        
    def ButtonClick(self, sender, e):
        try:
            self.Close()
        except Exception as ex:
            print(traceback.format_exc())
            
    
lstValues = IN[0]
out = []

my_window = MainForm(lstValues)
my_window.ShowDialog()
out.append(my_window.vm.TxtValue)
out.append(my_window.vm.ItemA)
out.append(my_window.vm.ItemB)

OUT = out
4 Likes