Dynamically subscribe/unsubscribe event

Ok I’m really starting to stretch my capabilities here. So in the my startup file I have subscribed to the Idling event. I also have created a dockable panel that opens when I click a button in the ribbon panel. What I want to do is only subscribe to the idling event when the panel is open, and unsubscribe when it is closed. Is this possible? I’m afraid of having a script that subscribes to the idling event and runs code associated with that all the time, even when I don’t need it to do so.

Thanks!

1 Like

Found a solution. I can simply get the dockable pane with the its guid and then check IsShown. so the Idling event is still subscribed and always checking if the pane is shown when the event fires, but I can return out of function without running the rest of the code. I think I can live with that.

1 Like

Hi @Archibum
Would you mind sharing some sample code to illustrate?
I am not very familiar with idling events and dockable panels.

For sure! Here is a cleaned up sample of a dockable pane that gets dynamically updated with the id values of whatever is selected in the document. This is built on the Dockable Pane sample in the DevTools extension with some tweaks derived from solutions to some of my other questions on this forum.

This first bit of code is the startup.py file that gets put in the root of the extension folder.

#pylint: disable=import-error,invalid-name,broad-except,superfluous-parens
#pylint: disable=unused-import,wrong-import-position,unused-argument
#pylint: disable=missing-docstring
import System
import os.path as op
from pyrevit import HOST_APP, framework, coreutils, PyRevitException
from pyrevit import revit, DB, UI
from pyrevit import forms, script
from pyrevit.framework import wpf, ObservableCollection

sample_panel_id = "1458b7cb-4dbe-4a8e-bad3-837e14b0a1ca"

selected = []
def idling_eventhandler(sender, args):
    try: dockable_pane = UI.DockablePane(UI.DockablePaneId(System.Guid(sample_panel_id)))
    except: return

    global selected

    if HOST_APP.uidoc and dockable_pane.IsShown():
        try:
            ids = sorted(HOST_APP.uidoc.Selection.GetElementIds())
            if ids:
                if ids != selected:
                    selected = ids
                    registered_panel.update_list()
            else:
                if selected:
                    selected = []
                    registered_panel.update_list()
        except Exception as e:
            print e.message

HOST_APP.uiapp.Idling += \
    framework.EventHandler[UI.Events.IdlingEventArgs](idling_eventhandler)


class _WPFPanelProvider(UI.IDockablePaneProvider):
    def __init__(self, panel_type, default_visible=True):
        self._panel_type = panel_type
        self._default_visible = default_visible
        self.panel = self._panel_type()

    def SetupDockablePane(self, data):
        data.FrameworkElement = self.panel
        data.VisibleByDefault = self._default_visible

def register_dockable_panel(panel_type, default_visible=True):
    if not issubclass(panel_type, forms.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)
    panel_provider = _WPFPanelProvider(panel_type, default_visible)
    HOST_APP.uiapp.RegisterDockablePane(
        dockable_panel_id,
        panel_type.panel_title,
        panel_provider
        ) 
    return panel_provider.panel

class DockableExample(forms.WPFPanel):
    panel_source = op.join(op.dirname(__file__), "DockablePaneSample.xaml")
    panel_title = "Dockable Pane Sample"
    panel_id = sample_panel_id
    def __init__(self):
        wpf.LoadComponent(self, self.panel_source)
        self.thread_id = framework.get_current_thread_id()
        self.selected_lb.ItemsSource = []

    
    def update_list(self):
        try:
            template_list = [forms.TemplateListItem(s.IntegerValue) for s in selected]
            self.selected_lb.ItemsSource = ObservableCollection[forms.TemplateListItem](template_list)
        except Exception as e:
            print e.message


registered_panel = register_dockable_panel(DockableExample)

This second bit of code is the XAML for the dockable panel. It goes in that same root folder. Gotta make sure the file name is matching whatever is in the above code.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Background="White">
    <Page.Resources>
        <ControlTemplate x:Key="ItemTemplate">
            <TextBlock Text="{Binding name}"
                       ToolTip="{Binding name}"
                       VerticalAlignment="Center"
                       Margin="10,0,0,0">
            </TextBlock>
        </ControlTemplate>

        <DataTemplate x:Key="ItemContainerTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Control Grid.Column="0"
                         VerticalAlignment="Center" VerticalContentAlignment="Center"
                         Template="{DynamicResource ItemTemplate}">
                </Control>
            </Grid>
        </DataTemplate>

        <ItemsPanelTemplate x:Key="ItemsPanelTemplate">
            <StackPanel />
        </ItemsPanelTemplate>
    </Page.Resources>
    <DockPanel>
        <TextBlock DockPanel.Dock="Top" 
                   Text="Selected Elements" 
                   HorizontalAlignment="Stretch" 
                   Background="#FFEEEEEE"/>
        <ListView x:Name="selected_lb" 
                  HorizontalAlignment="Stretch" 
                  ItemTemplate ="{DynamicResource ItemContainerTemplate}"
                  ItemsPanel="{DynamicResource ItemsPanelTemplate}">
        </ListView>
    </DockPanel>
</Page>

And finally you need to add a button to open the panel. Here is the code for that.

__title__ = 'Open\nPanel'
__context__ = 'zero-doc'

from pyrevit import forms

test_panel_uuid = "1458b7cb-4dbe-4a8e-bad3-837e14b0a1ca"
forms.open_dockable_panel(test_panel_uuid)

I think that’s everything and I hope I copy and pasted it in here all correctly. Give it a test and let me know if something isn’t working.

2 Likes

Great @Archibum
Finally got the chance to try it ou
I had to move things around in the startup script to get things going as the registered_panel was referenced after its use. so I moved the idling_eventhandler and its use down the line

# -*- coding: UTF-8 -*-
import System
import os.path as op
from pyrevit import HOST_APP, framework, coreutils, PyRevitException
from pyrevit import revit, DB, UI
from pyrevit import forms, script
from pyrevit.framework import wpf, ObservableCollection

sample_panel_id = "1458b7cb-4dbe-4a8e-bad3-837e14b0a1ca"

selected = []



class _WPFPanelProvider(UI.IDockablePaneProvider):
    def __init__(self, panel_type, default_visible=True):
        self._panel_type = panel_type
        self._default_visible = default_visible
        self.panel = self._panel_type()

    def SetupDockablePane(self, data):
        data.FrameworkElement = self.panel
        data.VisibleByDefault = self._default_visible

def register_dockable_panel(panel_type, default_visible=True):
    if not issubclass(panel_type, forms.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)
    panel_provider = _WPFPanelProvider(panel_type, default_visible)
    HOST_APP.uiapp.RegisterDockablePane(
        dockable_panel_id,
        panel_type.panel_title,
        panel_provider
        ) 
    return panel_provider.panel

class DockableExample(forms.WPFPanel):
    panel_source = op.join(op.dirname(__file__), "DockablePaneSample.xaml")
    panel_title = "Dockable Pane Sample"
    panel_id = sample_panel_id
    def __init__(self):
        wpf.LoadComponent(self, self.panel_source)
        self.thread_id = framework.get_current_thread_id()
        self.selected_lb.ItemsSource = []

    
    def update_list(self):
        try:
            template_list = [forms.TemplateListItem(s.IntegerValue) for s in selected]
            self.selected_lb.ItemsSource = ObservableCollection[forms.TemplateListItem](template_list)
        except Exception as e:
            print e.message


registered_panel = register_dockable_panel(DockableExample)

def idling_eventhandler(sender, args):
    try: dockable_pane = UI.DockablePane(UI.DockablePaneId(System.Guid(sample_panel_id)))
    except: return

    global selected

    if HOST_APP.uidoc and dockable_pane.IsShown():
        try:
            ids = sorted(HOST_APP.uidoc.Selection.GetElementIds())
            if ids:
                if ids != selected:
                    selected = ids
                    registered_panel.update_list()
            else:
                if selected:
                    selected = []
                    registered_panel.update_list()
        except Exception as e:
            print e.message

HOST_APP.uiapp.Idling += \
    framework.EventHandler[UI.Events.IdlingEventArgs](idling_eventhandler)
2 Likes