How to load/save visible PaletteSet

How to load/save visible PaletteSet

ricaun
Advisor Advisor
2,353 Views
20 Replies
Message 1 of 21

How to load/save visible PaletteSet

ricaun
Advisor
Advisor

I'm planing with PaletteSet and I want to remember is the user close or not the PaletteSet, so the next time AutoCAD opens the PaletteSet is in the same state as before.

 

I found the PaletteSet have the Load and Save event that have access to PalettePersistEventArgs with the ConfigurationSection.

 

I didn't found a way to get the ConfigurationSection from the PaletteSet without using the events.

 

In the end I'm using the Application.UserConfigurationManager.OpenCurrentProfile() to save and load the visibility of the PaletteSet.

 

Here is my PaletteSetUtils class to help create PaletteSet.

public static class PaletteSetUtils
{
    public static PaletteSet Create(string title, Guid guid, System.Windows.Media.Visual visual, bool defaultVisible = true)
    {
        var paletteSet = new PaletteSet(title, guid);
        paletteSet.MinimumSize = new System.Drawing.Size(300, 300);
        paletteSet.DockEnabled = DockSides.Right | DockSides.Left;
        paletteSet.Dock = DockSides.Right;
        paletteSet.Style = PaletteSetStyles.ShowAutoHideButton
                            | PaletteSetStyles.ShowCloseButton
                            | PaletteSetStyles.ShowPropertiesMenu
                            | PaletteSetStyles.Snappable;

        paletteSet.Save += PaletteSet_Save;
        paletteSet.AddVisual(title, visual);
        paletteSet.LoadVisible(defaultVisible);
        return paletteSet;
    }
    private static string GetConfigurationVisibleKey(this PaletteSet paletteSet)
    {
        return $"{paletteSet.GetType().Name}.{paletteSet.Name}.Visible";
    }
    private static void LoadVisible(this PaletteSet paletteSet, bool defaultVisible = true)
    {
        var configurationSection = Application.UserConfigurationManager.OpenCurrentProfile();
        var visible = (bool)configurationSection.ReadProperty(paletteSet.GetConfigurationVisibleKey(), defaultVisible);
        paletteSet.Visible = visible;
    }
    private static void SaveVisible(this PaletteSet paletteSet)
    {
        var configurationSection = Application.UserConfigurationManager.OpenCurrentProfile();
        configurationSection.WriteProperty(paletteSet.GetConfigurationVisibleKey(), paletteSet.Visible);
    }
    private static void PaletteSet_Save(object sender, PalettePersistEventArgs e)
    {
        var paletteSet = (PaletteSet)sender;
        paletteSet.SaveVisible();
    }
    public static void ToggleVisible(this PaletteSet paletteSet)
    {
        paletteSet.Visible = !paletteSet.Visible;
    }
}

 

With something like this:

public class AppPalette : IExtensionApplication
{
    public void Initialize()
    {
        PaletteSetUtils.Create("Test", new Guid("00000000-0000-0000-0000-000000000000"), new Grid());
    }
    public void Terminate() { }
}

 

The code works, but I would prefer to use PalettePersistEventArgs.ConfigurationSection. 

If someone know how to do it would be great.

 

Another question would be:

  • How to change the title of the PaletteSet after created.
  • How to find all PaletteSet already register inside AutoCAD.

 

Thanks

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Accepted solutions (4)
2,354 Views
20 Replies
Replies (20)
Message 2 of 21

ActivistInvestor
Mentor
Mentor

You shouldn't be creating an instance of the PalletSet class. You derive a class from it and then create an instance of that and manipulate it from within its constructor, as I showed in my previous example.

Message 3 of 21

ActivistInvestor
Mentor
Mentor
Accepted solution

A quick search of my old code turned up this simple example of how to consume the PaletteSet class. It does most of the things you're struggling to figure out how to do, and should answer most of your questions, if you run the example as-is. 

 

AutoCAD automatically saves and restores the docking state of a PaletteSet when you pass it a GUID. There is no need to do that yourself. If you run the example  as-is, you'll see that.

 

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
using Autodesk.Windows.Themes;
using System;
using System.Windows.Forms;

namespace PaletteSetExample
{

   /// <summary>
   /// Basic PaletteSet example
   /// 
   /// Consuming the PaletteSet is done by deriving
   /// a class from it, and creating an instance of
   /// the derived class. All other interaction with
   /// the PaletteSet base class is done from within
   /// the derived class, not from the outside.
   /// </summary>
   public class MyPaletteSet : PaletteSet
   {
      /// <summary>
      /// The single instance of this class
      /// </summary>
      static MyPaletteSet instance = null;

      /// <summary>
      /// The GUID that allows AutoCAD to persist this
      /// PaletteSet's settings
      /// </summary>
      static Guid guid = new Guid("{41738157-3080-4015-AB53-3ACF8067C792}");

      /// <summary>
      /// The name of the command that AutoCAD will issue
      /// to show the PaletteSet. If the PaletteSet is open
      /// when AutoCAD is last closed, when it starts up the
      /// next time, AutoCAD will issue this command to show
      /// the PaletteSet:
      /// </summary>
      const string showCommand = "SHOWMYPALETTESET";

      RichTextBox textBox;

      public MyPaletteSet() : base("MyPaletteSet", showCommand, guid)
      {
         var theme = ThemeManager.PaletteSettings.CurrentTheme;
         var userControl = new UserControl();
         textBox = new RichTextBox();
         textBox.BackColor = FromMediaColor(theme.PaletteContainerBackgroundColor);
         textBox.ForeColor = FromMediaColor(theme.ControlTextColor);
         textBox.Dock = DockStyle.Fill;
         textBox.BorderStyle = BorderStyle.None;
         userControl.Controls.Add(textBox);
         userControl.BorderStyle = BorderStyle.None;
         this.DockEnabled = DockSides.Left | DockSides.Right;
         base.Add("MyPaletteSet Example", userControl);
      }

      /// <summary>
      /// Prevent AutoCAD From stealing the focus
      /// from the RichTextBox
      /// </summary>
      public override bool KeepFocus 
      { 
         get => true; 
      }

      /// <summary>
      /// The command that shows the PaletteSet
      /// 
      /// The initial docking state of the PaletteSet
      /// is not set. The user docks the PaletteSet as
      /// they prefer, and AutoCAD will remember where
      /// it was docked and dock it in the same place
      /// the next time AutoCAD starts and shows the
      /// PaletteSet.
      /// 
      /// </summary>
      
      [CommandMethod(showCommand)]
      public static void MyPaletteSetCommand()
      {
         if(instance == null)
            instance = new MyPaletteSet();
         instance.Visible = true;
      }

      /// Convert System.Windows.Media color to System.Drawing.Color
      internal static System.Drawing.Color FromMediaColor(System.Windows.Media.Color color)
      {
         return System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B);
      }
   }
}

 

Message 4 of 21

_gile
Consultant
Consultant

Hi,

You could read this topic about AutoCAD User Interfaces.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 21

ricaun
Advisor
Advisor
Accepted solution

@ActivistInvestor

You are right, your example works, the palette stay open in the next AutoCAD section... But why? 

 

Looks like AutoCAD remember and execute the command 'SHOWMYPALETTESET' if the 'MyPaletteSet' was visible before close AutoCAD...

 

I never create a CommandMethod in my sample project, this explain the reason never works for me, and I create approach using IConfigurationSection.

 

There are some way to register a CommandMethod without using the Attribute, like RegisterCommandMethod.Create("SHOWMYPALETTESET", ()=>{ // do something });

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 6 of 21

ActivistInvestor
Mentor
Mentor

@ricaun wrote:

@ActivistInvestor

You are right, your example works, the palette stay open in the next AutoCAD section... But why? 

 

Looks like AutoCAD remember and execute the command 'SHOWMYPALETTESET' if the 'MyPaletteSet' was visible before close AutoCAD...

 

I never create a CommandMethod in my sample project, this explain the reason never works for me, and I create approach using IConfigurationSection.


I wouldn't expect a Revit programmer to be familiar with the concept of "commands", but that's the basic and most-common way that .NET plugins allow the user to access their functionality. It may be helpful to review the AutoCAD Developer's Guide for becoming familiar with the basics of AutoCAD .NET development.

 


 

There are some way to register a CommandMethod without using the Attribute, like RegisterCommandMethod.Create("SHOWMYPALETTESET", ()=>{ // do something });

 


No. The attribute is the only way to define commands. They make it easy because AutoCAD's runtime does the work of finding and registering them.

0 Likes
Message 7 of 21

daniel_cadext
Advisor
Advisor
Accepted solution

"No. The attribute is the only way to define commands"

 

There was 

namespace Autodesk.AutoCAD.Internal
public static void AddCommand(string cmdGroupName, string cmdGlobalName, string cmdLocalName, CommandFlags cmdFlags, CommandCallback func);

 

 I think this was added for IronPython and the likes

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
Message 8 of 21

ricaun
Advisor
Advisor
Accepted solution

@daniel_cadext wrote:

"No. The attribute is the only way to define commands"

 

There was 

namespace Autodesk.AutoCAD.Internal
public static void AddCommand(string cmdGroupName, string cmdGlobalName, string cmdLocalName, CommandFlags cmdFlags, CommandCallback func);

 

 I think this was added for IronPython and the likes


Great! The Autodesk.AutoCAD.Internal.Utils class have a lot of useful methods. For commands this: AddCommand, IsCommandDefined and RemoveCommand.

 

Here is my utility class to create commands on the fly.

using Autodesk.AutoCAD.Runtime;

public static class CommandUtils
{
    private const string CommandGroup = "CommandGroup";

    public static bool IsCommandDefined(string commandName)
    {
        return Autodesk.AutoCAD.Internal.Utils.IsCommandDefined(commandName);
    }
    public static bool AddCommand(string commandName, Autodesk.AutoCAD.Internal.CommandCallback action)
    {
        return AddCommand(CommandGroup, commandName, CommandFlags.Modal, action);
    }
    public static bool AddCommand(string commandName, CommandFlags commandFlags, Autodesk.AutoCAD.Internal.CommandCallback action)
    {
        return AddCommand(CommandGroup, commandName, commandFlags, action);
    }
    public static bool AddCommand(string commandGroup, string commandName, CommandFlags commandFlags, Autodesk.AutoCAD.Internal.CommandCallback action)
    {
        Autodesk.AutoCAD.Internal.Utils.AddCommand(commandGroup, commandName, commandName, commandFlags, action);
        return IsCommandDefined(commandName);
    }
    public static bool RemoveCommand(string commandGroup, string commandName)
    {
        if (IsCommandDefined(commandName))
        {
            Autodesk.AutoCAD.Internal.Utils.RemoveCommand(commandGroup, commandName);
            return true;
        }
        return false;
    }
    public static bool RemoveCommand(string commandName)
    {
        return RemoveCommand(CommandGroup, commandName);
    }
}

 

And I can update my PaletteSetUtils to remove the ConfigurationSection and use CommandUtils.AddCommand create the show/hide palette without using an attribute. 🤗

 

public static class PaletteSetUtils
{
    public static PaletteSet Create(string title, Guid guid, System.Windows.Media.Visual visual)
    {
        var commandName = "Show_Palette_" + title.Replace(" ", "_").Replace("-", "_");
        return Create(commandName.ToUpperInvariant(), title, guid, visual);
    }
    public static PaletteSet Create(string commandName, string title, Guid guid, System.Windows.Media.Visual visual)
    {
        var paletteSet = new PaletteSet(title, commandName, guid);
        paletteSet.MinimumSize = new System.Drawing.Size(300, 300);
        paletteSet.DockEnabled = DockSides.Right | DockSides.Left;
        paletteSet.Dock = DockSides.Right;
        paletteSet.Style = PaletteSetStyles.ShowAutoHideButton
                            | PaletteSetStyles.ShowCloseButton
                            | PaletteSetStyles.ShowPropertiesMenu
                            | PaletteSetStyles.Snappable;

        paletteSet.KeepFocus = true;
        paletteSet.AddVisual(title, visual);
        CommandUtils.AddCommand(commandName, () => { paletteSet.ToggleVisible(); });
        return paletteSet;
    }
    public static void ToggleVisible(this PaletteSet paletteSet)
    {
        paletteSet.Visible = !paletteSet.Visible;
    }
}

 

Now looks better.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 9 of 21

ricaun
Advisor
Advisor

I still have a question related to PaletteSet. 

 

How can I update the title, looks like AutoCAD records the title and after creating the PaletteSet. 

 

I didn't find a way to rename the title or unregister the PaletteSet using the the Guid. Anyone have some idea how to do it?

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 10 of 21

ActivistInvestor
Mentor
Mentor

The command needs to remain defined so that the user can show the pallet set there also should be another command to hide it.

 

I don't understand your aversion to using attributes to Define commands since they are pretty much the standard way of doing that and relying on undocumented apis is not really recommended..

 

You really don't have any legitimate reason to not use an attribute since the command must exist until AutoCAD closes.

 

I purposely did not tell you about those apis because they are not documented and because you don't really need them. Those apis are used by autocad's macro recorder to Define commands that execute recorded macros.

0 Likes
Message 11 of 21

ActivistInvestor
Mentor
Mentor

@daniel_cadext wrote:

"No. The attribute is the only way to define commands"

 

There was 

See my reply to the OP

0 Likes
Message 12 of 21

ricaun
Advisor
Advisor

@ActivistInvestor wrote:

The command needs to remain defined so that the user can show the pallet set there also should be another command to hide it.

 

I don't understand your aversion to using attributes to Define commands since they are pretty much the standard way of doing that and relying on undocumented apis is not really recommended..

 

You really don't have any legitimate reason to not use an attribute since the command must exist until AutoCAD closes.

 

I purposely did not tell you about those apis because they are not documented and because you don't really need them. Those apis are used by autocad's macro recorder to Define commands that execute recorded macros.


I want to create a library for AutoCAD similar like I have Revit (https://github.com/ricaun-io/ricaun.Revit.UI) to make easier to create Ribbons and UI stuff without the need create extra code in the main plugin.

 

And in the case of PaletteSet is much convenient to just run PaletteSetUtils.Create to create a custom palette, without the need to create a extra command to control if the palette is visible or not. I could have a single command to toggle the Palette visibility, but now that I don't need a method with the attribute I can create one command for hide and another for show. 😊

 

PaletteSetUtils.Create("SHOW_MY_PALETTE", "My palette Title", new Guid("00000000-0000-0000-0000-000000000000"), new MyCustomPalette());

 

And create command is really useful in some case like if I need to create 100 commands on the fly.

 

for (int i = 0; i < 100; i++)
{
    var commandNumber = i;
    CommandUtils.AddCommand($"Add_Command_{commandNumber}", () => { Debug.WriteLine(commandNumber); });
}

 

I already found some really internal stuff like Autodesk.AutoCAD.ApplicationServices.ExtensionLoader that I was messing to inject a command, was not working very well.

The Autodesk.AutoCAD.Internal.Utils.AddCommand is public class and works like a charm.

 

And I found Autodesk.AutoCAD.Runtime.ExtensionLoader.Load(fileName) that is probably the same thing as the NETLOAD command.

 

So you can share internal stuff about AutoCAD API. 😊

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 13 of 21

ActivistInvestor
Mentor
Mentor

@ricaun wrote:


I want to create a library for AutoCAD similar like I have Revit (https://github.com/ricaun-io/ricaun.Revit.UI) to make easier to create Ribbons and UI stuff without the need create extra code in the main plugin.

 

 

Your code library (following a Fluent API style) looks like a collection of one-line extension methods that do nothing except set a property, and return the argument.

 

Those extension methods serve no useful purpose.

 

For example, using your extension methods:

var textBox = new RibbonTextBox()
    .SetSelectTextOnFocus()
    .SetShowImageAsButton()
    .SetPromptText("Search")
    .SetValue("Search")
    .SetImage(Icons.Icon);

 

And here is my code not using any third-party library (and using object-initializer syntax):

var textBox = new RibbonTextBox()
{
   SelectTextOnFocus = true,  
   ShowImageAsButton = true,
   PromptText = "Search",
   Value = "Search",
   Image = Icons.Icon
};

 

So, what is the purpose of your extension methods used above?  All that they do is trade the assignment operator (=) for parenthesis (). My code above which uses no 3rd-party library is just as compact as yours, and its function is clearer rather than obfuscated. I don't use external libraries (including my own) only for the sake of using them. While the Fluent API-style of programming has its place (e.g., Linq), property assignment is not one of them.

 

All of what I've seen from you thus far seems suggest that you aren't all that familiar with the OOP-way of reusing code through subclassing (for example, creating instances of the PaletteSet class directly, verses defining a subclass as I had shown earlier).

 

For example, if I have to create many RibbonTextBoxes that all have a subset of their properties set to the same values, I can do this:

public class MyCustomRibbonTextbox : RibbonTextBox
{
   public MyCustomRibbonTextBox()
   {
      this.SelectTextOnFocus = true;
      this.ShowImageAsButton = true;
      this.PromptText = "Search";
      this.Value = "Search";
      this.Image = Icons.Icon;
   }
}

 

And I would only have to do this to create an instance with its properties set accordingly:

   ribbpnPane1.Add(new MyCustomRibbonTextBox());

 

So, I can't come up with any other reason that explains what purpose there is to a collection of one-line extension methods that do nothing except set a property value. IMO, they are self-defeating because they actually create as much or more code than they eliminate. They are like a solution that is looking for a problem.

0 Likes
Message 14 of 21

ricaun
Advisor
Advisor

@ActivistInvestor wrote:

Your code library looks like a collection of one-line extension methods that do nothing except set a property, and return the argument.


Yes, Revit is a little different to work with Ribbons.

 

You don't have have access the Autodesk.Windows.RibbonTextBox class, you need to create the Autodesk.Revit.UI.TextBox class and that is added inside the Autodesk.Revit.UI.RibbonPanel, that you create using the CreateRibbonPanel method.

 

And is possible to use reflection to get the internal Autodesk.Windows.RibbonTextBox and Autodesk.Windows.RibbonPanel. That's the main reason the extension make sense to access some internal proprieties that is not expose in the main Autodesk.Revit.UI class.

 

Because I use in all my plugins the ricaun.Revit.UI library, if I update the library to support automatic image change base in the light or dark theme, all plugin gonna have this feature by default, no extra code required, just update the library. That happen in Revit 2024 that the Dark theme was introduced in Revit.

 

So a similar library with similar features for AutoCAD make sense for me.

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 15 of 21

ActivistInvestor
Mentor
Mentor

My comments aren't really about the RibbonTextBox or whether it is accessible, the examples I showed used that type as an example, but could have used any other type that have similar extension methods that don't do anything except set a property.

 

In the example, there is little-to-no advantage to using the fluent extension methods.

0 Likes
Message 16 of 21

ricaun
Advisor
Advisor

You are right in your example there is no advantage to use fluent extensions.

 

The idea is to only use fluent extensions, and even that is setting a simple proprietary internally make sense in the fluent API way.

 

This is how I create a tab and panel with a button.

 

ricaun_1-1747336229559.png

 

public class AppTest : ExtensionApplication
{
    public const string RibbonTab = "Tab";
    public const string RibbonPanel = "Panel";
    public override void OnStartup(RibbonControl ribbonControl)
    {
        var ribbonPanel = ribbonControl.CreateOrSelectPanel(RibbonPanel, RibbonTab);

        ribbonPanel.CreateButton("Change\rTheme")
            .SetDescription("Description")
            .SetLargeImage("Resources/Cube-Grey-Light.tiff")
            .SetToolTipImage("Resources/Cube-Grey-Light.tiff")
            .SetToolTip("ToolTip")
            .SetCommand(() => {
                Application.SetSystemVariable("COLORTHEME", 1 ^ (short)Application.GetSystemVariable("COLORTHEME"));
            });
    }
    public override void OnShutdown(RibbonControl ribbonControl)
    {
            
    }
}

 

Looks so simple that I don't need to know or care what kind of Autodesk.Windows.RibbonItem the CreateButton is using, just call CreateButton in the RibbonPanel to create a button and use Set to change the proprieties of the button.

 

The fluent API way looks easier for me.

 

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 17 of 21

ActivistInvestor
Mentor
Mentor

@ricaun wrote:

 

The fluent API way looks easier for me.

 

I can't see how it's easier to have to define an extension method for every property of every RibbonItem-based type, that does nothing other than set's the property's value, only to be able to consistently use Fluent-style code.

 

Thanks, but no thanks.  I'll stick with stock C#

Message 18 of 21

Medithaibet
Contributor
Contributor

@ActivistInvestor 
hello, I also want to ask the follow question:
Because during the testing process, the title needs to be frequently modified, I also hope to be able to find the specific location of its registration in the registry, so as to delete the registration information for debugging and checking whether the modification was successful.

So I also want to know how to manually delete the previously saved information to facilitate Debug testing. If i keep modifying the path of the GUID every time, it is often easy to forget. After the test is completed, the actual running ID will be the fixed one.

Think you first!

0 Likes
Message 19 of 21

daniel_cadext
Advisor
Advisor

the palette GUID is stored in the profile, you should be able to setup a developer profile, or, don't use a GUID.

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
0 Likes
Message 20 of 21

ActivistInvestor
Mentor
Mentor

The settings for your pallet set are stored in the file FixedProfile.aws.

 

It's an XML file so you could read it and remove your PalleteSet's entry from the file if you need reset the settings.