How to embed a webpage in a dockable window in Revit?

How to embed a webpage in a dockable window in Revit?

mizrachi_amir2
Advocate Advocate
2,611 Views
13 Replies
Message 1 of 14

How to embed a webpage in a dockable window in Revit?

mizrachi_amir2
Advocate
Advocate

Hello,

 

I am trying to embed a webpage  link as follows in my Revit Doackable window application/Command:

https://momento360.com/e/u/1eb0c5b8c1a64ef5a946b8ed3d53982e?utm_campaign=embed&utm_source=other&head...

But I get a blank dockable window.

 

Trying to embed www.google.com works successfully, embedding google maps pops up a lot of script errors as such:

mizrahi_amir_0-1715947897937.png

 

I was following this tutorial for creating this dockable window.

Next, I will be following this advanced solution made by @ricaun:
https://github.com/ricaun-io/RevitAddin.Dockable.Example/tree/master

 

My viewer xaml file is as simple as that:

<Page x:Class="RevitAddInsWPFSample.Viewer"
             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:RevitAddInsWPFSample"
             mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="280" Title="WPF Sample" Background="White">
    <Border BorderBrush="LightGray" BorderThickness="1">
        <Grid Background="White" Margin="5" >
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <WebBrowser Grid.Row="1" Name="webbrowser" Margin="5"/>
        </Grid>
    </Border>
</Page>

 

While my .xaml.cs file contains:

    public partial class Viewer : Page, IDockablePaneProvider
    {
        // fields
        public ExternalCommandData eData = null;
        public Document doc = null;
        public UIDocument uidoc = null;

        // IDockablePaneProvider abstrat method
        public void SetupDockablePane(DockablePaneProviderData data)
        {
            // wpf object with pane's interface
            data.FrameworkElement = this as FrameworkElement;
            // initial state position
            data.InitialState = new DockablePaneState
            {
                DockPosition = DockPosition.Tabbed,
                TabBehind = DockablePanes.BuiltInDockablePanes.ProjectBrowser
            };

        }
        // constructor
        public Viewer()
        {
            InitializeComponent();
            webbrowser.Source = new Uri("https://momento360.com/e/u/1eb0c5b8c1a64ef5a946b8ed3d53982e?utm_campaign=embed&utm_source=other&heading=0&pitch=0&field-of-view=75&size=medium&display-plan=true");

        }
        // custom initiator
        public void CustomInitiator(ExternalCommandData e)
        {
            // ExternalCommandData and Doc
            eData = e;
            doc = e.Application.ActiveUIDocument.Document;
            uidoc = eData.Application.ActiveUIDocument;
        }
    }

 

Any idea how can I sort this out?

 

Thank you!

Accepted solutions (2)
2,612 Views
13 Replies
Replies (13)
Message 2 of 14

ricaun
Advisor
Advisor

I don't think that is a dockable issue. Actually the WebBrowser is really bad, does not support modern javascript.

 

Inside Revit is easier to use the CefSharp, you just need to add the reference in your project and don't need to send with your plugin, similar like the RevitAPI.dll.

 

I created a package with the Revit references with the CefSharp.

I did this sample last week:

Is a simple Wpf with some custom local HTML and some JavaScript intercommunication, but you can use a webpage as well. 😀

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 3 of 14

mizrachi_amir2
Advocate
Advocate

Hi @ricaun, you're doing and sharing great stuff!!
I also followed your video concerning cefsharp.

 

BUT, I have a naïve question - after cloning your CefWebView project from here, how do I install and run it in Revit? After building your code I have no .addin file besides the dll file. Thus the command does not get loaded into Revit.

 

Can you please explain what am I doing wrong?  How can I run your code successfully?

 

Thank you 🙂 

0 Likes
Message 4 of 14

ricaun
Advisor
Advisor

For development and load the plugin I use my plugin AppLoader, so I don't need to create/copy the .addin file to the Revit folder. The AppLoader does the job to load/unload the .dll with Revit open, so I don't waste time waiting Revit to open and close every time I need to test Revit API code.

 

The .addin is created when you run the build automation to create the bundle folder and the installation for the plugin, the .addin and PackageContent.xml is created.

 

In all my solutions that contain the Build project you can execute the build.cmd to run the automation to build each Revit version available in the project, create the .addin for each version, organize in the bundle format with the PackageContent.xml file, and create a installation to basically copy the .bundle in the Autodesk ApplicationPlugins folder.

 

That automation runs in GitHub action as well to release the installation automatically depending of the dll version.

 

Here is a full workflow with the AppLoader: My First Revit Plugin - Part 1 

 

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 5 of 14

mizrachi_amir2
Advocate
Advocate

Thanks, @ricaun - will take a look at that.
BTW, I have this xaml code with the following warning:

mizrahi_amir_0-1716279418780.png

The 3 DLLs are present:

mizrahi_amir_1-1716279565910.png

 

But code still runs successfully. Why is that? I am afraid this warning might cause other issues that I am not aware of.

 

0 Likes
Message 6 of 14

mizrachi_amir2
Advocate
Advocate

I am trying to load a webpage upon clicking on an element.
This is how my Application initializes my selection event handler:

 

public Result OnStartup(UIControlledApplication application)
{
    // Create a new dockable pane id
    DockablePaneId paneId = new DockablePaneId(new Guid("9A795EF2-5A81-4300-95AD-FF339DA10DAF"));

    // Register dockable pane
    application.RegisterDockablePane(paneId, "My Browser Pane", new MyDockablePaneProvider());

    //Creating Tab and Ribbon
...
...
...
    // Initialize the selection handler and external event
    InitializeSelectionHandler(application);
    return Result.Succeeded;

}

private void InitializeSelectionHandler(UIControlledApplication application)
{
    // Create a new instance of the browser page
    MyImage browserPage = new MyImage();

    // Initialize the event handler with the browser page
    _handler = new ElementSelectionHandler(browserPage);
    _externalEvent = ExternalEvent.Create(_handler);

    // Register the event handler to the selection changed event
    application.SelectionChanged += Application_SelectionChanges;
}

private void Application_SelectionChanges(object sender, Autodesk.Revit.UI.Events.SelectionChangedEventArgs e)
{
        _externalEvent.Raise();
}

 

 

This is my ElementSelectionHandler class:

 

public class ElementSelectionHandler : IExternalEventHandler
{
    private MyImage _browserPage;

    public ElementSelectionHandler(MyImage browserPage)
    {
        _browserPage = browserPage;
    }

    public void Execute(UIApplication app)
    {
        UIDocument uidoc = app.ActiveUIDocument;
        var selectedIds = uidoc.Selection.GetElementIds();

        if (selectedIds.Count > 0)
        {
            ElementId elementId = selectedIds.First();
            Element element = uidoc.Document.GetElement(elementId);

            string url ="https://www.mySecondLink.com";
            _browserPage.NavigateTo(url);
        }
    }

    public string GetName()
    {
        return "ElementSelectionHandler";
    }
}

 

 

And this is my Page class:

 

    public partial class MyImage : Page
    {
        // fields
        private bool _isBrowserInitialized;

        public MyImage()
        {
            InitializeComponent();
        }

        public void NavigateTo(string url)
        {
            Browser.Load(url);
        }
    }

 

 

And its xaml file:

 

Page x:Class="BuildEye.MyImage"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BuildEye"
        xmlns:cef="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
        mc:Ignorable="d">

    <Grid>
        <cef:ChromiumWebBrowser x:Name="Browser" Address="https://www.myFirstLink.com" />
    </Grid>
</Page>

 

And my DockablePaneProvider:

 

public class MyDockablePaneProvider : IDockablePaneProvider
{
    public void SetupDockablePane(DockablePaneProviderData data)
    {
        MyImage page = new MyImage();
        data.FrameworkElement = page;
        data.InitialState = new DockablePaneState
        {
            DockPosition = DockPosition.Tabbed,
            TabBehind = DockablePanes.BuiltInDockablePanes.ProjectBrowser
        };
    }
}

 

 

Running the above loads correctly the first link in my DocakblePane, but throws the following exception once I am selecting an element in Revit:

Exception thrown: 'System.Exception' in CefSharp.Wpf.dll

 

Checking with ChatGPT, I modified my NavigateTo method as follows:

public void NavigateTo(string url)
{
    //Browser.Load(url);
    Dispatcher.Invoke(() => Browser.Load(url), DispatcherPriority.Normal);
}

 

But now I get this error:

System.Exception: 'The browser has not been initialized. Load can only be called after the underlying CEF browser is initialized (CefLifeSpanHandler::OnAfterCreated).'

 

I managed to get another solution from the Chat, no error but nothing happens and the second url doesn't get loaded.

 

Can someone instruct me how to figure this out?

 

Thank you!

 

0 Likes
Message 7 of 14

ricaun
Advisor
Advisor
Accepted solution

The Dispatcher makes sense, but there is another problem.

 

In your code you have two instance of the MyImage.


One you created in the InitializeSelectionHandler and another one used by Revit in the dockable in the MyDockablePaneProvider class.

 

You need to use only one, if you update the MyDockablePaneProvider to receive a Page would work.

 

 

public class MyDockablePaneProvider : IDockablePaneProvider
{
    private readonly Page page;
    public MyDockablePaneProvider(Page page)
    {
        this.page = page;
    }

    public void SetupDockablePane(DockablePaneProviderData data)
    {
        data.FrameworkElement = page;
        data.InitialState = new DockablePaneState
        {
            DockPosition = DockPosition.Tabbed,
            TabBehind = DockablePanes.BuiltInDockablePanes.ProjectBrowser
        };
    }
}

 

 

 

And in the end something like this.

 

 

// Create a new instance of the browser page
MyImage browserPage = new MyImage();

// Register dockable pane
application.RegisterDockablePane(paneId, "My Browser Pane", new MyDockablePaneProvider(browserPage));

// Initialize the event handler with the browser page
_handler = new ElementSelectionHandler(browserPage);
_externalEvent = ExternalEvent.Create(_handler);

...

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 8 of 14

mizrachi_amir2
Advocate
Advocate

I am now trying to support also Revit versions prior to 2023, as the SelectionChanged event was only introduced in Revit 2023 API.

I followed this great answer by @FAIR59 and all seems to work good except this error that I did not manage to resolve:

System.Exception: 'The browser has not been initialized. Load can only be called after the underlying CEF browser is initialized (CefLifeSpanHandler::OnAfterCreated).'

mizrachi_amir_0-1718451810761.png

 

This is my current workflow for versions prior to Revit 2023:

1. In my Application.cs added this code snippet under OnStartup():

#if !REVIT2023_OR_GREATER
            RibbonPanel m_ribbonPanel = application.CreateRibbonPanel(tabName, "ModifyPanel");
            string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
            PushButtonData buttonData = new PushButtonData("cmd_Dummy",
                       string.Format("X"), thisAssemblyPath, "BuildEye.cmd_Dummy");
            buttonData.AvailabilityClassName = "BuildEye.CommandEnabler";
            m_ribbonPanel.AddItem(buttonData);

#endif

            rebarPanel.AddSeparator();
            // Initialize the selection handler and external event
            InitializeSelectionHandler(application);
            return Result.Succeeded;

 

        private void InitializeSelectionHandler(UIControlledApplication application)
        {
            // Create a new instance of the browser page
            MyImage browserPage = new MyImage();

            // Create a new dockable pane id
            DockablePaneId paneId = new DockablePaneId(new Guid("9A795EF2-5A81-4300-95AD-FF339DA10DAF"));

            // Register dockable pane
            application.RegisterDockablePane(paneId, "VRevit", new MyDockablePaneProvider(browserPage));


            //application.SelectionChanged += Application_SelectionChanges;
#if REVIT2023_OR_GREATER

            // Initialize the event handler with the browser page
            _handler = new ElementSelectionHandler(browserPage);
            _externalEvent = ExternalEvent.Create(_handler);

            // Register the event handler to the selection changed event
            application.SelectionChanged += Application_SelectionChanges;
#elif !REVIT2023_OR_GREATER
            application.ControlledApplication.ApplicationInitialized += ControlledApplication_ApplicationInitialized;
#endif
        }

 

        void ControlledApplication_ApplicationInitialized(object sender, Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e)
        {
            adWin.RibbonControl ribbon = adWin.ComponentManager.Ribbon;
            adWin.RibbonTab BuildEyeTAB = null;
            adWin.RibbonPanel MyPanel = null;
            adWin.RibbonItem MyButton = null;
            // find MyPanel and MyButton
            foreach (var tab in ribbon.Tabs)
            {
                if (tab.Id == "BuildEye")
                {
                    BuildEyeTAB = tab;
                    foreach (var panel in tab.Panels)
                    {
                        if (panel.Source.Title == "ModifyPanel")
                        {
                            MyPanel = panel;
                            foreach (var item in panel.Source.Items)
                            {
                                if (item.Id == "CustomCtrl_%CustomCtrl_%BuildEye%ModifyPanel%cmd_Dummy")
                                {
                                    MyButton = item;
                                    break;
                                }
                            }
                        }
                    }
                    break;
                }
            }
            if (MyPanel != null && MyButton != null && BuildEyeTAB != null)
            {
                // find a "nice"position for the button
                int position = 0;
                foreach (var item in adWin.ComponentManager.QuickAccessToolBar.Items)
                {
                    if (string.IsNullOrWhiteSpace(item.Id)) continue;
                    position++;
                    if (item.Id == "ID_REVIT_FILE_PRINT") break;
                }
                // place button on QuickAccessToolBar
                if (position < adWin.ComponentManager.QuickAccessToolBar.Items.Count)
                {
                    adWin.ComponentManager.QuickAccessToolBar.InsertStandardItem(position, MyButton);
                }
                else adWin.ComponentManager.QuickAccessToolBar.AddStandardItem(MyButton);

                // remove button from MyPanel and hide tab if no panels 
                BuildEyeTAB.Panels.Remove(MyPanel);
                if (BuildEyeTAB.Panels.Count == 0) BuildEyeTAB.IsVisible = false;
            }

        }

 

Added CommandHandler class - tried to execute ElementSelectionHandler from here but getting the error described above:

public class CommandEnabler : IExternalCommandAvailability
{
    private static ExternalEvent _externalEvent;
    private static ElementSelectionHandler _handler;
    MyImage browserPage = new MyImage();


    public bool IsCommandAvailable(UIApplication uiApp, CategorySet catSet)
    {
        // Raise the SelectionChangedEvent
        // Initialize the event handler with the browser page
        _handler = new ElementSelectionHandler(browserPage);
        _externalEvent = ExternalEvent.Create(_handler);
        _externalEvent.Raise();
        return false; // disable button
    }
}

 

Added dummy command:

using Autodesk.Revit.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuildEye.Commands
{
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]

    public class cmd_Dummy : IExternalCommand
    {

        public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
     ref string message, ElementSet elements)
        {
            return Result.Succeeded;
        }
    }
}

 

All other implementations remain as before, which are working great for Revit 2023 and above.

What am I doing wrong for older versions to work the same?

Thank you!

0 Likes
Message 9 of 14

ricaun
Advisor
Advisor

Probably is a better to use a different approach to recreate the SelectChanged event in Revit API for old versions.


Using Idling event is the most simple way to do that, and can look similar like the SelectChanged introduced in 2023.

 

Here an implementation for a class SelectionChangedWatcher that use Idling to check if the selection changed.

Is a little old and that's basically what I use in some projects.

 

Your implementation with IExternalCommandAvailability looks complicated and I don't believe is a good idea to use availability for things that is not related to availability of the command.

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 10 of 14

mizrachi_amir2
Advocate
Advocate

Thank you, @ricaun, for this reference.

As you mentioned, since this is an old code, I had to adjust it.

Under the SelectionChangedWatcher class, If I use this snippet as is:

void OnIdling(object sender, IdlingEventArgs e )
  {
    Application app = sender as Application;

    UIApplication uiApplication
      = new UIApplication( app );

  

I get this error:

mizrachi_amir_0-1718477977298.png

So I tried this approach:

Autodesk.Revit.ApplicationServices.Application app = sender as Autodesk.Revit.ApplicationServices.Application;

UIApplication uiApplication = new UIApplication(app);

 

But then I get null for the 'app' variable.

 

0 Likes
Message 11 of 14

ricaun
Advisor
Advisor

The sender in the Idling is a UIApplication, here is the reference for the events.

 

Something like this.

void OnIdling(object sender, IdlingEventArgs e )
{
    UIApplication uiapp = sender as UIApplication;
}

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 12 of 14

mizrachi_amir2
Advocate
Advocate

I am not sure I get the logic - Application OnStartup creates a new SelectionChangedWatcher instance and registers the OnSelectionChanged event:

_selectionChangedWatcher = new SelectionChangedWatcher(application);

_selectionChangedWatcher.SelectionChanged += new EventHandler(OnSelectionChanged);

 

Then, SelectionChangedWatcher  constructor registers the OnIdling event:

public SelectionChangedWatcher(
  UIControlledApplication a)
{
    a.Idling += new EventHandler<IdlingEventArgs>(OnIdling);
}

 

While the OnIdling, which is being called while application is still initiating, tries to access the ActiveUIDocument:

UIApplication uiApplication = sender as UIApplication;

ICollection<ElementId> selected = uiApplication.ActiveUIDocument.Selection.GetElementIds();

 

And the ActiveUIDocument is still null at this stage.

What am I missing in the workflow?

 

Thank you.

0 Likes
Message 13 of 14

ricaun
Advisor
Advisor
Accepted solution

Yes that the idea.

 

The Idling trigger even if you don't have a document open inside Revit. You need check if the ActiveUIDocument is null, to basically ignore the Idling code.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 14 of 14

mizrachi_amir2
Advocate
Advocate
Thank you!!
That indeed solved my problem.