Dynamically update Dockable Panel

I’ve been playing around with the dockable panel example in the devtools a little bit and I was wondering if it was possible to dynamically update the content of the panel. Say I wanted to monitor the document for when a certain element is added and when it is, add it to a listbox in the dockable panel.

I’m not sure how i would go about setting this up so any ideas would be greatly appreciated. Thanks!

1 Like

You have to use event handlers, in your case DocumentChanged Event and do your thing once it triggers.

Thanks for the input thumDer. I’ve got my script working so that the form contents update on a manual button press, but I can’t figure out how to get the even handler to work. In my code, I can initiate the event handler and it is working as intended, but I’m not sure how to trigger the update. I don’t think I can just call DockableExample.update_list() because the method is not static, but I cannot make it static because I need to reference the form with “self”. I’m still a noob when it comes to this kind of python syntax understanding, so I’m sure it’s something simple that I am not doing correctly. Here is the code:

import sys
import time
import os.path as op

from pyrevit import HOST_APP, framework
from pyrevit import revit, DB, UI
from pyrevit import forms

from pyrevit.framework import wpf, ObservableCollection


def docopened_eventhandler(sender, args):
    forms.alert("Doc Opened")

HOST_APP.app.DocumentOpened += \
    framework.EventHandler[DB.Events.DocumentOpenedEventArgs](
        docopened_eventhandler
        )


class DockableExample(forms.WPFPanel):
    panel_title = "pyRevit Dockable Panel Title"
    panel_id = "3110e336-f81c-4927-87da-4e0d30d4d64a"
    panel_source = op.join(op.dirname(__file__), "DockableSheets2.xaml")

    def __init__(self):
      wpf.LoadComponent(self, self.panel_source)
      self.thread_id = framework.get_current_thread_id()
      self.title.Text = "Sheets in Model"

    def button_refresh(self, sender, args):
      self.update_list()

    def update_list(self):
      try:
        sheets = DB.FilteredElementCollector(HOST_APP.doc).OfClass(DB.ViewSheet).WhereElementIsNotElementType().ToElements()
        sheets = [s.Title for s in sheets if not s.IsPlaceholder]
        sheets.sort()
        template_list = [forms.TemplateListItem(s) for s in sheets]
        self.list_lb.ItemsSource = ObservableCollection[forms.TemplateListItem](template_list)
      except Exception as e:
        print e.message


forms.register_dockable_panel(DockableExample)
1 Like

I’m quite sure you can get your Panel instance with the following code:
panel = HOST_APP.uiapp.GetDockablePane(UI.DockablePaneId(panel_id))
I haven’t tried it yet in pyrevit so it is only a theory. :slight_smile:

Hi @Archibum, any progress with your issue?
I am trying to auto update my stack panel but I cant get the even handler to work. But it works well with standard modeless forms.
@thumDer theory doesn’t work. DockablePane object has no attribute error appears

Is it possible to autoupdate a DockablePanel with the eventhandler?

You are right, I was superficial. I compared the DockablePanel inplementation in pyrevit with my previous addin using dockable panels, and it was slightly different. The WPF panel was a sub-class of Page AND IDockablePaneProvider and the SetupDuckablePane() method was defined in it. If I have time i might try to make a PR but in the meanwhile you could implement your own WPFPanel class and register function using the ones provided in pyrevit as a template, but with the modifications I mentioned above.

As a minimal modification on tha pyrevit.forms module you could try:

class _WPFPanelProvider(UI.IDockablePaneProvider):
    """Internal Panel provider for panels"""

    def __init__(self, panel_type, default_visible=True):
        self._panel_type = panel_type
        self._default_visible = default_visible
        # this is added:
        self.panel = self._panel_type()

    def SetupDockablePane(self, data):
        """Setup forms.WPFPanel set on this instance"""
        # TODO: need to implement panel data
        # https://apidocs.co/apps/revit/2021.1/98157ec2-ab26-6ab7-2933-d1b4160ba2b8.htm

        # instead of this:
        # data.FrameworkElement = self._panel_type()
        # try this:
        data.FrameworkElement = self.panel
        data.VisibleByDefault = self._default_visible


def register_dockable_panel(panel_type, default_visible=True):
    """Register dockable panel
    Args:
        panel_type (forms.WPFPanel): dockable panel type
        default_visible (bool, optional):
            whether panel should be visible by default
    """
    if not issubclass(panel_type, WPFPanel):
        raise PyRevitException(
            "Dockable pane must be a subclass of forms.WPFPanel"
            )

    panel_uuid = coreutils.Guid.Parse(panel_type.panel_id)
    dockable_panel_id = UI.DockablePaneId(panel_uuid)
    # this is added:
    panel_provider = _WPFPanelProvider(panel_type, default_visible)
    HOST_APP.uiapp.RegisterDockablePane(
        dockable_panel_id,
        panel_type.panel_title,
        # instead of this:
        # _WPFPanelProvider(panel_type, default_visible)
        # try this:
        panel_provider
    )
    # this is added:
    return panel_provider.panel

Hope this helps.

4 Likes

@thumDer thanks a lot for the quick answer. Your solution works great! register_dockable_panel function now returns a WPFform instance I was looking for.

1 Like

I’m glad that it helped. I’ve just submitted a pull request, so in the future the default method will return the panel instance as well.

Awesome thumDer! Thanks for looking into this further. Hopefully I can get some time to play around with this this week. Can make some really powerful tools with this

Works great thanks @thumDer!

1 Like