打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
快速定位元素

一个常见的Revit模型至少包含上千个元素,稍微复杂一些的都是上万甚至十万级别的元素。要在这样的模型中查找某一个元素会非常费时费力,Revit本身不提供基于类似名称、类型等过滤条件的搜索。本小节利用Revit API基于元素类型、名称等条件在全模型文档中查找并显示元素。

该样例的功能如下:

  • 支持通过元素类别过滤查找

  • 支持通过元素名称过滤查找

  • 支持显示查找结果前50个元素

  • 搜索结果显示元素ID及元素名称

  • 支持显示单个元素(受API限制,不能保证每一个都成功显示)

如果希望更友好地显示过滤结果,例如分页等,因为不涉及到Revit功能,读者可以自行尝试。

由于我们需要支持选中元素后显示该元素,搜索对话框需要是非模态的,而对于非模态对话框,窗口加载完成后就已经不在Revit API有效上下文内了,这时如果调用RevitAPI,会导致异常使得程序崩溃。Revit API提供了UIApplication.Idling事件来处理这样的需求,为了响应Idling事件,我们需要实现IExternalApplication接口并在启动时注册该事件,在Revit关闭时注销该事件。需要注意的是,如果希望能在程序运行过程中动态根据情况注册/注销Idling事件,比较好的方法是定义一个静态的EventHandler,将该静态的对象注册,程序中其它地方的注册都注册到该静态EventHandler(参见Application类中的IdlingHandlers对象)。

实现接口的Application类代码如下:

public class Application : IExternalApplication
{
  public static event EventHandler IdlingHandlers;
  public Result OnStartup(UIControlledApplication application)   {
     // Create Ribbon Tab, panel and button      ... ...      application.Idling += OnIdling;
     return Result.Succeeded;   }
  private void OnIdling(object sender, IdlingEventArgs e)   {
     if (IdlingHandlers != null)      {         IdlingHandlers(sender, e);      }   }
  public Result OnShutdown(UIControlledApplication application)   {
     if (Command.searchDialog != null)      {         Command.searchDialog.Close();      }      application.Idling -= OnIdling;
     return Result.Succeeded;   }}

其中searchDialog是Command类的静态对象,目的是为了避免重复创建搜索对话框。Command类代码如下:

public class Command : IExternalCommand
{
  public static FormSearch searchDialog = null;
  public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)   {
     if (searchDialog == null)      {         searchDialog = new FormSearch(commandData.Application.ActiveUIDocument);         searchDialog.Show();      }
     return Result.Succeeded;   }}

这里的搜索对话框如下图所示:

对于窗口的数据,微软提供的WPF可以很方便的进行数据绑定展示,由于制作Revit插件经常会遇到类似比较复杂的界面,学会如何利用WPF进行数据绑定展示十分重要。这里我们以本实例为基础大致说明一下数据绑定处理的方法,更多的内容请参考微软的开发文档。

要完成WPF界面数据绑定,需要根据窗口要展示的数据定义一个数据类,该数据类必须实现INotifyPropertyChanged接口,并在修改需要绑定的数据时,主动触发PropertyChanged事件。为了避免在不同的数据类中都重复同样的代码,建议用一个数据基类,如下所示,所有数据类可以直接从该数据基类继承。

public class NotifyObjectBase : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
  protected void OnPropertyChanged(string propertyName)   {
     if (this.PropertyChanged != null)      {
        this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));      }   }}

对于界面需要展示的数据,我们用下面的数据类来定义:

public class ElementInfo : NotifyObjectBase
{
#region Member variables   private int _elementId;
  private string _name;
#endregion   public ElementInfo()   {      Id = -1;      Name = '';   }
  public ElementInfo(Element element)   {      Id = element.Id.IntegerValue;      Name = element.Name;   }
  public int Id   {
     get      {
        return _elementId;      }
     set      {         _elementId = value;         OnPropertyChanged('Id');      }   }
  public string Name   {
     get      {
        return _name;      }
     set      {         _name = value;         OnPropertyChanged('Name');      }   }}
public class SearchData : NotifyObjectBase
{
#region Member variables   private ObservableCollection _elements;
  private ObservableCollection _categories;
  private ElementInfo _selectElement;
  private ElementInfo _selectCategory;
#endregion   public ObservableCollection Elements   {
     get      {
        return _elements;      }
     set      {         _elements = value;         OnPropertyChanged('Elements');      }   }
  public ObservableCollection Categories   {
     get      {
        return _categories;      }
     set      {         _categories = value;         OnPropertyChanged('Categories');      }   }
  public ElementInfo SelectElement   {
     get      {
        return _selectElement;      }
     set      {         _selectElement = value;         OnPropertyChanged('SelectElement');      }   }
  public ElementInfo SelectCategory   {
     get      {
        return _selectCategory;      }
     set      {         _selectCategory = value;         OnPropertyChanged('SelectCategory');      }   }
  public SearchData()   {      Elements = new ObservableCollection();      Categories = new ObservableCollection();
     // add and select default one : all      ElementInfo info = new ElementInfo();      info.Name = 'All';      Categories.Add(info);      SelectCategory = info;   }
  public void Clear()   {      Elements.Clear();   }
  public void AddElement(Element element)   {      Elements.Add(new ElementInfo(element));   }
  public void AddCategory(Category category)   {      ElementInfo info = new ElementInfo();      info.Id = category.Id.IntegerValue;      info.Name = category.Name;      Categories.Add(info);   }}

注意在所有属性的设置部分,都需要调用OnPropertyChanged,参数与该属性的名称一致。其次,对于数据集(比如该界面用到的结果,以及类别清单),必须使用ObservableCollection,否则添加/删除元素时界面不能正确更新。

界面代码分两部分,一部分是界面xaml定义,另一部分是响应代码。界面定义如下:

                                                                                                                                                                   

以类别下拉列表数据绑定为例,ItemsSource='{BindingPath=Categories}'表示将数据对象的Categories属性绑定到该下拉列表中,SelectedItem='{Binding Path=SelectCategory}'表示将数据对象的SelectCategory绑定到当前选中的对象,默认情况下绑定是双向的,如果需要指定单向,可以这样修改{Binding Path=SelectCategory,Mode=OneWay}。

界面响应代码如下:

public partial class FormSearch : Window
{
  private UIDocument _uiDoc;
  private SearchData _data;
  private bool _searchClicked, _showClicked;
  private const int MaxCount = 50;
  public FormSearch(UIDocument uiDoc)   {      InitializeComponent();      _uiDoc = uiDoc;      _data = new SearchData();      _searchClicked = _showClicked = false;   }
  private void Window_Loaded(object sender, RoutedEventArgs e)   {      Application.IdlingHandlers += OnIdling;      DataContext = _data;
     // Initial all categories      foreach (Category cat in _uiDoc.Document.Settings.Categories)      {         _data.AddCategory(cat);      }   }
  private void OnIdling(object sender, IdlingEventArgs e)   {
     if (_searchClicked)      {         _searchClicked = false;         SearchElements();      }
     else if (_showClicked)      {         _showClicked = false;
        if (_data.SelectElement != null)         {            _uiDoc.ShowElements(new ElementId(_data.SelectElement.Id));         }      }   }
  private void SearchElements()   {      _data.Clear();
     string name = tbName.Text;      Document doc = _uiDoc.Document;      FilteredElementCollector collector = new FilteredElementCollector(doc).WhereElementIsNotElementType();
     if (_data.SelectCategory != null && _data.SelectCategory.Id != -1)      {         collector = collector.OfCategoryId(new ElementId(_data.SelectCategory.Id));      }
     var elements = collector.Where(x => x.Name.Contains(name)).Take(MaxCount);
     foreach (var element in elements)      {         _data.AddElement(element);      }   }
  private void Search_Click(object sender, RoutedEventArgs e)   {
     if (string.IsNullOrEmpty(tbName.Text))      {         MessageBox.Show('Please input name filter text!');         tbName.Focus();
        return;      }      _searchClicked = true;   }
  private void Show_Click(object sender, RoutedEventArgs e)   {
     if (_searchClicked || _data.SelectElement == null)
        return;      _showClicked = true;   }
  private void tbName_KeyUp(object sender, KeyEventArgs e)   {
     if (e.Key == Key.Enter)      {         Search_Click(sender, e);      }   }
  private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)   {      Application.IdlingHandlers -= OnIdling;      Command.searchDialog = null;   }}

注意一下这里OnIdling的注册/注销方法,所有与Revit相关的代码需要放到OnIdling函数中。其次在Window_Loaded函数中(该函数在界面加载时会被调用)需要绑定数据对象与整个界面,通过DataContext = _data;语句。

本小节主要包含了以下一些知识点:

  • Revit API调用需要在正确的上下文内,超出该范围的调用会引起程序崩溃

  • 在非模态对话框中,可以通过响应Idling事件来获取Revit API上下文

  • Idling事件需要在IExternalApplication的OnStartup中注册,在OnShutdown中注销

  • 界面数据绑定可以利用WPF的数据绑定功能

  • 需要绑定的数据类都必须实现INotifyPropertyChanged接口 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Revit的参数
C# Revit二次开发关于元素Element
学习3、Revit二次开发:简单的按钮
MSXML 4.0 DOM API
[转]Android dex分包方案
用DOM/JDOM解析XML文件(3)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服