Interaction issue between PushButton.Enabled property and IExternalCommandAvailability.IsCommandAvailable callback.

Interaction issue between PushButton.Enabled property and IExternalCommandAvailability.IsCommandAvailable callback.

ol.nistratov
Contributor Contributor
334 Views
1 Reply
Message 1 of 2

Interaction issue between PushButton.Enabled property and IExternalCommandAvailability.IsCommandAvailable callback.

ol.nistratov
Contributor
Contributor

There is an interaction issue betweenPushButton.Enabled property and IExternalCommandAvailability.IsCommandAvailable check.

 

IsCommandAvailable method provide control over whether your external command is enabled or disabled and can change PushButton state.

 

IExternalCommandAvailability.IsCommandAvailable has priority on PushButton.Enabled. So if IsCommandAvailable
return false for the PushButton with Enabled property equals to true the PushButton will be desabled.

 

IExternalCommandAvailability.IsCommandAvailable callback will be called by Revit's user interface any time there is a contextual change but only for PushButton with Enabled property equals to true.

 

Change the Enabled property for the PushButton does not invoke IExternalCommandAvailability.IsCommandAvailable callback.

 

The issue:

In case if PushButton.Enabled property is managed by plugin code and can be changed by http response or as a result of long task execution without interaction with Revit context the PushButton would be stay disabled even when IsCommandAvailable returns true.

 

Steps to reproduce:

Prerequisites:

  1. PushButton.Enabled = false;
  2. Add logic to update PushButton.Enabled property to true without interaction with Revit context.
  3. Push Button.Availability ClassName set to the class that checks if the document is open.

Steps:

  1. Open Revit
  2. Open Document. The PushButton is disabled, so the IsCommandAvailable callback will not be invoked.
  3. Update PushButton.Enabled property. NOTE: Enabled property change do not invoke IsCommandAvailable callback.
  4. PushButton is still disabled.
  5. Click on Ribbon panel. PushButton will be enabled.

 

Please see code example below for details:

using System;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Linq;
using System.Reflection;
using System.Windows;

namespace RevitTestPlugin
{
    [Transaction(TransactionMode.Manual)]
    [Regeneration(RegenerationOption.Manual)]
    public class Application : IExternalApplication
    {

        public Result OnStartup(UIControlledApplication application)
        {
            var panel = CreateRibbonPanel(application);
            var assemblyLocation = Assembly.GetExecutingAssembly().Location;

            var activeUiDocument = new PushButtonData("ActiveUIDocumentButton", "  ActiveUIDocumentButton  ", assemblyLocation, "RevitTestPlugin.ActiveUiDocumentExternalCommand");
            activeUiDocument.AvailabilityClassName = "RevitTestPlugin.ActiveUiDocumentExternalCommandAvailability";

            var button = panel.AddItem(activeUiDocument) as PushButton;
            button.Enabled = false;
            button.ItemText = "Disabled";

            var timer = new System.Timers.Timer(TimeSpan.FromMinutes(5).TotalMilliseconds);
            timer.AutoReset = false;
            timer.Elapsed += (sender, args) =>
            {
                button.Enabled = true;
                button.ItemText = "Enabled";
            };

            timer.Start();
            return Result.Succeeded;
        }
        
        public Result OnShutdown(UIControlledApplication application)
        {
            return Result.Succeeded;
        }

        private RibbonPanel CreateRibbonPanel(UIControlledApplication application)
        {
            var tabName = "TestTab";
            var panelName = "TestPanel";

            application.CreateRibbonTab(tabName);
            application.CreateRibbonPanel(tabName, panelName);

            var panels = application.GetRibbonPanels(tabName);
            return panels.First(p => p.Name == panelName);
        }
    }

    [Transaction(TransactionMode.Manual)]
    public class ActiveUiDocumentExternalCommandAvailability : IExternalCommandAvailability
    {
        public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
        {
            return applicationData.ActiveUIDocument != null; ;
        }
    }

    [Transaction(TransactionMode.Manual)]
    public class ActiveUiDocumentExternalCommand : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            MessageBox.Show(nameof(ActiveUiDocumentExternalCommand) + " was clicked");
            return Result.Succeeded;
        }
    }
}

 

The question is:

  1. Can I trigger the IExternalCommandAvailability.IsCommandAvailable execution from outside code?
  2. What is the right way to work with PushButton.Enabled property and IExternalCommandAvailability.IsCommandAvailable callback in such cases?

 

Thank you for any help.

335 Views
1 Reply
Reply (1)
Message 2 of 2

ricaun
Advisor
Advisor

I don't know if it's possible to force a specific IExternalCommandAvailability.IsCommandAvailable to trigger, but I know every time your command is rendered in the panel or a context change, like a selection change the IsCommandAvailable is triggered if your command is visible to the user.

 

The RibbonItem.Enabled is related to the UI and has no idea of the existence of the IsCommandAvailable in the command.

 

Think like that I prefer to forget about the RibbonItem.Enabled and use only IsCommandAvailable. Something like this:

 

using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Threading.Tasks;

namespace RevitAddin.Commands
{
    [Transaction(TransactionMode.Manual)]
    public class CommandAvailability : IExternalCommand, IExternalCommandAvailability
    {
        public static bool IsAvailable { get; set; } = true;
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elementSet)
        {
            UIApplication uiapp = commandData.Application;

            IsAvailable = false;

            Task.Run(async () =>
            {
                await Task.Delay(1000);
                IsAvailable = true;
            });
            return Result.Succeeded;
        }

        public bool IsCommandAvailable(UIApplication applicationData, CategorySet selectedCategories)
        {
            return IsAvailable;
        }
    }
}

 

The only problem with this approach is that the IsCommandAvailable is not triggered when the IsAvailable changes to true the command look like not available, but if you select something in Revit the IsCommandAvailable gonna trigger and everything gonna works normally.

 

I still don't figure out a way good way to trigger all the IsCommandAvailable visible, or force to update/refresh all the UI in Revit.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils