Obtaining button name using events for plugins working inside of another plugin

Obtaining button name using events for plugins working inside of another plugin

yildiz.emrullah
Participant Participant
658 Views
6 Replies
Message 1 of 7

Obtaining button name using events for plugins working inside of another plugin

yildiz.emrullah
Participant
Participant

Hello, 
I am working on an app that recreates an existing plugin to run it inside our plugin which works like a plugin store. In this app, I use an xaml file to read button data and create buttons with corresponding parameters such as DisplayName. I use a node object to store button information and create buttons as I read these objects.

In the end, I am trying to make one command that needs to find the clicked button name and filter the matching command name from the list where I archived the button name and command name. Then, it needs to find the command name from the external .dll and invoke its method. But I am getting a hard time setting the architecture since Revit is crushing saying " Unrecoverable error has occurred.." I am suspecting from that the way I am unsubscribing from the click event is not healthy. My code also is not showing anything when I click for the first time.
Do you have any suggestions or alternative ways to obtain the button name as soon as I clicked? 

Here are some of my classes(ButtonInfo is my event class, Commands is the class which I assign for my buttons that I created by reading xaml. 

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using ButtonCreation.Utils;
using System;
using System.Windows;

namespace ButtonCreation
{
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
public class Commands : IExternalCommand
{
private bool _subscribedToEvent = true;
private ExternalCommandData _commandData;
public ElementSet _elements;

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
_commandData = commandData;
_elements = elements;
if (_subscribedToEvent)
{
Autodesk.Windows.ComponentManager.UIElementActivated += (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = false;
}
else
{
MessageBox.Show("not subscribed");
}
return Result.Succeeded;
Autodesk.Windows.ComponentManager.UIElementActivated -= (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = true;
}

}
}

using ArcadisRevitConnector.ArcadisRibbonItems.ArcadisButtons;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Linq;

namespace ButtonCreation.Utils
{
public static class ButtonInfo
{
public static void ClickedButtonName(object sender, Autodesk.Windows.UIElementActivatedEventArgs e, ElementSet elements)
{
string buttonText = e.Item.Text;
MessageBox.Show("Button Name: " + buttonText);

var filteredList = Station.ButtonData.Where(x => x.Item2 == buttonText).ToList();
if (!filteredList.Any())
{
return;
}

string externalCommandName = filteredList[0].Item1;
MessageBox.Show("Command Name: " + externalCommandName);

string commandNamespace = @"bbTools.UI.Ribbon.Commands";
string fullCommandName = commandNamespace + "." + externalCommandName;
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
string directoryName = System.IO.Path.GetDirectoryName(assemblyLocation);
string commandAssemblyLocation = directoryName + "\\Resources\\bbTools.dll";
Assembly externalAssembly = Assembly.LoadFrom(commandAssemblyLocation);
Type commandType = externalAssembly.GetType(fullCommandName);

if (commandType == null)
{
MessageBox.Show("Command Name is not valid");
return;
}

object command = Activator.CreateInstance(commandType);

try
{
object result = commandType.InvokeMember("ExecuteCommand",
BindingFlags.InvokeMethod,
null,
command,
new object[] { elements },
null);

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}


}
}



 

0 Likes
659 Views
6 Replies
Replies (6)
Message 2 of 7

ricaun
Advisor
Advisor

Probably when you click in the first time the event is already triggered, and I am pretty sure that if you register a delegate is not possible to unregister, create a static method to register or something.

 

Are you gonna have a hard time making work the `Autodesk.Windows.ComponentManager.UIElementActivated` is not in context with Revit, this could be the problem. If some exception happens and is not handled Revit shows the FATAL ERROR and closes.

 

Why are you trying to create something like that instead of using the Revit way to add buttons and stuff?

 

Ps: This forum has a code block, you could edit the post and put the code inside.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 3 of 7

yildiz.emrullah
Participant
Participant

Hi Henrique,
I am using another dll that creates buttons by reading information from my object called Node. Unfortunately, I don't have access to the buttons that are being created with the Revit pushbutton structure. I can only supply information to that tool that creates Pushbuttons or pulldowns or stacked items reading information from my node. This structure/template exists to make things easier while reading xaml recursively. 
I am able to select parameters for the last step which is assigning commands, button names, or icons to my buttons. Therefore, I couldn't find any other method except AdWindows.dll to obtain the button name from the button that I clicked in my plugin on runtime.

Here is my code again. Sorry for the previous code-sharing attempt. In this partial code, I have buttoninfo class with click event and subscribe method to subscribe event and unsubscribe method to unsubscribe. I am using the Unsubscribe method inside of subscribe method, and I am using subscribe method inside of the command class' execute method. (which I believe explains why I am not able to see the command name when I clicked for the first time.)I cannot subscribe to my event to onstartup or shutdown because it is a plugin inside of another plugin as a part of the app store. The user downloads the plugin that he wants and the main plugin makes it visible for you if you would like to see it on the Revit ribbon. My project is dealing with the part uploading other plugins to our main plugin. So, this is why I am trying to subscribe to another class instead of onstartup. There is no startup for it. Here are my command and buttoninfo classes:

 

 

 

 

 

 

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Windows;
using ButtonCreation.Utils;
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Linq;

namespace ButtonCreation
{
    public class Command : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            ButtonInfo.SubscribeEvent(elements);
            return Result.Succeeded;
        }
    }
}
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Windows;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Linq;

namespace ButtonCreation.Utils
{
    public class ButtonInfo
    {
        public static void ClickedButtonName(object sender, UIElementActivatedEventArgs e, ElementSet elements)
        {
            string buttonText = e.Item.Text;
            var filteredList = Station.ButtonData.Where(x => x.Item2 == buttonText).ToList();
            if (filteredList.Count > 0)
            {
                Station.commandName = filteredList[0].Item1;
                MessageBox.Show("Command name: " + Station.commandName);
                string commandNamespace = @"bbTools.UI.Ribbon.Commands";
                string fullCommandName = commandNamespace + "." + Station.commandName;
                string assemblyLocation = Assembly.GetExecutingAssembly().Location;
                string directoryName = System.IO.Path.GetDirectoryName(assemblyLocation);
                string commandAssemblyLocation = directoryName + "\\Resources\\bbTools.dll";
                Assembly externalAssembly = Assembly.LoadFrom(commandAssemblyLocation);
                MessageBox.Show("Full Command Name from the Event: "+fullCommandName);
                Type commandType = externalAssembly.GetType(fullCommandName);


                if (commandType == null)
                {
                    MessageBox.Show("Command name is not valid.");
                    return;
                }

                object command = Activator.CreateInstance(commandType);
                var result = commandType.InvokeMember("ExecuteCommand",
                                                        BindingFlags.InvokeMethod,
                                                        null,
                                                        command,
                                                        new object[] { elements },
                                                        null);

            }
            else
            {
                MessageBox.Show(buttonText);
            }
            UnsubscribeEvent(elements);
        }
        public static void UnsubscribeEvent(ElementSet elements)
        {
            Autodesk.Windows.ComponentManager.UIElementActivated -= (s, e) => ButtonInfo.ClickedButtonName(s, e, elements);
            Station.boolData = false;
        }
        public static void SubscribeEvent(ElementSet elements)
        {
            Autodesk.Windows.ComponentManager.UIElementActivated += (s, e) => ButtonInfo.ClickedButtonName(s, e, elements);
            Station.boolData = true;
        }
    }
}

 

 

 

 

 

 

Here is how I create a pushbutton in another class and pass the command name data stored in the node object.
 

 

 

 

 

 

 

string displayName = node.Children.FirstOrDefault(item => item.Type == "DisplayName").InnerText;
string formattedDisplayName = displayName.Replace("\\n", "\r");
string commandName = "";
if(node.Children.FirstOrDefault(item => item.Type == "CommandName")!= null)
{
  commandName = node.Children.FirstOrDefault(item => 
  item.Type=="CommandName").InnerText;
  Station.ButtonData.Add((commandName, formattedDisplayName));
}
// ...some gibberish and the last step
node.ndObject = (object)new ndPushButton(displayName, formattedDisplayName, Assembly.GetExecutingAssembly().Location, "ButtonCreation.Command", node.Icon, "ToolTip", "LongDescription");

 

 

 

 

 

 

Here is another station class that I use as data storage:

 

 

 

 

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ButtonCreation
{
    public static class Station
    {
        public static List<(string, string)> ButtonData = new List<(string, string)>();
        public static List<IconType> Images = new List<IconType>();
        public static string commandName = "";
        public static bool boolData = false;
    }
}

 

 

 

 

 

 

With this current structure, I am able to pass the correct commandname to the clicked event method after clicking the button second time. I can print the correct command that button needs to execute but I believe it is creating a problem when I want to invoke a method from another dll. I am pretty sure of its parameters (only "elements") are correct as well. As extra information, the external command is using an interface called Cmd which is an abstract class with an IExternalCommand interface.
Hope this answer your questions. 


@yildiz.emrullah wrote:

Hello, 
I am working on an app that recreates an existing plugin to run it inside our plugin which works like a plugin store. In this app, I use an xaml file to read button data and create buttons with corresponding parameters such as DisplayName. I use a node object to store button information and create buttons as I read these objects.

In the end, I am trying to make one command that needs to find the clicked button name and filter the matching command name from the list where I archived the button name and command name. Then, it needs to find the command name from the external .dll and invoke its method. But I am getting a hard time setting the architecture since Revit is crushing saying " Unrecoverable error has occurred.." I am suspecting from that the way I am unsubscribing from the click event is not healthy. My code also is not showing anything when I click for the first time.
Do you have any suggestions or alternative ways to obtain the button name as soon as I clicked? 

Here are some of my classes(ButtonInfo is my event class, Commands is the class which I assign for my buttons that I created by reading xaml. 

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using ButtonCreation.Utils;
using System;
using System.Windows;

namespace ButtonCreation
{
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
public class Commands : IExternalCommand
{
private bool _subscribedToEvent = true;
private ExternalCommandData _commandData;
public ElementSet _elements;

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
_commandData = commandData;
_elements = elemenzts;
if (_subscribedToEvent)
{
Autodesk.Windows.ComponentManager.UIElementActivated += (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = false;
}
else
{
MessageBox.Show("not subscribed");
}
return Result.Succeeded;
Autodesk.Windows.ComponentManager.UIElementActivated -= (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = true;
}

}
}

using ArcadisRevitConnector.ArcadisRibbonItems.ArcadisButtons;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Linq;

namespace ButtonCreation.Utils
{
public static class ButtonInfo
{
public static void ClickedButtonName(object sender, Autodesk.Windows.UIElementActivatedEventArgs e, ElementSet elements)
{
string buttonText = e.Item.Text;
MessageBox.Show("Button Name: " + buttonText);

var filteredList = Station.ButtonData.Where(x => x.Item2 == buttonText).ToList();
if (!filteredList.Any())
{
return;
}

string externalCommandName = filteredList[0].Item1;
MessageBox.Show("Command Name: " + externalCommandName);

string commandNamespace = @"bbTools.UI.Ribbon.Commands";
string fullCommandName = commandNamespace + "." + externalCommandName;
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
string directoryName = System.IO.Path.GetDirectoryName(assemblyLocation);
string commandAssemblyLocation = directoryName + "\\Resources\\bbTools.dll";
Assembly externalAssembly = Assembly.LoadFrom(commandAssemblyLocation);
Type commandType = externalAssembly.GetType(fullCommandName);

if (commandType == null)
{
MessageBox.Show("Command Name is not valid");
return;
}

object command = Activator.CreateInstance(commandType);

try
{
object result = commandType.InvokeMember("ExecuteCommand",
BindingFlags.InvokeMethod,
null,
command,
new object[] { elements },
null);

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}


}
}



string displayName = node.Children.FirstOrDefault(item => item.Type == "DisplayName").InnerText;
string formattedDisplayName = displayName.Replace("\\n", "\r");
string commandName = "";
if(node.Children.FirstOrDefault(item => item.Type == "CommandName")!= null)
{
commandName = node.Children.FirstOrDefault(item => item.Type == "CommandName").InnerText;
Station.ButtonData.Add((commandName, formattedDisplayName));
}
// Determine the size of button
bool isLargeButton = type.Value.EndsWith("LargeButton");

// Set the item type and get the corresponding icon name
node.ItemType = isLargeButton ? "CustomLargeButton" : "CustomSmallButton";
var iconName = node.Children.FirstOrDefault(item => item.Type == (isLargeButton ? "LargeIcon" : "SmallIcon")).InnerText;

//Find the corresponding icon
foreach (IconType i in Station.Images)
{
if (i.Name == iconName)
{
node.Icon = TransformImage.BitmapImageToBitmap(isLargeButton ? i.LargeBitmapImage : i.SmallBitmapImage);
break;
}
}
node.ArcadisRO = (object)new ArcadisPushButton(displayName, formattedDisplayName, Assembly.GetExecutingAssembly().Location, "ButtonCreation.Command", node.Icon, "ToolTip", "LongDescription");



@yildiz.emrullah wrote:

Hello, 
I am working on an app that recreates an existing plugin to run it inside our plugin which works like a plugin store. In this app, I use an xaml file to read button data and create buttons with corresponding parameters such as DisplayName. I use a node object to store button information and create buttons as I read these objects.

In the end, I am trying to make one command that needs to find the clicked button name and filter the matching command name from the list where I archived the button name and command name. Then, it needs to find the command name from the external .dll and invoke its method. But I am getting a hard time setting the architecture since Revit is crushing saying " Unrecoverable error has occurred.." I am suspecting from that the way I am unsubscribing from the click event is not healthy. My code also is not showing anything when I click for the first time.
Do you have any suggestions or alternative ways to obtain the button name as soon as I clicked? 

Here are some of my classes(ButtonInfo is my event class, Commands is the class which I assign for my buttons that I created by reading xaml. 

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using ButtonCreation.Utils;
using System;
using System.Windows;

namespace ButtonCreation
{
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
public class Commands : IExternalCommand
{
private bool _subscribedToEvent = true;
private ExternalCommandData _commandData;
public ElementSet _elements;

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
_commandData = commandData;
_elements = elements;
if (_subscribedToEvent)
{
Autodesk.Windows.ComponentManager.UIElementActivated += (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = false;
}
else
{
MessageBox.Show("not subscribed");
}
return Result.Succeeded;
Autodesk.Windows.ComponentManager.UIElementActivated -= (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = true;
}

}
}

using ArcadisRevitConnector.ArcadisRibbonItems.ArcadisButtons;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Linq;

namespace ButtonCreation.Utils
{
public static class ButtonInfo
{
public static void ClickedButtonName(object sender, Autodesk.Windows.UIElementActivatedEventArgs e, ElementSet elements)
{
string buttonText = e.Item.Text;
MessageBox.Show("Button Name: " + buttonText);

var filteredList = Station.ButtonData.Where(x => x.Item2 == buttonText).ToList();
if (!filteredList.Any())
{
return;
}

string externalCommandName = filteredList[0].Item1;
MessageBox.Show("Command Name: " + externalCommandName);

string commandNamespace = @"bbTools.UI.Ribbon.Commands";
string fullCommandName = commandNamespace + "." + externalCommandName;
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
string directoryName = System.IO.Path.GetDirectoryName(assemblyLocation);
string commandAssemblyLocation = directoryName + "\\Resources\\bbTools.dll";
Assembly externalAssembly = Assembly.LoadFrom(commandAssemblyLocation);
Type commandType = externalAssembly.GetType(fullCommandName);

if (commandType == null)
{
MessageBox.Show("Command Name is not valid");
return;
}

object command = Activator.CreateInstance(commandType);

try
{
object result = commandType.InvokeMember("ExecuteCommand",
BindingFlags.InvokeMethod,
null,
command,
new object[] { elements },
null);

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}


}
}



 




 


@yildiz.emrullah wrote:

Hello, 
I am working on an app that recreates an existing plugin to run it inside our plugin which works like a plugin store. In this app, I use an xaml file to read button data and create buttons with corresponding parameters such as DisplayName. I use a node object to store button information and create buttons as I read these objects.

In the end, I am trying to make one command that needs to find the clicked button name and filter the matching command name from the list where I archived the button name and command name. Then, it needs to find the command name from the external .dll and invoke its method. But I am getting a hard time setting the architecture since Revit is crushing saying " Unrecoverable error has occurred.." I am suspecting from that the way I am unsubscribing from the click event is not healthy. My code also is not showing anything when I click for the first time.
Do you have any suggestions or alternative ways to obtain the button name as soon as I clicked? 

Here are some of my classes(ButtonInfo is my event class, Commands is the class which I assign for my buttons that I created by reading xaml. 

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using ButtonCreation.Utils;
using System;
using System.Windows;

namespace ButtonCreation
{
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
public class Commands : IExternalCommand
{
private bool _subscribedToEvent = true;
private ExternalCommandData _commandData;
public ElementSet _elements;

public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
_commandData = commandData;
_elements = elements;
if (_subscribedToEvent)
{
Autodesk.Windows.ComponentManager.UIElementActivated += (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = false;
}
else
{
MessageBox.Show("not subscribed");
}
return Result.Succeeded;
Autodesk.Windows.ComponentManager.UIElementActivated -= (s, e) => ButtonInfo.ClickedButtonName(s, e, _elements);
_subscribedToEvent = true;
}

}
}

using ArcadisRevitConnector.ArcadisRibbonItems.ArcadisButtons;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml.Linq;

namespace ButtonCreation.Utils
{
public static class ButtonInfo
{
public static void ClickedButtonName(object sender, Autodesk.Windows.UIElementActivatedEventArgs e, ElementSet elements)
{
string buttonText = e.Item.Text;
MessageBox.Show("Button Name: " + buttonText);

var filteredList = Station.ButtonData.Where(x => x.Item2 == buttonText).ToList();
if (!filteredList.Any())
{
return;
}

string externalCommandName = filteredList[0].Item1;
MessageBox.Show("Command Name: " + externalCommandName);

string commandNamespace = @"bbTools.UI.Ribbon.Commands";
string fullCommandName = commandNamespace + "." + externalCommandName;
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
string directoryName = System.IO.Path.GetDirectoryName(assemblyLocation);
string commandAssemblyLocation = directoryName + "\\Resources\\bbTools.dll";
Assembly externalAssembly = Assembly.LoadFrom(commandAssemblyLocation);
Type commandType = externalAssembly.GetType(fullCommandName);

if (commandType == null)
{
MessageBox.Show("Command Name is not valid");
return;
}

object command = Activator.CreateInstance(commandType);

try
{
object result = commandType.InvokeMember("ExecuteCommand",
BindingFlags.InvokeMethod,
null,
command,
new object[] { elements },
null);

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}


}




 

Message 4 of 7

ricaun
Advisor
Advisor

I still does not understand the reason you are doing like this.

 

When you create the button on your main dll you could create a button pointing to another dll command with no problem.

 

Just make your requirements less dump.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 5 of 7

yildiz.emrullah
Participant
Participant

That way was our first intention but the main dll was throwing errors related to proxy settings. We were able to pass that error creating a new object of the external class in the command class using reflections.  Right now if I do that I am losing buttons in my ribbon which I put external dll as their assembly location with the correct command class.

Message 6 of 7

ricaun
Advisor
Advisor
What kinda of error?

If your main project has the reference of the other dll usually is easy to create a pushbutton, and without the reference as possible as well, just need the full path and name of the command.

I do not encourage you to go with the AdWindow code, looks messy and is not the intended way to execute commands.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 7 of 7

yildiz.emrullah
Participant
Participant

Hi,

Unfortunately, it is not throwing any error when I use direct external dll as the assembly location. It was directly not creating the buttons with external dll and creating others. Also, I was able to proceed with running the external command class by switching the invoke method from Execute Command to Execute which belongs to its interface class "Cmd". Right now I have an issue only with obtaining the button name if possible without adwindows.dll or I need to figure out how to set up an architecture with adwindows.dll so that I can process the button name with my first click. The second alternative, I noticed that there is a journal file located at %LOCALAPPDATA%\Autodesk\Revit\Autodesk Revit 2020\Journals. It might be a possible way to obtain button names but I don't have any experience with these files.