Need help with my first xaml file

Hello,
I am trying to learn how to use WPF (xaml) alongside my scripts.
This is the very simple xaml file:

<?xml version="1.0" encoding="utf-8"?>
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="Find Views for Line Styles"
	Height="150"
	Width="300"
	WindowStartupLocation="CenterScreen">
	<StackPanel
		Grid.Row="2"
		VerticalAlignment="Top"
		Margin="10,5,10,5">
		<Button
			x:Name="SelectLineButton"
			Content="Select a line in your model"
			Click="SelectLineButton_Click"
			Margin="5 20 5 20" />
		<!-- !Buttons -->
		<Grid>
			<Grid.ColumnDefinitions>
				<ColumnDefinition
					Width="*" />
				<ColumnDefinition
					Width="*" />
			</Grid.ColumnDefinitions>
			<Button
				Click="OK_Click"
				Margin="10 0 10 0"
				Grid.Column="0"
				Content="OK" />
			<Button
				Click="CancelButton_Click"
				Margin="10 0 10 0"
				Grid.Column="1"
				Content="Cancel" />
		</Grid>
	</StackPanel>
</Window>

and this is the python code:

xamlfile = script.get_bundle_file('ui.xaml')

import wpf
from System import Windows

class CustomISelectionFilter(ISelectionFilter):
    def AllowElement(self, element):
        if type(element) == DetailLine:
            return True
    

class Window1 (WPFWindow):
    def __init__(self, xaml_file_name):
        WPFWindow.__init__(self, xaml_file_name)
        self.ShowDialog()
       

    def SelectLineButton_Click(self,sender,e):
        try:
            self.Hide()
            line_sel_ref = selection.PickObject(ObjectType.Element, CustomISelectionFilter())
            line_sel = uidoc.Document.GetElement(line_sel_ref)
            self.Show()
            return line_sel
        except:
            UI.TaskDialog.Show("Operation canceled","Canceled by the user")
    
    def OK_Click (self, sender, e):
        UI.TaskDialog.Show("Hello Revit")
        
        
    
    def CancelButton_Click(self, sender, e):
        self.Close()

form = Window1('ui.xaml')

I can click on the “select a line…” button and pick a line in the model, but when i click in the “OK” button, my revit crash.
I have removed the code, replaced with a simple “Hello Revit” and still crashes.
Could you please help me understand the issue. Thanks

1 Like

HI @hoss53,
I believe this won’t solve your issue, but you can’t return from a method called by a GUI event.

You need to turn line_sel into a Window1 attribute, so that you can retrieve it with form.line_sel (or self.line_sel if you need it inside the class).

BTW, really bad naming if you ask me! Try to be as descriptive as possible with your classes and variables names. Your 3 months self from now will thank you if you make the code clear :wink:

1 Like

Thanks for the help regarding the naming etc.
I am not developing any tools, just trying to understand how things work when comes to using own user interface.
Going back to my original example, OK button has been set to just show a message as follows

    def OK_Click (self, sender, e):
        UI.TaskDialog.Show("Hello Revit", "Hello Revit)

If I run the script and click on ok, without touching anything else, dialog box shows with the set message.
But if I run the script and click on “Select a line in your model” button and pick a line and click on OK, my Revit crashes
So I am guessing I need to use external event somewhere. Would be grateful if someone point me to an example please. Thanks

I think I found an answer to my question
I created a fuction “select_line_inmodel” for selecting the object and using external event handler from pyRevitMEP External Event
changed the code for the button that ask user to pick object as follow

def SelectLineButton_Click(self, sender, e):
self.Hide()
customizable_event.raise_event(self.select_line_inmodel)
self.Show()

and all seems to be working fine now

2 Likes

The script that I have put together was just for learning purposes and I have not spent time naming classes / functions / variables in a good way, sorry :flushed:
so this script asks the user to pick a line (annotation line) and shows a list of views where instances of this line type exist
please do not use on live project as I have not tested the code fully

this is the script

#import libraries
import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI import IExternalEventHandler, ExternalEvent
from Autodesk.Revit.UI import Selection
from Autodesk.Revit.UI.Selection import *
from Autodesk.Revit.Exceptions import InvalidOperationException
from pyrevit.forms import WPFWindow
from pyrevit import script, HOST_APP, engine, revit

# noinspection PyUnresolvedReferences
from pyrevit import UI

# get current document
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument
app = __revit__.Application
uiapp = __revit__
selection = uidoc.Selection 

class CustomISelectionFilter(ISelectionFilter):
	def AllowElement(self, element):
		if type(element) == DetailLine:
			return True

class CustomizableEvent:
	def __init__(self):
		# Create an handler instance and his associated ExternalEvent
		custom_handler = _CustomHandler()
		custom_handler.customizable_event = self
		self.custom_event = UI.ExternalEvent.Create(custom_handler)

		# Initialise raise_event variables
		self.function_or_method = None
		self.args = ()
		self.kwargs = {}

	def _raised_method(self):

		self.function_or_method(*self.args, **self.kwargs)

	def raise_event(self, function_or_method, *args, **kwargs):
		self.args = args
		self.kwargs = kwargs
		self.function_or_method = function_or_method
		self.custom_event.Raise()

class _CustomHandler(UI.IExternalEventHandler):
	"""Subclass of IExternalEventHandler intended to be used in CustomizableEvent class
	Input : function or method. Execute input in a IExternalEventHandler"""

	def __init__(self):
		self.customizable_event = None

	# Execute method run in Revit API environment.
	# noinspection PyPep8Naming, PyUnusedLocal
	def Execute(self, application):
		try:
			self.customizable_event._raised_method()
		except InvalidOperationException:
			# If you don't catch this exeption Revit may crash.
			print("InvalidOperationException catched")

	# noinspection PyMethodMayBeStatic, PyPep8Naming
	def GetName(self):
		return "Execute an function or method in a IExternalHandler"

customizable_event = CustomizableEvent()

class Lesson10 (WPFWindow):
	def __init__(self, xaml_file_name):
		WPFWindow.__init__(self, xaml_file_name)
		self.linestyle_name=""
		self.view_ids = []
		self.unique_view_ids={}
		self.ShowDialog()

	def select_line_inmodel(self):
		try:
			selected_line_ref = selection.PickObject(ObjectType.Element, CustomISelectionFilter())
			selected_line = uidoc.Document.GetElement(selected_line_ref)
			selected_linestyle_name = selected_line.LineStyle.Name
			self.linestyle_name = selected_linestyle_name
		except:
			UI.TaskDialog.Show("Operation canceled","Canceled by the user")

	#Find views where we have an instance of the selected line style
		#Collect all the line types
		lines = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Lines)
		#filter out the line type and get views that have the selected linestle
		for l in lines:
			if l.LineStyle.Name == self.linestyle_name:
				self.view_ids.append(l.OwnerViewId)
		#Create a unique list of view Ids
		for i in self.view_ids:
			if i in self.unique_view_ids: self.unique_view_ids[i] +=1
			else: self.unique_view_ids[i] =1
		return self.unique_view_ids
	
	# def get_selected_linestyle(self):
	# 		selected_items = []
	# 		for item in self.UI_ListBox_Materials.ItemsSource:
	# 			if item.IsChecked:
	# 				selected_items.append(item.element)
	# 		return selected_items


	def out_put(self):
		#Script output
		output = script.get_output()
		#Header for output
		output.print_md("List of all View where we have instance/s of linestyle: {}".format(self.linestyle_name))
		print ("Click on a Id of the view to open it...")
		# report the views hosting the line style
		for key, value in (sorted(self.unique_view_ids.items())):
			print('There are {} instances of line style: {} on View:'.format(value, self.linestyle_name) + (doc.GetElement(key).Name) + '{} '.format(output.linkify(key)))

	def SelectLineButton_Click(self, sender, e):
		self.Hide()
		customizable_event.raise_event(self.select_line_inmodel)
		self.Show()

	def Cancel_button(self, sender, e):
		self.Close()
	def OK_button(self, sender, e):
		self.Close()
		self.out_put()


if __name__ == '__main__':
	modeless_form = Lesson10("ui.xaml")

and this is the xaml file

<?xml version="1.0" encoding="utf-8"?>
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="Lesson_10"
	Height="500"
	Width="300" WindowStartupLocation="CenterScreen">
	<StackPanel Grid.Row="2"
		VerticalAlignment="Top"
		Margin="10,5,10,5">
		<Button
			x:Name="SelectLineButton"
			Content="Select a line in your model"
			Click="SelectLineButton_Click"
			Margin="5" />
		<TextBlock
			Text="Or Select from below list"
			FontWeight="Bold"
			Margin="0 10"
			HorizontalAlignment="Center" />
		<ListBox
			x:Name="UI_ListBox_Categories"
			Height="350"
			SelectionMode="Single"
			HorizontalContentAlignment="Stretch">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<CheckBox
						IsChecked="{Binding IsChecked}">
						<TextBlock
							Text="{Binding Name}" />
					</CheckBox>
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
		<Grid>
			<Grid.ColumnDefinitions>
				<ColumnDefinition
					Width="*" />
				<ColumnDefinition
					Width="*" />
			</Grid.ColumnDefinitions>
			<Button
				x:Name="OK"
				Grid.Column="0"
				Margin="5 10 5 10"
				Content="Ok"
				Click="OK_button" />
			<Button
				x:Name="Cancel"
				Grid.Column="2"
				Margin="5 10 5 10"
				Content="Cancel"
				Click="Cancel_button" />
		</Grid>
	</StackPanel>
</Window>

the script is work in progress as I am planning to have it show a full list of line types so user can either select it in the view or pick one from list. Hope this help