Use Navisworks API with WPF – Binding Model Hierarchy to Tree View

By Xiaodong Liang

In the last post, we have seen how to create an application of WPF with .NET control. As mentioned, WPF could be looked as the successor of Win Form. But it is much more powerful and flexible.  One of its unique features is Data Binding.  WPF has a built-in set of data services to enable application developers to bind and manipulate data within applications. If you have some codes of Win Form to build the hierarchy tree of Navisworks model like the Selection Tree in UI, you must have to iterate each items and build the nodes & child nodes recursively. While with WPF, the job is much easier.

The whole project is available at Download ADN-NwWPFTree

Firstly, add a tree view control to the form. You could either drag it from toolbox, or add the elements directly in the xaml. I adjusted the width of Navisworks view control to make the tree control wider.

<Window x:Class="NwWPFControlApp.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;       

        Title="MainWindow" Height="350" Width="525"

        xmlns:NwApi="clr-namespace:Autodesk.Navisworks.Api;assembly=Autodesk.Navisworks.Api"

        xmlns:NwControls="clr-namespace:Autodesk.Navisworks.Api.Controls;assembly=Autodesk.Navisworks.Controls"

        xmlns:my="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration" Closed="Window_Closed">      

    <DockPanel>

        <Button Content="Open File" Height="23" Name="button1" Width="75" Click="button1_Click" />

        <WindowsFormsHost Width="198">

            <!–x:Uid used in XAML context–>

            <!–x:Name governed by the XAML namescope–>

            <NwControls:ViewControl x:Uid="viewControl"  

                                    x:Name="viewControl"

                                  Dock="Fill" />

        </WindowsFormsHost>       

        <TreeView Height="308" Name="treeView1" Width="223">

        </TreeView>

    </DockPanel>

</Window>

Next, we need to tell the tree view which source to bind. I am not a WPF expert, I’d appreciate many posts in internet which guides me how to get started with. I’d recommend this article below. It helped me to know the basic elements I need for building the tree.

http://blogs.msdn.com/b/mikehillberg/archive/2009/10/30/treeview-and-hierarchicaldatatemplate-step-by-step.aspx

In short, the key steps with tree binding are:

  1. tell the tree view control which data source you want to bind. In general, this is the top of the hierarchy. In our case, it is the root item of the tree. e.g.  

Document oDoc = documentControl.Document;
 treeView1.DataContext = oDoc.Models[0].RootItem;

  1. bind the data source  to the tree view.   

       <!–tell the tree the collection of the hierarchy. in our case it is RootItem.Children–>

        <TreeView Height="308" Name="treeView1" Width="223" ItemsSource="{Binding Path=Children}">

            <!–define the item (tree node) template, i.e. each ModelItem corresponds to a node–>

             <TreeView.ItemTemplate >

                <!–in order to have a hierarchical tree, it also requires to bind ModeItem.Children. Thus

                 WPF knows to iterate the child node–>

                <HierarchicalDataTemplate  ItemsSource="{Binding Path=Children }">

                    <!–TextBlock defines the final text displayed with the tree node. e.g. we tell WPF to display

                    ModelItem.DsiplayName–>

                    <TextBlock Foreground="Red" Text="{Binding Path=DisplayName}" />

                </HierarchicalDataTemplate>

            </TreeView.ItemTemplate>

        </TreeView>

 

image

The tree is built.But you may quickly find some nodes have no name! What’s up? Actually, ModelItem.DisplayName is the name displayed for the end user. It is supplied by the original CAD designer. e.g. in this model which is from a DWG file, the layer has name, the block definition has name, however, insert or the end geometries have no name. While I guess Navisworks organized the names when it is loading the CAD file. So it looks well in its Selection Tree. ModelItem.ClassName or ModelItem.ClassDisplayName are not empty in default. But they are not meaningful to the end user. I do not suggest to bind them to the tree.

image

Well, does this mean we have no way to give the desired name by binding? No, WPF is much flexible. It allows you with conditional statements. again, the tutorial on internet tells more:

http://stackoverflow.com/questions/11395349/writing-conditional-statements-in-xaml-code

I chose the way IValueConverter. I do not want to spend time to explain the theory of IValueConverter. Please refer to MSDN for details. In our case, we need firstly create our own conversion class. e.g.

// conversion class for tree node

public class NwTreeConverter : IValueConverter

{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

    {

        // since we bind DisplayName to the node, the value is a string

        //<TextBlock Foreground="Red" Text="{Binding Path=DisplayName}" />

 

        if (value == "")

            // if it is empty, return an arbitrary string

            return "dummy";

        else

            // if it is not empty, return it

            return value;

 

    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

    {

        // we do not need to convert back at this moment

        throw new NotImplementedException();

    }

}

 

Some more lines in the xmal are required, in order to tell the tree view control to use the class.

<Window x:Class="NwWPFControlApp.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;       

        Title="MainWindow" Height="350" Width="525"

        xmlns:NwApi="clr-namespace:Autodesk.Navisworks.Api;assembly=Autodesk.Navisworks.Api"

        xmlns:NwControls="clr-namespace:Autodesk.Navisworks.Api.Controls;assembly=Autodesk.Navisworks.Controls"

        xmlns:my="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration" Closed="Window_Closed"

        xmlns:src="clr-namespace: NwWPFControlApp">

 

 

    <!–import the conversion class–>

    <Control.Resources>

        <src:NwTreeConverter x:Key="NwTreeConverter"/>

    </Control.Resources>

     

    <DockPanel>

        <Button Content="Open File" Height="23" Name="button1" Width="75" Click="button1_Click" />

        <WindowsFormsHost Width="198">

            <!–x:Uid used in XAML context–>

            <!–x:Name governed by the XAML namescope–>

            <NwControls:ViewControl x:Uid="viewControl"  

                                    x:Name="viewControl"

                                  Dock="Fill" />

        </WindowsFormsHost>

 

 

        <!–tell the tree the collection of the hierarchy. in our case it is RootItem.Children–>

        <TreeView Height="308" Name="treeView1" Width="223" ItemsSource="{Binding Path=Children}" SelectedItemChanged="treeView1_SelectedItemChanged">

            <!–define the item (tree node) template, i.e. each ModelItem corresponds to a node–>

             <TreeView.ItemTemplate >

                <!–in order to have a hierarchical tree, it also requires to bind ModeItem.Children. Thus

                 WPF knows to iterate the child node–>

                <HierarchicalDataTemplate  ItemsSource="{Binding Path=Children }">

                    <!–TextBlock defines the final text displayed with the tree node. e.g. we tell WPF to display

                    ModelItem.DsiplayName–>

                    <!–tell the textbox to use the string returned from the conversion class–>

                    <TextBlock Foreground="Red" Text="{Binding Path=PropertyCategories, Converter={StaticResource  NwTreeConverter}}" />

                </HierarchicalDataTemplate>

            </TreeView.ItemTemplate>

        </TreeView>

    </DockPanel>

</Window>

 

 

Now, if we open the model, the tree looks like as below.

image

But it is not elegant to hard-code the name of the node with “dummy”. I have been racking my brain trying the way to provide more meaningful name. Finally, I can only think of to bind the ModelItem.PropertyCategories thus we could get any information in the properties and use them as the node name. So the conversion class is tuned to:

// conversion class for tree node
public class NwTreeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // 
 
        // the value is PropertyCategoryCollection
        PropertyCategoryCollection oColl = value as PropertyCategoryCollection;
        if (oColl == null)
        {
            // can be null? that must be a terrible error!
            return "error!";
        }
        else
        {
            // try to check if there is name with the item
            DataProperty oDP = oColl.FindPropertyByDisplayName("Item", "Name");
            if (oDP != null)
            {
                //use "name" as node name
                return oDP.Value.ToDisplayString();
            }
            else
            {
                // no "name", then check "type"
                oDP = oColl.FindPropertyByDisplayName("Item", "Type");
                if (oDP != null)
                {
                    //use "type" as node name
                    return oDP.Value.ToDisplayString();
                }
                else
                {
                    // can be null? that must be a terrible error!
                    return "Error!";
                }
            }
 
        }
 
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

And tell the xmal to bind PropertyCategories to the text of tree item.

<TextBlock Foreground="Red" Text="{Binding Path=PropertyCategories, Converter={StaticResource  NwTreeConverter}}" />

 

 

 

 

Now our tree view is:

image

looks not bad, isn’t it? A little pity is I did not find a workaround to provide more meaningful name for the Insert node like UI does. But at least, it tells the end user what the node type is.

OK, I’d say the post is completed….wait! you must be wondering how the tree item can connect to the real model item since they are bound. yeah! You are correct! see below :-)

 

private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e)
{
    ModelItem oCurrentNode= (ModelItem)e.NewValue;
    if (oCurrentNode != null)
    {
        ModelItemCollection oMC = new ModelItemCollection();
        oMC.Add(oCurrentNode);
        Document oDoc = documentControl.Document;
        oDoc.CurrentSelection.CopyFrom(oMC);
    }
}

 

image

 

I am thinking the next topic about WPF. If you have any suggestion, please share with me.


Comments

One response to “Use Navisworks API with WPF – Binding Model Hierarchy to Tree View”

  1. Wei Zhou Avatar
    Wei Zhou

    Hi Xiaodong,
    Thanks for your technical blog regarding the Navisworks .NET API. My question for you is actually from your PPT regarding the following stated information:
    “Known Limitations
    -No Mouse, Keyboard events yet
    Although derives from .NET control, these events have not been implemented
    -Some scenarios can use
    OnCurrenSelectionChanging
    OnCurrenSelectionChanged ”
    Does it mean I can’t use my mouse to manipulate the model for zoom, pan and rotate since the model is in the WPF instead of Navisworks itself? If so, do I need to input coordinate values using code for the model so that to show different angles to view? I hope this is not the truth as it will be very clumsy to apply the advantage of this WPF.
    Hope to receive your clarification.
    Kind regards
    Wei

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading