Lesson 03.04

How to reuse SelectFromDict Form

Let's make SelectFromDict reusable and also prepare many functions for quickly selecting elements that we might need. It's time to create a library of functions similar to what we used in pyrevit.forms.

Lesson 03.04

How to reuse SelectFromDict Form

Let's make SelectFromDict reusable and also prepare many functions for quickly selecting elements that we might need. It's time to create a library of functions similar to what we used in pyrevit.forms.

Summary

Reuse SelectFromDict Form

In this lesson, we will practice once again on reusing our WPF form. But this time, not only we will make SelectFromDict reusable, we will also prepare many classes so we can simplify selection of various elements.

Let's dive in.

Recap - Reusing Code

Firstly, let's quickly recap what we did in Lesson 3.2.

As you remember we created ef_forms folder inside our lib, and we placed there Alert.py and Alert.xaml files. Then we imported the relevant classes inside of __init__ file so we can easily import it directly from ef_forms

Now, we are going to do the same steps to make SelectFromDict reusable.

Copy SelectFromDict to lib

Now, let's do the same for SelecFromDict.

Firstly, go to the previous lesson and copy python and xaml code to lib folder. I will rename them to:

  • SelectFromDict.py

  • SelectFromDict.xaml

💡Be careful with Refactor function in pyCharm. If you don't pay attention you can rename matching keyword in other places too.


Once you copy files, we can clean up Python file:

  • Remove pyRevit metatags (title, doc…)

  • Remove execution code on the bottom

  • Add doc strings

Let's also add another feature to this class, so it can also take lists as arguments. It's very simple, we just need to check if input is a list and convert it to a dict by making key:value as the same values.

class SelectFromDict(Window):
    def __init__(self, items, label='', title='', btn_name=''):

        # Convert List to Dict
        if isinstance(items, list):
            items = {i:i for i in items}
        elif isinstance(items, dict):
            pass
        else:
            raise Exception('Only List and Dict Allowed.')

    # Other code...
Create a function

Next, let's create a function as a placeholder base, so we can make a few copies in a moment and add more logic. Also we will change the name to ef_select_from_dict to keep naming logic similar for all our custom forms.

def ef_select_from_dict(items, label='', title='', btn_name=''):
    """ Function to select elements from Dict/List
    e.g.
    all_views_vt = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
    dict_views = {v.Name : v for v in all_views}

    selected_views = ef_select_from_dict(dict_views, label='Select Views', title = 'EF Select Views Form', btn_name='Select')"""
    UI = SelectFromDict(items, label=label, title=title, btn_name=btn_name)
    return UI.selected_items

Don't forget to import this new function inside of ef_forms/__init__.py file

from Alert          import ef_alert
from SelectFromDict import ef_select_from_dict

Now you can also test it inside any of your pushbuttons by using the following code:

#⬇️ Import
from ef_forms import ef_select_from_dict

#🟦 Get Views
all_views = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
dict_views = {v.Name : v for v in all_views}

#🔶 User Selection
selected_views = ef_select_from_dict(dict_views, label='Select Views', title = 'EF Select Views Form', btn_name='Select')

Now let's create more reusable functions based on SelectFromDict class.

ef_select_views/view_templates()

Let's create a function so we can easily select Views or ViewTemplates.

Copy the function that we made and get views from the project so you don't have to do that every single time.

We just want to call the function without arguments from now on to select views.

Here is the code:

def ef_select_views():
    """Function to create a Listbox to Select Views."""
    all_views_vt = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
    all_views    = [view for view in all_views_vt if not view.IsTemplate]
    dict_views   = {v.Name : v for v in all_views}

    UI = SelectFromDict(dict_views, label='Select Views', title='EF Select Views Form', btn_name='Select Views')
    return UI.selected_items


def ef_select_view_templates():
    """Function to create a Listbox to Select ViewTemplates."""
    all_views_vt        = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
    all_viewtemplates   = [view for view in all_views_vt if view.IsTemplate]
    dict_view_templates = {v.Name : v for v in all_viewtemplates}

    UI = SelectFromDict(dict_view_templates, label='Select View Templates', title='EF Select ViewTemplate Form', btn_name='Select ViewTemplate')
    return UI.selected_items

Don't forget to update ef_forms/__init__.py

from Alert          import ef_alert
from SelectFromDict import (ef_select_from_dict,
                            ef_select_views,
                            ef_select_view_templates)

And now we can test these functions in any pushbutton:

#⬇️ Import
from ef_forms import ef_select_views, ef_select_view_templates

#2. Select Views/ViewTemplates
sel_views = ef_select_views()
sel_vt    = ef_select_view_templates()

print(sel_views)
print('-'*50)
print(sel_vt)

And here are results:

select_wall_types()

Let's practice a bit more. Let's create a function to choose WallTypes.


def ef_select_wall_types():
    """Function to create a Listbox to Select WallTypes."""
    all_wall_types  = FilteredElementCollector(doc).OfClass(WallType).ToElements()
    dict_wall_types = {Element.Name.GetValue(wall_type) : wall_type for wall_type in all_wall_types}


    UI = SelectFromDict(dict_wall_types, label='Select WallTypes', title='EF Select WallTypes Form', btn_name='Select Types')
    return UI.selected_items

💡Don't forget to update __init__.py file.

And here is how to use it

#⬇️ Import
from ef_forms import ef_select_wall_types

#🔶 Select WallTypes
sel_wall_types = ef_select_wall_types()
print(sel)
select_types_by_cat()

Also, while you are creating your functions, think of how to make them reusable too. For example we just create a function to select form WallTypes.

But we could make it better and allow user to provide any BuiltInCategory to make it more reusable.

Here is the code:


def ef_select_types_by_cat(bic, label='Select Type', title='EF Select Types Form', btn_name='Select Types'):
    """Function to create a Listbox to Select WallTypes."""
    all_el_types  = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsElementType().ToElements()

    dict_el_types = {}
    for typ in all_el_types:
        type_name   = Element.Name.GetValue(typ)
        family_name = typ.FamilyName
        key_name    = '{}_{}'.format(family_name, type_name)

        dict_el_types[key_name] = typ

    # dict_el_types = {Element.Name.GetValue(el_type) : el_type for el_type in all_el_types}


    UI = SelectFromDict(dict_el_types, label=label, title=title, btn_name=btn_name)
    return UI.selected_items

💡Update __init__ imports

And then you can test it

#⬇️ Import
from ef_forms import ef_select_types_by_cat

sel_win_types = ef_select_types_by_cat(BuiltInCategory.OST_Windows, label='Select WindowTypes', btn_name='Select WindowTypes')
print(sel_win_types)

And here is your final form, which is really versatile and can save you a lot of time in the long-run.

Final Code

Now, let me share all my final code snippets so we avoid any confusion.

# -*- coding: utf-8 -*-
# ╦╔╦╗╔═╗╔═╗╦═╗╔╦╗╔═╗
# ║║║║╠═╝║ ║╠╦╝ ║ ╚═╗
# ╩╩ ╩╩  ╚═╝╩╚═ ╩ ╚═╝ IMPORTS
#====================================================================================================
from Autodesk.Revit.DB import *
from pyrevit import forms   # By importing forms you also get references to WPF package! IT'S Very IMPORTANT !!!
import wpf, os, clr         # wpf can be imported only after pyrevit.forms!

# .NET Imports
clr.AddReference("System")
from System.Collections.Generic import List
from System.Windows import Window, Visibility
from System.Windows.Controls import CheckBox, Button, TextBox, ListBoxItem, TextBlock

# ╦  ╦╔═╗╦═╗╦╔═╗╔╗ ╦  ╔═╗╔═╗
# ╚╗╔╝╠═╣╠╦╝║╠═╣╠╩╗║  ║╣ ╚═╗
#  ╚╝ ╩ ╩╩╚═╩╩ ╩╚═╝╩═╝╚═╝╚═╝ VARIABLES
#====================================================================================================
PATH_SCRIPT = os.path.dirname(__file__)
doc     = __revit__.ActiveUIDocument.Document #type: Document
uidoc   = __revit__.ActiveUIDocument
app     = __revit__.Application



# ╔╦╗╔═╗╦╔╗╔  ╔═╗╔═╗╦═╗╔╦╗
# ║║║╠═╣║║║║  ╠╣ ║ ║╠╦╝║║║
# ╩ ╩╩ ╩╩╝╚╝  ╚  ╚═╝╩╚═╩ ╩ MAIN FORM
#====================================================================================================
# Inherit .NET Window for your UI Form Class
class SelectFromDict(Window):
    def __init__(self, items, label='', title='', btn_name=''):
        """Select From Dictionary Form

        e.g.
        all_views = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
        dict_views = {v.Name : v for v in all_views}

        UI = SelectFromDict(dict_views, 'Select Views', 'Select Views Form', 'Select Views.')

        print(UI.selected_items)
        print('-'*100)
        print(UI.selected_names) """


        if isinstance(items, list):
            items = {i:i for i in items}
        elif isinstance(items, dict):
            pass
        else:
            raise Exception('Only List and Dict Allowed.')


        # Connect to .xaml File (in the same folder!)
        path_xaml_file = os.path.join(PATH_SCRIPT, 'SelectFromDict.xaml')
        wpf.LoadComponent(self, path_xaml_file)


        # Change Values in the form
        if label:    self.UI_label.Text = label
        if title:    self.Title = title
        if btn_name: self.UI_btn_run.Content = btn_name

        self.populate_listbox(items)

        # Show Form
        self.ShowDialog()

    # ╔═╗╦═╗╔═╗╔═╗╔═╗╦═╗╔╦╗╦ ╦
    # ╠═╝╠╦╝║ ║╠═╝║╣ ╠╦╝ ║ ╚╦╝
    # ╩  ╩╚═╚═╝╩  ╚═╝╩╚═ ╩  ╩
    @property
    def selected_items(self):
        selected_items = []

        for item in self.UI_listbox.Items:
            checkbox = item.Content
            if checkbox.IsChecked:
                item = checkbox.Tag
                selected_items.append(item)

        return selected_items

    @property
    def selected_names(self):
        selected_names = []
        for item in self.UI_listbox.Items:
            checkbox = item.Content
            if checkbox.IsChecked:
                textblock = checkbox.Content
                item_name = textblock.Text
                selected_names.append(item_name)

        return selected_names


    # ╔╦╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗
    # ║║║║╣  ║ ╠═╣║ ║ ║║
    # ╩ ╩╚═╝ ╩ ╩ ╩╚═╝═╩╝
    def populate_listbox(self, items):

        # Clear ListBox
        self.UI_listbox.Items.Clear()

        for item_name, item in sorted(items.items()):

            # Create TextBlock
            textblock      = TextBlock()
            textblock.Text = item_name

            # Create CheckBox
            check         = CheckBox()
            check.Content = textblock
            check.Tag     = item

            # Create ListBoxItem
            list_item         = ListBoxItem()
            list_item.Content = check

            # Add To ListBox
            self.UI_listbox.Items.Add(list_item)


    # ╔═╗╦  ╦╔═╗╔╗╔╔╦╗╔═╗
    # ║╣ ╚╗╔╝║╣ ║║║ ║ ╚═╗
    # ╚═╝ ╚╝ ╚═╝╝╚╝ ╩ ╚═╝
    def UIe_search_changed(self, sender, e):
        search_input = self.UI_search.Text.lower()
        if search_input:
            search_words = search_input.split()

            for item in self.UI_listbox.Items:
                checkbox  = item.Content
                textblock = checkbox.Content
                item_name = textblock.Text.lower()

                if all(word in item_name for word in search_words):
                    item.Visibility = Visibility.Visible
                else:
                    item.Visibility = Visibility.Collapsed

        else:
            for item in self.UI_listbox.Items:
                item.Visibility = Visibility.Visible

    def listbox_select_all(self, toggle):
        for item in self.UI_listbox.Items:
            if item.Visibility == Visibility.Visible:
                checkbox = item.Content
                checkbox.IsChecked = toggle

    def UIe_btn_all(self, sender, e):
        self.listbox_select_all(True)

    def UIe_btn_none(self, sender, e):
        self.listbox_select_all(False)

    # ╔╗ ╦ ╦╔╦╗╔╦╗╔═╗╔╗╔╔═╗
    # ╠╩╗║ ║ ║  ║ ║ ║║║║╚═╗
    # ╚═╝╚═╝ ╩  ╩ ╚═╝╝╚╝╚═╝ BUTTONS
    #==================================================
    def UIe_btn_run(self, sender, e):
        """Button action: Rename view with given """
        self.Close()






# ╔═╗╦ ╦╔╗╔╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
# ╠╣ ║ ║║║║║   ║ ║║ ║║║║╚═╗
# ╚  ╚═╝╝╚╝╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
def ef_select_from_dict(items, label='', title='', btn_name=''):
    """ Function to select elements from Dict/List
    e.g.
    all_views_vt = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
    dict_views = {v.Name : v for v in all_views}

    selected_views = ef_select_from_dict(dict_views, label='Select Views', title = 'EF Select Views Form', btn_name='Select')"""
    UI = SelectFromDict(items, label=label, title=title, btn_name=btn_name)
    return UI.selected_items


def ef_select_views():
    """Function to create a Listbox to Select Views."""
    all_views_vt = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
    all_views    = [view for view in all_views_vt if not view.IsTemplate]
    dict_views   = {v.Name : v for v in all_views}

    UI = SelectFromDict(dict_views, label='Select Views', title='EF Select Views Form', btn_name='Select Views')
    return UI.selected_items

def ef_select_view_templates():
    """Function to create a Listbox to Select ViewTemplates."""
    all_views_vt        = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToElements()
    all_viewtemplates   = [view for view in all_views_vt if view.IsTemplate]
    dict_view_templates = {v.Name : v for v in all_viewtemplates}

    UI = SelectFromDict(dict_view_templates, label='Select View Templates', title='EF Select ViewTemplate Form', btn_name='Select ViewTemplate')
    return UI.selected_items


def ef_select_wall_types():
    """Function to create a Listbox to Select WallTypes."""
    all_wall_types  = FilteredElementCollector(doc).OfClass(WallType).ToElements()
    dict_wall_types = {Element.Name.GetValue(wall_type) : wall_type for wall_type in all_wall_types}


    UI = SelectFromDict(dict_wall_types, label='Select WallTypes', title='EF Select WallTypes Form', btn_name='Select Types')
    return UI.selected_items


def ef_select_types_by_cat(bic, label='Select Type', title='EF Select Types Form', btn_name='Select Types'):
    """Function to create a Listbox to Select WallTypes."""
    all_el_types  = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsElementType().ToElements()

    dict_el_types = {}
    for typ in all_el_types:
        type_name   = Element.Name.GetValue(typ)
        family_name = typ.FamilyName
        key_name    = '{}_{}'.format(family_name, type_name)

        dict_el_types[key_name] = typ

    # dict_el_types = {Element.Name.GetValue(el_type) : el_type for el_type in all_el_types}


    UI = SelectFromDict(dict_el_types, label=label, title=title, btn_name=btn_name)
    return UI.selected_items

<Window Title="EF-SelectFromDict"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="Auto" Width="300" MinHeight="150"
        WindowStartupLocation="CenterScreen" ResizeMode="CanResizeWithGrip"
        SizeToContent="Height">

    <StackPanel Margin="10">

        <!--Listbox Label-->
        <TextBlock x:Name="UI_label" Text="Select Elements:" FontWeight="Bold" Margin="0,0,0,10"/>

        <!--SearchBox-->
        <DockPanel Margin="0,0,0,10">
            <TextBlock Text="🔎" Margin="0,0,5,0"/>
            <TextBox x:Name="UI_search" TextChanged="UIe_search_changed"/>
        </DockPanel>

        <!--ListBox-->
        <ListBox x:Name="UI_listbox" Height="250" Margin="0,0,0,10">

            <!--Item A-->
            <ListBoxItem>
                <CheckBox>
                    <TextBlock Text="Element_A"/>
                </CheckBox>
            </ListBoxItem>

            <!--Item B-->
            <ListBoxItem>
                <CheckBox>
                    <TextBlock Text="Element B"/>
                </CheckBox>
            </ListBoxItem>

        </ListBox>


        <!--ListBox Buttons-->
        <DockPanel HorizontalAlignment="Center" Margin="0,0,0,10">
            <Button Click="UIe_btn_all" Content="Select All"  Width="75" Margin="0,0,10,0"/>
            <Button Click="UIe_btn_none" Content="Select None" Width="75"/>
        </DockPanel>


        <!--Separator-->
        <Separator Margin="0,0,0,10"/>

        <!--Run Button-->
        <Button x:Name="UI_btn_run" Click="UIe_btn_run" Content="Select" Width="160"/>


    </StackPanel>


</Window>

And here is ef_forms/__init__.py code:

from Alert          import ef_alert
from SelectFromDict import (ef_select_from_dict,
                            ef_select_views,
                            ef_select_view_templates,
                            ef_select_wall_types,
                            ef_select_types_by_cat)
Conclusion

And that's how you can reuse your WPF form and also prepare more functions by using the same form to make selection of different elements much more efficient in your scripts.

Homework

Now it's time to practice.

I want you to create more reusable functions that will be more useful to you. Maybe you work with MEP or Structure or other Architectural elements. Just create a few more functions, test them and of course share with the community.

By sharing you will all see more examples and also get to share your hard work with others. Sharing is Caring after all!

And once you share a few of your own custom functions, you are ready for the next lesson where we will create a FlexForm and cover Behind-Code in process.

Happy Coding!

🙋‍♂️ See you in the next lesson.
- EF

🙋‍♂️ See you in the next lesson.
- EF

Discuss the lesson:

P.S. Sometimes this chat might experience connection issues.
Please be patient or join via Discord app so you can get the most out of this community and get access to even more chats.

Discuss the lesson:

P.S. Sometimes this chat might experience connection issues.
Please be patient or join via Discord app so you can get the most out of this community and get access to even more chats.