Lesson 03.01

Create Alert Form

In this module we will focus on creating reusable forms. And let's start with the most reusable form you will ever create - Alert Form.

Lesson 03.01

Create Alert Form

In this module we will focus on creating reusable forms. And let's start with the most reusable form you will ever create - Alert Form.

Summary

Welcome Back

In this lesson, we'll make a simple alert form, and we’ll also add an option to provide an image, so we can insert a few funny memes inside our WPF forms

Let’s open Visual Studio Code and just begin the lesson with the default code.

<Window Title="EF-First WPF Form"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="Auto" Width="300" MinHeight="100"
        WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
</Window>
Setting Up the Base Form

Let's start by creating the front end of our form and then we will move to python and make it work in pyRevit.

This is a very simple XAML form and you are more than capable doing it on your own already. There will only be a 1 new control - <Image>, and it's not complicated either.

So here is the XAML code.

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

    <!--StackPanel for all Elements-->
    <StackPanel Margin="10">

        <!--Title-->
        <TextBlock x:Name="UI_msg"  
                   Text="Error Message"
                   Margin="0,0,0,10"
                   TextWrapping="Wrap" 
                   FontWeight="Bold" 
                   FontSize="16"/>

        <!--SubMessage-->
        <TextBlock x:Name="UI_submsg"  
                   TextWrapping="Wrap" 
                   Margin="0,0,0,10"/>

        <!--Image-->
        <Image x:Name="UI_img"
               Height="Auto" 
               Margin="0,0,0,10"
               Visibility="Collapsed"/>

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

        <!--Button-->
        <Button Content="Close" 
                Width="100" 
                Margin="0,0,0,10"
                Click="UIe_btn_run"/>

💡 Notice that I've also introduced TextWrapping property so text can take multiple lines.

💡 I've also added x:Name attributes, so we can get these elements inside the code later.

Image Control

Before we move to the python code, let's have a look at <Image> control.

Obviously you would need to provide the path to you image file so it can be displayed in the WPF, and for that we need to use Source property.

Here is an example:

<!--Image-->
<Image x:Name="UI_img"
       Source ="Absolute/Path/Here"
        Height="Auto" 
        Margin="0,0,0,10"
        Visibility="Collapsed"/>

But for now, we will keep Visibility ="Collapsed" because it will be an optional argument, and we will activate it if we need it.

WPF Base

Next, let's prepare the WPF Base Class for this Form.

We will connect our XAML code to the WPF Class and try to display the form in Revit. And once it works we will be able to move to the next steps.

Here is the python code for this step:


# -*- coding: utf-8 -*-
__title__   = "03.01 - Alert"
__doc__     = """Version = 1.0
Date    = 24.10.2024
________________________________________________________________
Description:
Learn how to create Alerts form that includes 
- Error Heading and SubMessage
- Image (optional)
- Button
- ExitScript Functionality

________________________________________________________________
Author: Erik Frits"""

# ╦╔╦╗╔═╗╔═╗╦═╗╔╦╗╔═╗
# ║║║║╠═╝║ ║╠╦╝ ║ ╚═╗
# ╩╩ ╩╩  ╚═╝╩╚═ ╩ ╚═╝ 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, sys    # wpf can be imported only after pyrevit.forms!

# .NET Imports
clr.AddReference("System")
from System.Windows import Window, Visibility
from System import Uri
from System.Windows.Media.Imaging import BitmapImage

# ╦  ╦╔═╗╦═╗╦╔═╗╔╗ ╦  ╔═╗╔═╗
# ╚╗╔╝╠═╣╠╦╝║╠═╣╠╩╗║  ║╣ ╚═╗
#  ╚╝ ╩ ╩╩╚═╩╩ ╩╚═╝╩═╝╚═╝╚═╝ 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 AlertForm(Window):
    def __init__(self):
        # Connect to .xaml File (in the same folder!)
        path_xaml_file = os.path.join(PATH_SCRIPT, 'Alert.xaml')
        wpf.LoadComponent(self, path_xaml_file)

        # Show Form
        self.ShowDialog()

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


# ╦ ╦╔═╗╔═╗  ╔═╗╔═╗╦═╗╔╦╗
# ║ ║╚═╗║╣   ╠╣ ║ ║╠╦╝║║║
# ╚═╝╚═╝╚═╝  ╚  ╚═╝╩╚═╩ ╩
#====================================================================================================

UI = AlertForm()

💡Make sure you have an EventHandler for your submit <Button> Event.

Test & Debug (Height = 'Auto')

Now it's time to test the form with out Base WPF Class.

It should just display the front-end without working functionality. And while it shows up the form you might've noticed that the height of the form is wrong.

It's much taller for no reason, and that's because of Height="Auto" property.

I don't know why it happens, but I've found how to solve this issue. You just need to add another property SizeToContent="Height" and then it will work well.

So make sure to adjust this XAML part:

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

And if you test it once more, now your base form should be shown with the correct height.

Now we just need to configure our input.

Add Alert Arguments

Now let's modify the class so we can provide a few arguments and then add the logic for them.

We will need the following:

  • msg

  • submsg (optional)

  • image (optional)

  • title (optional)

  • exitscript bool (optional)

Then once we get these arguments, we also need to add code to change the values in our WPF form.

Also we will store exitscript value so we can exit the script after we close the form if necessary inside the event handler for the button.

Here is how to do that:

class AlertForm(Window):
    def __init__(self, msg, sub_msg="", image_path="", title='EF Alert Form', exitscript=False):
        # Connect to .xaml File (in the same folder!)
        path_xaml_file = os.path.join(PATH_SCRIPT, 'Alert.xaml')
        wpf.LoadComponent(self, path_xaml_file)

        # Change Properties
        self.Title          = title
        self.UI_msg.Text    = msg

        if sub_msg:
            self.UI_submsg.Text = sub_msg

        if image_path:
            self.UI_img.Source     = BitmapImage(Uri(image_path))
            self.UI_img.Visibility = Visibility.Visible

        self.exitscript = exitscript


        # Show Form
        self.ShowDialog()

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

        if self.exitscript:
            sys.exit()

💡Make sure you import Visibility class from .NET instead of Revit API class.

Test with Image

Alright our class is ready, now we can test our form.

Let's get the absolute path of the image i've prepared and provide it as an arugument as well.

Here is code to test the function:

print('Before')

# Show form to the user

img_name = "meme_bug.jpg"
img_path = os.path.join(PATH_SCRIPT, img_name)
sub_msg = ("This is a relly long message, so we can test wrapping and stuff."
           "This is a relly long message, so we can test wrapping and stuff.")

UI = AlertForm('Error Message 1', sub_msg=sub_msg,
               image_path=img_path,title= 'Alert Message',
               exitscript=True)


print('After')

And here is the final form:

Final Code

Let me put here the final code to avoid any confusion.

XAML:

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

    <!--StackPanel for all Elements-->
    <StackPanel Margin="10">

        <!--Title-->
        <TextBlock x:Name="UI_msg"  
                   Text="Error Message"
                   Margin="0,0,0,10"
                   TextWrapping="Wrap" 
                   FontWeight="Bold" 
                   FontSize="16"/>

        <!--SubMessage-->
        <TextBlock x:Name="UI_submsg"  
                   TextWrapping="Wrap" 
                   Margin="0,0,0,10"/>

        <!--Image-->
        <Image x:Name="UI_img"
               Height="Auto" 
               Margin="0,0,0,10"
               Visibility="Collapsed"/>

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

        <!--Button-->
        <Button Content="Close" 
                Width="100" 
                Margin="0,0,0,10"
                Click="UIe_btn_run"/>

Python:

# -*- coding: utf-8 -*-
__title__   = "03.01 - Alert"
__doc__     = """Version = 1.0
Date    = 24.10.2024
________________________________________________________________
Description:
Learn how to create Alerts form that includes 
- Error Heading and SubMessage
- Image (optional)
- Button
- ExitScript Functionality

________________________________________________________________
Author: Erik Frits"""

# ╦╔╦╗╔═╗╔═╗╦═╗╔╦╗╔═╗
# ║║║║╠═╝║ ║╠╦╝ ║ ╚═╗
# ╩╩ ╩╩  ╚═╝╩╚═ ╩ ╚═╝ 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, sys    # wpf can be imported only after pyrevit.forms!

# .NET Imports
clr.AddReference("System")
from System.Windows import Window, Visibility
from System import Uri
from System.Windows.Media.Imaging import BitmapImage

# ╦  ╦╔═╗╦═╗╦╔═╗╔╗ ╦  ╔═╗╔═╗
# ╚╗╔╝╠═╣╠╦╝║╠═╣╠╩╗║  ║╣ ╚═╗
#  ╚╝ ╩ ╩╩╚═╩╩ ╩╚═╝╩═╝╚═╝╚═╝ 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 AlertForm(Window):
    def __init__(self, msg, sub_msg="", image_path="", title='EF Alert Form', exitscript=False):
        #⬇️ Connect to .xaml File (in the same folder!)
        path_xaml_file = os.path.join(PATH_SCRIPT, 'Alert.xaml')
        wpf.LoadComponent(self, path_xaml_file)

        #🟦 Change Main Message
        self.UI_msg.Text    = msg

        #🟦 Sub-Message
        if sub_msg:
            self.UI_submsg.Text = sub_msg

        #🟦 Image
        if image_path:
            self.UI_img.Source     = BitmapImage(Uri(image_path))
            self.UI_img.Visibility = Visibility.Visible

        #🟦 Change Title
        self.Title          = title

        #🟧 Store Exitscript value
        self.exitscript = exitscript


        # Show Form
        self.ShowDialog()

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

        if self.exitscript:
            sys.exit()

# ╦ ╦╔═╗╔═╗  ╔═╗╔═╗╦═╗╔╦╗
# ║ ║╚═╗║╣   ╠╣ ║ ║╠╦╝║║║
# ╚═╝╚═╝╚═╝  ╚  ╚═╝╩╚═╩ ╩
#====================================================================================================
print('Before')

# Show form to the user

img_name = "meme_bug.jpg"
img_path = os.path.join(PATH_SCRIPT, img_name)
sub_msg = ("This is a relly long message, so we can test wrapping and stuff."
           "This is a relly long message, so we can test wrapping and stuff.")

UI = AlertForm('Error Message 1', sub_msg=sub_msg,
               image_path=img_path,title= 'Alert Message',
               exitscript=True)


print('After')

Wrapping Up

Alright this form works exactly like we want to.

In the next lesson, I'll show you how to make it reusable, so you can easily import it into any of your pyRevit scripts and reuse it over and over.


Happy Coding, and make sure you recreate this form that we did here together. Feel free to experiment and adjust it to your own needs.

🙋‍♂️ 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.