如何从Revit停靠式WPF按钮执行IExternalCommand并解决崩溃问题
问题描述
已在Revit中创建停靠式WPF面板,可正常显示/隐藏。目标是在WPF面板按钮中运行自定义命令(替代功能区按钮),该命令在Add-In Manager中可正常执行,但点击WPF按钮时Revit崩溃,不清楚如何为Execute方法传递所需参数。
命令代码(PrecastDisallowJoin.cs)
using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using Autodesk.Revit.UI.Selection; using System.Collections.Generic; using System.Linq; namespace Adams.Commands { [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] public class PrecastDisallowJoin : IExternalCommand { public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { var uiApplication = commandData.Application; var application = uiApplication.Application; var uiDocument = uiApplication.ActiveUIDocument; var document = uiDocument.Document; // 提示用户选择墙体 var references = uiDocument.Selection .PickObjects( ObjectType.Element, new WallSelectionFilter(), "Please select walls"); var components = references.Select(r => document.GetElement(r)).ToList(); // 启动事务 using (Transaction t = new Transaction(document, "Change Wall Join Behavior")) { t.Start(); // 遍历选中墙体,修改连接行为 foreach (Reference reference in references) { Wall wall = document.GetElement(reference) as Wall; WallUtils.DisallowWallJoinAtEnd(wall, 0); WallUtils.DisallowWallJoinAtEnd(wall, 1); } // 提交事务 t.Commit(); } return Result.Succeeded; } public class WallSelectionFilter : ISelectionFilter { public bool AllowElement(Element elem) { //return elem is FamilyInstance; return elem.Name.Contains("Precast"); } public bool AllowReference(Reference reference, XYZ position) { return true; } } } }
WPF后台代码(Customers.xaml.cs)
using Autodesk.Revit.UI; using System.Windows.Controls; using Adams.Commands; using System.Windows; namespace Adams.ui { public partial class Customers : UserControl { public UIDocument uIDocument { get; } public ExternalCommandData commandData { get; } public Customers(UIDocument uIDocument ) { InitializeComponent(); } private void btnStartExcelElementsApp_Click(object sender, RoutedEventArgs e) { string message = string.Empty; PrecastDisallowJoin precastDisallow = new PrecastDisallowJoin(); precastDisallow.Execute(commandData, ref message, null); } } }
解决方案
1. 崩溃原因
你的commandData字段从未赋值,始终为null。调用Execute时传入null,导致访问commandData.Application触发空引用异常,直接导致Revit崩溃。
2. 优化方案:分离命令逻辑(推荐)
将命令的核心逻辑从IExternalCommand.Execute中抽离成独立方法,既保留外部命令的兼容性,又能在WPF面板中直接调用,无需依赖ExternalCommandData参数。
修改命令类代码
using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using Autodesk.Revit.UI.Selection; using System; using System.Linq; namespace Adams.Commands { [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] public class PrecastDisallowJoin : IExternalCommand { // 抽离核心逻辑为静态方法,供WPF直接调用 public static Result Run(UIApplication uiApp) { try { var uiDocument = uiApp.ActiveUIDocument; var document = uiDocument.Document; // 处理用户取消选择的情况 var references = uiDocument.Selection .PickObjects( ObjectType.Element, new WallSelectionFilter(), "Please select walls"); using (Transaction t = new Transaction(document, "Change Wall Join Behavior")) { t.Start(); foreach (Reference reference in references) { if (document.GetElement(reference) is Wall wall) { WallUtils.DisallowWallJoinAtEnd(wall, 0); WallUtils.DisallowWallJoinAtEnd(wall, 1); } } t.Commit(); } return Result.Succeeded; } catch (OperationCanceledException) { // 用户取消选择,返回已取消状态 return Result.Cancelled; } catch (Exception ex) { TaskDialog.Show("命令执行失败", ex.Message); return Result.Failed; } } // 保留原Execute方法,兼容Add-In Manager和功能区按钮 public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { return Run(commandData.Application); } public class WallSelectionFilter : ISelectionFilter { public bool AllowElement(Element elem) { return elem.Name.Contains("Precast"); } public bool AllowReference(Reference reference, XYZ position) { return true; } } } }
修改WPF后台代码
using Autodesk.Revit.UI; using System.Windows; using System.Windows.Controls; using Adams.Commands; namespace Adams.ui { public partial class Customers : UserControl { private readonly UIApplication _uiApp; // 构造函数接收UIApplication实例(从面板初始化处传入) public Customers(UIApplication uiApp) { InitializeComponent(); _uiApp = uiApp; } private void btnStartExcelElementsApp_Click(object sender, RoutedEventArgs e) { PrecastDisallowJoin.Run(_uiApp); } } }
3. 面板初始化时传入UIApplication
在注册停靠面板的代码中,初始化Customers控件时需要传入当前的UIApplication实例,例如:
// 注册停靠面板的代码片段 public void RegisterPanel(UIControlledApplication app) { // 创建面板 var panel = app.CreateDockablePanel("YourPanelId", "自定义面板", new Customers(app.ActiveUIApplication)); // 设置面板属性(如停靠位置等) }
关键注意事项
- 始终添加异常捕获:用户取消选择(
OperationCanceledException)、元素类型错误等情况都可能导致崩溃,必须捕获并处理。 - 避免直接调用
IExternalCommand.Execute:该方法是Revit外部命令的标准入口,适合功能区/Add-In Manager调用,WPF面板中直接调用核心逻辑更灵活。
内容的提问来源于stack exchange,提问作者raj
相关产品推荐
相关产品推荐

