Access WPF Form Controls and Get User Input
Learn how to access WPF controls with python code so you can read and modify properties inside your form. This is essential concept to understand how to use x:Name attribute in WPF, so let me show you how to use it.
Access WPF Form Controls and Get User Input
Learn how to access WPF controls with python code so you can read and modify properties inside your form. This is essential concept to understand how to use x:Name attribute in WPF, so let me show you how to use it.
Summary
Get User Inputs from WPF Forms
In the previous lessons you've learnt how to create WPF forms for pyRevit.
You can:
Design your XAML Forms
Connect them to pyRevit
Display in Revit
And even add EventTriggers
But there is a crucial piece missing right now. You still don't know how to read user input from your forms…
After all, that's the primary purpose of creating these WPF forms.
So let's explore how to assign an x:Name
attribute to your controls and access them in your Python code. This is a key concept in WPF that allows you to reference and manipulate your controls effectively and read whatever values users provide.
Prepare a new Button
Firstly, prepare the button for the lesson. I will duplicate the previous lesson and continue working on the same form.
I will clean up the code a little, because we won't need the events we created, except for the submit button. So here is the code I'm starting this lesson with:
<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="150"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
SizeToContent="Height">
<StackPanel Margin="10">
<DockPanel Margin="5">
<TextBlock Text="Input A" Margin="0,0,10,0" FontWeight="Bold"/>
<TextBox Text="Default Value..." Foreground="Gray"/>
</DockPanel>
<DockPanel Margin="5">
<TextBlock Text="Input B" Margin="0,0,10,0" FontWeight="Bold"/>
<TextBox Text="Default Value..." Foreground="Gray"/>
</DockPanel>
<DockPanel Margin="5">
<TextBlock Text="Input C" Margin="0,0,10,0" FontWeight="Bold"/>
<ComboBox>
<ComboBoxItem Content="Walls"/>
<ComboBoxItem Content="Floors"/>
<ComboBoxItem Content="Roofs"/>
<ComboBoxItem Content="Windows" IsSelected="True"/>
<ComboBoxItem Content="Doors"/>
</ComboBox>
</DockPanel>
<DockPanel HorizontalAlignment="Center" Margin="5">
<CheckBox Content="Check 1" Margin="0,0,10,0"/>
<CheckBox Content="Check 2" Margin="0,0,10,0"/>
<CheckBox Content="Check 3" />
</DockPanel>
<StackPanel Margin="5">
<TextBlock Text="Select Views:" FontWeight="Bold" Margin="0,0,0,5"/>
<DockPanel Margin="5">
<TextBlock Text="🔎" Margin="0,0,5,0" />
<TextBox/>
</DockPanel>
<ListBox Height="150" SelectedIndex="0">
<ListBoxItem>
<CheckBox Content="View - A"/>
</ListBoxItem>
<ListBoxItem>
<CheckBox Content="View - B"/>
</ListBoxItem>
<ListBoxItem>
<CheckBox Content="View - C"/>
</ListBoxItem>
</ListBox>
<DockPanel HorizontalAlignment="Center" Margin="0,10,0,0">
<Button Content="Select All" Width="100" Margin="0,0,10,0"/>
<Button Content="Select None" Width="100"/>
</DockPanel>
</StackPanel>
<Separator Margin="5,5,5,12"/>
<Button Content="Submit!" Width="100" Click="UIe_btn_run" />
__title__ = "02.03 - First pyRevit Form (Events)"
__doc__ = """Version = 1.0
Date = 15.09.2024
________________________________________________________________
Description:
Learn how to handle Events in WPF with IronPython.
________________________________________________________________
Author: Erik Frits"""
from Autodesk.Revit.DB import *
from pyrevit import forms
import wpf, os, clr
clr.AddReference("System")
from System.Collections.Generic import List
from System.Windows import Application, Window
from System.Windows.Controls import CheckBox, Button, TextBox, ListBoxItem
from System import Uri
PATH_SCRIPT = os.path.dirname(__file__)
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument
app = __revit__.Application
class FirstButton(Window):
def __init__(self):
path_xaml_file = os.path.join(PATH_SCRIPT, 'FirstButtonEvents.xaml')
wpf.LoadComponent(self, path_xaml_file)
self.ShowDialog()
def UIe_btn_run(self, sender, e):
print('Form Submitted!')
self.Close()
UI = FirstButton()
Assigning x:Name Attributes
And now we are ready to begin the lesson.
To access any controls in your WPF Form to read values or even modify some properties, we need to assign an x:Name
attribute to your controls.
This will give them internal name in your WPF class that can be used to reference this element to either read its properties or modify them.
Let's assign this attribute to a few controls and then I explain more.
You can use x:Name
like a property and assign any name you want to reference in your python code.
💡I recommend to always start with prefix UI_
as it will help you in debugging and just in general finding all these attribute names.
So, if I create x:Name = "UI_textbox1"
, it means that I can access this specific control by using self.UI_textbox1
python code inside of my WPF class.
Everything is accessible inside your python class that is based on the WPF Window
class because it loads up the XAML code inside of it.
You can do that to any controls:
<DockPanel x:Name="UI_dock" Margin="5">
<TextBlock x:Name="UI_textblock1" Text="Input A" Margin="0,0,10,0" FontWeight="Bold"/>
<TextBox x:Name="UI_textbox1" Text="Default Value..." Foreground="Gray" />
💡 Just don't forget to update XAML code in pushbutton.
Accessing Control Values in Python
Let's try to read the <TextBox>
control in our python code. I will create a few print statements inside of the Button run method.
So to get the Textbox element itself we can use self.UI_textbox1
and then reading its properties is simple:
def UIe_btn_run(self, sender, e):
print('Form Submitted!')
print(self.UI_textbox1)
print(self.UI_textbox1.Text)
print(self.UI_textbox1.Foreground)
self.Close()
Change WPF Controls with Python
Not only we can access WPF controls to read their properties, but we can also modify them the same way.
Let's use exactly the same element and set it to another value. I will also comment out self.Close() so we can actually see the change when we click on the button.
def UIe_btn_run(self, sender, e):
print('Form Submitted!')
print(self.UI_textbox1)
print(self.UI_textbox1.Text)
print(self.UI_textbox1.Foreground)
self.UI_textbox1.Text = "I just changed this text!"
If you going to test it, you will first print whatever was written in the form, and then you will modify it to your own message with python code.
XAML - Assign Names to WPF Controls
Let's go back to XAML and assign more x:Name attributes to other elements.
When you do that, notice that you can not have the same name for different elements. This will give you an Invalid Markup Error, so make sure you use unique names.
I will add x:Name
attribute to
UI_textbox1
UI_textbox2
UI_combobox
UI_check1
UI_check2
UI_check3
UI_search
UI_listbox
💡It's recommended to put x:Name property as the first one in your controls. It will help with readability and maintaining your XAML code.
Here is the final XAML 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="450" Width="300" MinHeight="150"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<StackPanel Margin="10">
<DockPanel Margin="5">
<TextBlock x:Name="UI_textblock1" Text="Input A" Margin="0,0,10,0" FontWeight="Bold"/>
<TextBox x:Name="UI_textbox1"
Text="Default Value..."
Foreground="Gray"
/>
</DockPanel>
<DockPanel Margin="5">
<TextBlock Text="Input B" Margin="0,0,10,0" FontWeight="Bold"/>
<TextBox x:Name="UI_textbox2"
Text="Default Value..." Foreground="Gray" />
</DockPanel>
<DockPanel Margin="5">
<TextBlock Text="Input C" Margin="0,0,10,0" FontWeight="Bold"/>
<ComboBox x:Name="UI_combobox">
<ComboBoxItem Content="Walls"/>
<ComboBoxItem Content="Floors"/>
<ComboBoxItem Content="Roofs"/>
<ComboBoxItem Content="Windows" IsSelected="True"/>
<ComboBoxItem Content="Doors"/>
</ComboBox>
</DockPanel>
<DockPanel HorizontalAlignment="Center" Margin="5">
<CheckBox x:Name="UI_check1" Content="Check 1" Margin="0,0,10,0" />
<CheckBox x:Name="UI_check2" Content="Check 2" Margin="0,0,10,0" />
<CheckBox x:Name="UI_check3" Content="Check 3" />
</DockPanel>
<StackPanel Margin="5">
<TextBlock Text="Select Views:" FontWeight="Bold" Margin="0,0,0,5"/>
<DockPanel Margin="5">
<TextBlock Text="🔎" Margin="0,0,5,0" />
<TextBox x:Name="UI_search"/>
</DockPanel>
<ListBox x:Name="UI_listbox" Height="150" SelectedIndex="0">
<ListBoxItem>
<CheckBox Content="View - A"/>
</ListBoxItem>
<ListBoxItem>
<CheckBox Content="View - B"/>
</ListBoxItem>
<ListBoxItem>
<CheckBox Content="View - C"/>
</ListBoxItem>
</ListBox>
<DockPanel HorizontalAlignment="Center" Margin="0,10,0,0">
<Button Content="Select All" Width="100" Margin="0,0,10,0"/>
<Button Content="Select None" Width="100"/>
</DockPanel>
</StackPanel>
<Separator Margin="5,5,5,12"/>
<Button Content="Submit!"
Width="100"
Click="UIe_btn_run" />
Best Practice to read WPF properties
Alright, before we are goin to go and get all controls with self.GivenName
, I want to mention the best practice for getting WPF values.
Let's talk about @property
decorator in python, that would look like this:
@property
- is a concept in Object-Oriented Programming(OOP) in python.
It allows you to define properties as methods, which will let you define more logic for getting and setting different values. It has many different uses, but let's keep it simple.
First thing you need to know is that when you create a method and apply property decorator.
For example:
1️⃣You could create a method to get a value in your class, and then assign it to a variable.
2️⃣ Or you could apply @property
to the method and then it would become a property.
And in both cases you would get the result by using instance.my_value
class TestClass():
def __init__(self):
self.my_value = self.get_value()
def get_value(self):
x = 1
y = 2
return x*y
class TestClass():
def __init__(self):
pass
@property
def my_value(self):
x = 1
y = 2
return x*y
💡 Keep in mind that this is a very abstract example to explain the functionality of how to use it. You can dive deeper on your own into @property
and how it can be used in your own classes.
Read WPF Values
Now let's apply this @property
to our WPF class and read all the values from our form. Just make sure the name of your properties are not exact match of what you called you UI elements so you don't override them.
Here is the final python code:
__title__ = "02.04 - Get User Inputs with xName"
__doc__ = """Version = 1.0
Date = 15.09.2024
________________________________________________________________
Description:
Learn how to get user input from WPF forms by using xName attribute.
________________________________________________________________
Author: Erik Frits"""
from Autodesk.Revit.DB import *
from pyrevit import forms
import wpf, os, clr
clr.AddReference("System")
from System.Collections.Generic import List
from System.Windows import Application, Window
from System.Windows.Controls import CheckBox, Button, TextBox, ListBoxItem
from System import Uri
PATH_SCRIPT = os.path.dirname(__file__)
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument
app = __revit__.Application
class FirstButton(Window):
def __init__(self):
path_xaml_file = os.path.join(PATH_SCRIPT, 'xNameAttributes.xaml')
wpf.LoadComponent(self, path_xaml_file)
self.ShowDialog()
@property
def textbox1(self):
return self.UI_textbox1.Text
@property
def textbox2(self):
return self.UI_textbox2.Text
@property
def search(self):
return self.UI_search.Text
@property
def combobox_item(self):
return self.UI_combobox.SelectedItem
@property
def check1(self):
return self.UI_check1.IsChecked
@property
def check2(self):
return self.UI_check2.IsChecked
@property
def check3(self):
return self.UI_check3.IsChecked
def UIe_btn_run(self, sender, e):
print('Form Submitted!')
print('Printing from inside form:!')
print('textbox1: {}'.format(self.textbox1))
print('textbox2: {}'.format(self.textbox2))
print('check1: {}'.format(self.check1))
print('check2: {}'.format(self.check2))
print('check3: {}'.format(self.check3))
print('search: {}'.format(self.search))
self.Close()
UI = FirstButton()
print('-'*50)
print('Printing from outside form:!')
print('textbox1: {}'.format(UI.textbox1))
print('textbox2: {}'.format(UI.textbox2))
print('check1: {}'.format(UI.check1))
print('check2: {}'.format(UI.check2))
print('check3: {}'.format(UI.check3))
print('search: {}'.format(UI.search))
Summary
Now, you've learnt how to use x:Name
attribute in WPF to access, read and set values to your controls.
Also, I hope that I didn't overwhelm you with @property
or anything else during this lesson. But if I did, please let me know in the community.
Tell me if you found anything confusing during this lesson and if you think that I should re-record it differently. I geniunly want to hear your feedback about this lesson as I feel I might have given too much at once.
It's 100% appreciated as it helps me make better lessons for you.
Homework
Also don't forget to practice. Try adding x:Name attribute to some of your control and get them with the code. Try reading values or even modifying some of the values.
Share your code and insights in the community with others. Together you will learn much more than on your own.
And then, In the next lesson, we'll dive into how to populate data in <ListBox>
and how to get user selection from the list.
Until then and 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.