WPF Window inside an Inventor Add-In

WPF Window inside an Inventor Add-In

AlexFielder
Advisor Advisor
3,799 Views
16 Replies
Message 1 of 17

WPF Window inside an Inventor Add-In

AlexFielder
Advisor
Advisor

Hi all,

 

This post relates to this post by @adam.nagy so hopefully he will see and reply directly but:

 

I am attempting to create a wpf window inside of an Inventor Addin- I know I can do this with a windows form, but I found Adam's post and thought I would give it a try.

 

Inside of the Addin I added a User Control and placed the following code inside:

 

<Window x:Class="Power_Pack_For_Inventor_AddIn.SelectFeaturesWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Power_Pack_For_Inventor_AddIn"
             mc:Ignorable="d" ResizeMode="CanResize" Height="354.667" Width="457.333" BorderThickness="2" SizeToContent="WidthAndHeight" Padding="0">
    <StackPanel Background="Gray" Margin="0,0,0,0">
        <Button x:Name="button" Content="Button" VerticalAlignment="Bottom" Margin="0,0,0,0"/>
        <Border BorderBrush="OldLace" BorderThickness="10" CornerRadius="10"
            HorizontalAlignment="Center" VerticalAlignment="Center">
            <Image Source="ksp.jpg" Width="200" Height="200"
             Stretch="Fill" x:Name="blogImage" />
            <Border.RenderTransform>
                <SkewTransform CenterX="250" CenterY="0"
                       AngleX="0" AngleY="350" />
            </Border.RenderTransform>
        </Border>
        <Border Width="220" Height="300" BorderThickness="10"
            CornerRadius="10" BorderBrush="OldLace">
            <Border.Background>
                <VisualBrush Visual="{Binding ElementName=blogImage}">
                    <VisualBrush.Transform>
                        <ScaleTransform ScaleX="1" ScaleY="-1"
                            CenterX="200" CenterY="150" />
                    </VisualBrush.Transform>
                </VisualBrush>
            </Border.Background>
            <Border.OpacityMask>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0" Color="Black" />
                    <GradientStop Offset="0.3" Color="Transparent" />
                </LinearGradientBrush>
            </Border.OpacityMask>
            <Border.RenderTransform>
                <SkewTransform CenterX="260" CenterY="0"
                       AngleX="40" AngleY="350" />
            </Border.RenderTransform>
        </Border>
    </StackPanel>
</Window>

Inserted into the SelectFeaturesWindow.xaml.vb element of that Window.xaml file I have:

 

Namespace Power_Pack_For_Inventor_AddIn
    Partial Public Class SelectFeaturesWindow
        Inherits Window
        Public Sub New()
            'InitializeComponent()
        End Sub

        Private Sub button_Click(sender As Object, e As RoutedEventArgs) Handles button.Click
            MessageBox.Show("Hello World!", "Feature type selection.")
            Me.Close()
        End Sub
    End Class
End Namespace

 

Inside of my Inventor Addin I have a button which when clicked runs this code:

 

Try
	Dim oDoc As Document = g_inventorApplication.ActiveDocument
	'Show the WPF Form!
	Dim selectFeaturesFrm As New SelectFeaturesWindow
	Dim helper As New WindowInteropHelper(selectFeaturesFrm)
	helper.Owner = New IntPtr(g_inventorApplication.MainFrameHWND)
	selectFeaturesFrm.ShowDialog()
Catch ex As Exception

End Try

and all looks fine. The code builds, the addin loads and I can click the button in my ribbonbar.

 

The problem is that when the Window loads it looks like this inside of Inventor:

 

XAML Broken.PNG

 

A big ol' screen of white nothing.

 

If I look in the designer inside of Visual Studio I can clearly see it working though:

 

XAML Working.PNG

 

EDIT: As I typed this out, I changed one thing and it started working:

 

I changed the uncommented the InitializeComponent() having previously modified the class inside of the XAML from this:

<Window x:Class="SelectFeaturesWindow"

To this:

 

<Window x:Class="Power_Pack_For_Inventor_AddIn.SelectFeaturesWindow"

And lo and behold it started working.

 

I could have not bothered with this post at all now that it is working but I figured it may help others like myself who are just starting

to delve into WPF with Inventor to pay attention to these small details.

Accepted solutions (1)
3,800 Views
16 Replies
Replies (16)
Message 2 of 17

adam.nagy
Autodesk Support
Autodesk Support
Accepted solution

Hi Alex,

 

Glad you managed to figure it out and thanks for sharing it. 🙂

 

Since questions cannot be marked as solutions I'll mark my answer as such - so that others know it's solved. 

 

Cheers,



Adam Nagy
Autodesk Platform Services
0 Likes
Message 3 of 17

dba78
Advocate
Advocate

Hi, just to clarify what happened:

 

in a Visual Basic-Project you don't need to add Namespace to each class you create. the compiler will add the Default Namespace of you project.

So you had your Default-Namespace 

Power_Pack_For_Inventor_AddIn

in you project and you additionally added it to your xam.vb

so after compile your partial class' (xaml.vb) qualified name became   

Power_Pack_For_Inventor_AddIn.Power_Pack_For_Inventor_AddIn.SelectFeaturesWindow

your Xaml was compiled to

Power_Pack_For_Inventor_AddIn.SelectFeaturesWindow

They "didn't find each other" on runtime....

So yes, adding this extra Namespace to your xaml-Class Name solved the problem apparently, 

but you capsulated both in a subnamespace (xaml and xaml.vb)

 

The correct solution in this case is to remove the extra Namespace in xaml.vb

 

And finally: this is not related to Inventor, but to VB (In C# you are forced to fully qualify all your classes, so this wouldn't have popped up)

 

In WPF do not comment out the InitializeComponent() Method!!! In VB you're not forced to have the default ctor, but if you have it, the InitializeComponent calls the building of the UIElement and therefore must not be commented out!

 

Cheers,

Daniel

 

 

Message 4 of 17

Anonymous
Not applicable

Hi @adam.nagy,

 

I am using the same approach you mentioned in your article your https://adndevblog.typepad.com/manufacturing/2015/11/wpf-window-inside-an-inventor-add-in.html

but Addin(c#) doesn't show the images(.png and .jpg) dockable windows. I found some information about build event changing to resource in properties of images(https://stackoverflow.com/questions/15653105/in-my-wpf-application-my-loaded-png-logo-in-image-shows...).Could you pls help me with this?

0 Likes
Message 5 of 17

dba78
Advocate
Advocate

you may try the following:

  • add your image files as project resources (project-> properties->resources->images)
  • make sure the files will compiled as resources (select file in project explorer, 'F4' ->Build Action=Resource)
  • set 'Copy Local'=false

This way WPF finds the resource on runtime.

 

0 Likes
Message 6 of 17

Anonymous
Not applicable

Hi,

 

Thanks for your reply. I have already tried these steps you mentioned.It is now working.

0 Likes
Message 7 of 17

dba78
Advocate
Advocate

could you please paste your call to the resource in XAML?

<Image Source="..."/>

 

0 Likes
Message 8 of 17

Anonymous
Not applicable

xaml.PNGstill can't see images. I debugging addin and compile. But the result is the same.

0 Likes
Message 9 of 17

dba78
Advocate
Advocate

hi, as assumed, you have a malformed Uri in your Source-Attribute.

this should help you out:

https://stackoverflow.com/questions/5552618/how-to-reference-image-resources-in-xaml

https://stackoverflow.com/questions/9419611/how-to-refer-to-embedded-resources-from-xaml

 

BR,

Daniel

(btw: your issue is more WPF than Inventor, so it were better placed on SO)

Message 10 of 17

Anonymous
Not applicable

thx daniel first link handles the issue.

I appreciate your help.

0 Likes
Message 11 of 17

dba78
Advocate
Advocate

I'm glad I could help 🙂

 

0 Likes
Message 12 of 17

2019mmp003
Contributor
Contributor

HI @AlexFielder , I tried referring your code for my c# addin. 


SCDockWindow sc = new SCDockWindow();

Inventor.UserInterfaceManager oUserInterfaceMgr = m_inventorApplication.UserInterfaceManager;
WindowInteropHelper helper = new WindowInteropHelper(sc);
Inventor.DockableWindow oDockWindow = oUserInterfaceMgr.DockableWindows.Add(Guid.NewGuid().ToString(), "iSOlarconfig", "iSolarCOngilas");

 

SCDockWindow is a wpf page

In the underlined it shows an error indicating that we cannot convert SolarConfigurator.SCDockWindow to System.windows.window

Can I get a solution for this.

 

0 Likes
Message 13 of 17

yan.gauthier
Advocate
Advocate

Hi,

 

I have myself several WPF windows in my addin. If it doesn't matter to you that you have the ability to dock the window, here how:

 

In the button class:

  • on the click event, Create a new thread and create your window instance in it (you want your own UI thread)
  • Store the ContextDispatcher of the UI thread on a private variable (you need this to send/post to the UI thread from the Inventor Thread)
  • From the UI thread, subsbribe to events (best way I found to send data from UI thread to Inventor Thread. Just defined some custom eventArgs class to passs whatever you want)
  • Outside the new thread declaration, store the CurrentDispatcher (Inventor's). you will use it to Invoke/BeginInvoke methods to the Inventor Thread

You can set the window to be top of Inventor so you have something similar to a dockable window by making the Inventor application the owner of the window :

 

var helper = new WindowInteropHelper(WPFWindow);
                        helper.Owner = new IntPtr(AddInServer.InvApp.MainFrameHWND);
0 Likes
Message 14 of 17

2019mmp003
Contributor
Contributor

@yan.gauthier Can you please provide me some reference code for this! It would be of great help! Thanks!!

0 Likes
Message 15 of 17

2019mmp003
Contributor
Contributor

Also, when until the wpf window is open, we cannot use any other inventor buttons, how can we use both the window buttons as well as inventor options simultaneously?

0 Likes
Message 16 of 17

dba78
Advocate
Advocate

Hi,
in dockableWindow you will need a ElementHost (generally spoken, to show WPF Content in Forms-Container).
See:
https://adndevblog.typepad.com/manufacturing/2012/06/embedding-a-wpf-user-control-in-a-dockablewindo...

0 Likes
Message 17 of 17

yan.gauthier
Advocate
Advocate

Hi,

 

LoadPropForm here is my WPF window object. It follows the MVVM structure. the ViewModel contains the event to be subscribed to

 

override protected void ButtonDefinition_OnExecute(NameValueMap context)
        {

            mainThreadDispatcher = Dispatcher.CurrentDispatcher;

            // Create a thread
            if (!loadPropOpen)
            {
                //Must have an activeEditDocument to run
                if (AddInServer.InvApp.ActiveEditDocument is Document document)
                {
                    Thread newWindowThread = new Thread(new ThreadStart(() =>
                    {
                        // Create our context, and install it:
                        syncContext = new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher);
                        SynchronizationContext.SetSynchronizationContext(syncContext);

                        LoadPropForm = new LoadPropForm(AddInServer.InvApp, mainThreadDispatcher.Invoke<PropModel>(() =>
                        {
                            //Instanciating this instance loads Divalto List async
                            DivaltoLazies divaltoUtilities = DivaltoLazies.Instance;

                            //get material and treatment lists
                            using (var genikAddinDbContext = new Db.GenikAddinDbContext(Constants.GenikAddinDataBaseConnectionString))
                            {
                                //includes treatments as well
                                materials = new ConcurrentBag<Db.Material>(genikAddinDbContext.Materials.Include(x => x.Treatments).ToList());
                            }

                            return new PropModel(document, materials.ToList());  //Create new propModel from inventor Thread

                        }));
                        // When the window closes, shut down the dispatcher
                        LoadPropForm.Closed += (s, e) =>
                        {
                            mainThreadDispatcher.Invoke(() =>
                            {
                                AddInServer.InvApp.ApplicationEvents.OnActivateDocument -= ApplicationEvents_OnActivateDocument;
                            });

                            Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
                            loadPropOpen = false;

                        };

                        //When Window is loaded reset mouse cursor
                        LoadPropForm.Loaded += (s, e) =>
                        {
                            mainThreadDispatcher.Invoke(() =>
                            {
                                Mouse.OverrideCursor = null;
                            });
                        };
                        ((ViewModel)LoadPropForm.DataContext).InventorUpdated += LoadPropButton_InventorUpdated;
 
                        //Run updating task from main thread
                        ((ViewModel)LoadPropForm.DataContext).SchematikPersistantUpdated += (s, e) =>
                        {
                            mainThreadDispatcher.Invoke(() =>
                            {
                                Document doc;
                                if (AddInServer.InvApp.ActiveEditDocument.FullDocumentName.Equals(e.DocumentName, StringComparison.CurrentCultureIgnoreCase))
                                {

                                    doc = AddInServer.InvApp.ActiveEditDocument;
                                }
                                else
                                {
                                    doc = (from Document woo in AddInServer.InvApp.Documents
                                           where woo.FullDocumentName.Equals(e.DocumentName, StringComparison.CurrentCultureIgnoreCase)
                                           select woo).FirstOrDefault();
                                }

                                if (doc != null)
                                {
                                    InventorMethods.UpdateCustomProperty(e.ChangedValue, doc, e.PropertyName);
                                }
                                else
                                {
                                    throw new Exception($"la composante {System.IO.Path.GetFileName(e.DocumentName)} n'est pas ouverte.");
                                }


                                //DataControl.InventorExtension.UpdateCustomProperty(e.ChangedValue, AddInServer.InvApp.ActiveEditDocument, e.PropertyName);
                            });
                        };

                        //Print single document from main Inventor Thread
                        ((ViewModel)LoadPropForm.DataContext).PrintDrawingEvent += (s, e) =>
                        {
                            mainThreadDispatcher.Invoke(() =>
                            {
                                if (InventorMethods.CompareWithVault(e.Model.FilePath))
                                {
                                    InventorMethods.PrintSingleDrawing(e.Model, e.Settings, e.Quantity);
                                }
                            });
                        };


                        //Setting up Owner makes the windows over its owner only
                        var helper = new WindowInteropHelper(LoadPropForm);
                        helper.Owner = new IntPtr(AddInServer.InvApp.MainFrameHWND);
                        
                        
                        
                        LoadPropForm.Show();
                        //LoadPropForm.Topmost = true; //always first 
                        loadPropOpen = true;
                        // Start the Dispatcher Processing
                        System.Windows.Threading.Dispatcher.Run();

                    }));

                    // Setup and start thread as before
                    newWindowThread.SetApartmentState(ApartmentState.STA);
                    newWindowThread.IsBackground = true;
                    //Display wait cursor
                    mainThreadDispatcher.Invoke(() =>
                    {
                        Mouse.OverrideCursor = Cursors.Wait;
                    });
                    newWindowThread.Start();

                    AddInServer.InvApp.ApplicationEvents.OnActivateDocument += ApplicationEvents_OnActivateDocument;
                }
            }

        }

        private void LoadPropButton_InventorUpdated(object sender, InventorPropertyEventArgs e)
        {
            mainThreadDispatcher.Invoke(() =>
            {
                Document doc;
                if (AddInServer.InvApp.ActiveEditDocument.FullDocumentName.Equals(e.DocumentName, StringComparison.CurrentCultureIgnoreCase))
                {

                    doc = AddInServer.InvApp.ActiveEditDocument;
                }
                else
                {
                    doc = (from Document woo in AddInServer.InvApp.Documents
                           where woo.FullDocumentName.Equals(e.DocumentName, StringComparison.CurrentCultureIgnoreCase)
                           select woo).FirstOrDefault();
                }

                if (doc != null)
                {
                    InventorMethods.UpdateCustomProperty(e.ChangedValue, doc, e.PropertyName);
                }
                else
                {
                    //fire once ?
                    MessageBox.Show($"la composante: {System.IO.Path.GetFileName(e.DocumentName)}{System.Environment.NewLine}n'est pas ouverte. Les changements sont perdus.", "PropModel", MessageBoxButton.OK, MessageBoxImage.Exclamation, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly);
                }

            });
        }


        //Update LoadProp info when active document change
        private void ApplicationEvents_OnActivateDocument(_Document DocumentObject, EventTimingEnum BeforeOrAfter, NameValueMap Context, out HandlingCodeEnum HandlingCode)
        {
            HandlingCode = HandlingCodeEnum.kEventNotHandled; // Let inventor do his things

            if (BeforeOrAfter == EventTimingEnum.kAfter)
            {

                if (materials == null)
                {
                    using (var genikAddinDbContext = new Db.GenikAddinDbContext(Constants.GenikAddinDataBaseConnectionString))
                    {
                        materials = new ConcurrentBag<Db.Material>(genikAddinDbContext.Materials.Include(x => x.Treatments).ToList());
                    }
                }

                //Generate PropModel on Inventor thread, then send it to UI
                //PropModel propModel = new PropModel(DocumentObject, materialDataSource.ToList(), traitementMateriel.ToList());
                PropModel propModel = new PropModel(DocumentObject, materials.ToList());


                syncContext.Post(o =>
                {
                    ((ViewModel)LoadPropForm.DataContext).InventorUpdated -= LoadPropButton_InventorUpdated;
                    LoadPropForm.ReloadViewModel(DocumentObject, propModel);
                }, null);

                
                syncContext.Post(o => ((ViewModel)LoadPropForm.DataContext).InventorUpdated += LoadPropButton_InventorUpdated , null);

            }
            else if (BeforeOrAfter == EventTimingEnum.kBefore)
            {
                //Validate if there are pending changes
                //If yes, check if the document is still opened
                bool needToSaveDocumentChanges = false;
                string fileName = "";

                syncContext.Send(o => ((ViewModel)LoadPropForm.DataContext).CheckPendingChanges(ref needToSaveDocumentChanges, ref fileName), null);

                if (needToSaveDocumentChanges)
                {
                    //Check if filename still exists:
                    if((from Document doc in AddInServer.InvApp.Documents
                           where doc.FullDocumentName.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)
                           select doc).Any())
                    {
                        //post is fire and forget !
                        syncContext.Post(o =>
                        {
                            ((ViewModel)LoadPropForm.DataContext).Model.AcceptChanges();
                            ((ViewModel)LoadPropForm.DataContext).SchematikData.UpdateInventor();
                            ((ViewModel)LoadPropForm.DataContext).SchematikData.AcceptChanges();

                        }, null);
                    }
                    else
                    {
                        MessageBox.Show($"Le document: {System.IO.Path.GetFileNameWithoutExtension(fileName)}{System.Environment.NewLine}A été fermé. Les changements ont été perdus", "PropModel", MessageBoxButton.OK, MessageBoxImage.Exclamation, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly);
                    }

                }

            }



        }
0 Likes