How to remember the visual state of plugin windows

haroon.haider39ZXL
Contributor
Contributor

How to remember the visual state of plugin windows

haroon.haider39ZXL
Contributor
Contributor

Hi Guys,

 

We have a plug in that loads buttons into the main ribbon of civil 3D, each of which open dockable windows.

 

We load our plugin in the following way:

public class Civil3DPlugin : IExtensionApplication
{
	internal static string VERSION = "4.11.3";

	private List<AutoHidePaletteSet> paletteSets = new List<AutoHidePaletteSet>();

	public void Initialize()
	{

		if (ComponentManager.Ribbon == null)
		{
			ComponentManager.ItemInitialized +=
				new EventHandler<RibbonItemEventArgs>
				  (RibbonLoadedHandler);
		}
		else
		{
			InitializeRibbonMenu();
		}
	}

	private void RibbonLoadedHandler(object sender, RibbonItemEventArgs e)
	{

		ComponentManager.ItemInitialized -=
			new EventHandler<RibbonItemEventArgs>
				(RibbonLoadedHandler);

		InitializeRibbonMenu();
	}

	public void Terminate()
	{
	}

	private void InitializeRibbonMenu()
	{
		RibbonTab tab = new RibbonTab { Title = " ", Id = " " };

		RibbonPanelSource collabPanel = new RibbonPanelSource() { Title = Utility.CollaborationPanelName };
		RibbonPanelSource modelSharingPanel = new RibbonPanelSource() { Title = Utility.ModelSharingPanelName };
		RibbonPanelSource generalPanel = new RibbonPanelSource() { Title = Utility.GeneralPanelName };

		tab.Panels.Add(new RibbonPanel { Source = collabPanel });
		tab.Panels.Add(new RibbonPanel { Source = modelSharingPanel });
		tab.Panels.Add(new RibbonPanel { Source = generalPanel });

		ICommand issuesCommand = new RelayCommand((x) => ShowDockPane(Utility.IssuesPaneTitle));
		RibbonButton issuesButton = GenerateRibbonButton(Utility.IssuesButtonName, Utility.IssuesButtonDescription, Utility.IssuesButtonTooltip, issuesCommand, Utility.Resource.ISSUES_ICON);
		collabPanel.Items.Add(issuesButton);
		InitialiseDockPane(Utility.IssuesPaneTitle, Utility.issuesId, new IssueViewerApplicationViewModel(cim, VERSION));

		// More buttons added here in the same way

		ComponentManager.Ribbon.Tabs.Add(tab);
	}

	private RibbonButton GenerateRibbonButton(string text, string description, string tooltip, ICommand command, Utility.Resource icon)
	{
		RibbonButton button = new RibbonButton
		{
			Size = RibbonItemSize.Large,
			ShowText = true,
			ShowImage = true,
			IsToolTipEnabled = true,
			Orientation = System.Windows.Controls.Orientation.Vertical
		};

		button.Text = text;
		button.Description = description;
		button.ToolTip = tooltip;
		button.CommandHandler = command;

		System.IO.Stream stream = Utility.GetResourceStream(icon);
		PngBitmapDecoder decoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
		button.LargeImage = decoder.Frames[0];

		return button;
	}

	private void InitialiseDockPane(string paletteName, Guid appId, PluginViewModelBase avm)
	{
		var pluginView = new MainView { DataContext = avm };
		var ps = new AutoHidePaletteSet(paletteName, appId)
		{
			Style = PaletteSetStyles.ShowCloseButton,
			TitleBarLocation = PaletteSetTitleBarLocation.Right
		};
		paletteSets.Add(ps);
		ps.AddVisual("Content", pluginView);
	}

	private void ShowDockPane(string paletteName)
	{
		AutoHidePaletteSet paletteSet = paletteSets.FirstOrDefault(p => p.Name == paletteName);

		if (paletteSet != null && !paletteSet.Visible)
		{
			paletteSet.Visible = true;
		}
	}
}

 

What is the best way to remember the visual state of the plugins (were they visible, where they were docked, sizes etc) so that the next time a user opens civil 3D, the plugins tabs are where they were last?

0 Likes
Reply
Accepted solutions (1)
766 Views
6 Replies
Replies (6)

norman.yuan
Mentor
Mentor

AutoCAD remembers PaletteSet's state if the PaletteSet is visible (docked or floating) when AutoCAD quits, as long as the GUID value used for creating the PaletteSet is the same. 

 

From your code, as I can see, while you intend to create multiple PaletteSet eventually, the shown code only creates one PaletteSet with a GUID value stored in Utility.issuesId property. Is this issuesId property a unique GUID value for SPECIFIC PaletteSet? If it is, that particular PaletteSet should remember its state automatically.

 

With that said, if you want to implement your own state-remember mechanism, you can handle PaletteSet's Load/Save events. Here is an example of handling Load/Save events:

 

https://adndevblog.typepad.com/autocad/2015/04/finding-if-paletteset-is-newly-created.html 

 

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes

haroon.haider39ZXL
Contributor
Contributor

Thanks Norman for taking a look at this, we put this issue on the back bench until we had the plugin logic sorted! I can confirm that the location/size/dockedstate of the plugin windows are remembered each time, but not the visibility. I would prefer to use this feature if possible. Is there something we can do to check if this is working correctly?

 

This is our current implementation:

 

namespace Plugin
{
    public class Civil3DPlugin : IExtensionApplication
    {
        internal static string VERSION = "4.11.3";
        private Civil3DInteractionManager cim;
        private List<PaletteSet> paletteSets = new List<PaletteSet>();

        public void Initialize()
        {
            cim = new Civil3DInteractionManager();

            if (ComponentManager.Ribbon == null)
            {
                ComponentManager.ItemInitialized +=
                    new EventHandler<RibbonItemEventArgs>
                      (RibbonLoadedHandler);
            }
            else
            {
                InitializeRibbonMenu();
            }
        }

        private void RibbonLoadedHandler(object sender, RibbonItemEventArgs e)
        {
            ComponentManager.ItemInitialized -=
                new EventHandler<RibbonItemEventArgs>
                    (RibbonLoadedHandler);

            InitializeRibbonMenu();
        }

        public void Terminate()
        {
        }

        private void InitializeRibbonMenu()
        {
            RibbonTab tab = new RibbonTab { Title = "Name", Id = "PluginID" };

            RibbonPanelSource collabPanel = new RibbonPanelSource() { Title = Utility.GetStringFromResources("CollaborationPanelName") };
            tab.Panels.Add(new RibbonPanel { Source = collabPanel });
			// ... other panels

            HostSoftware host = HostSoftware.CIVIL;

            ICommand issuesCommand = new RelayCommand((x) => ShowDockPane(Utility.GetStringFromResources("IssuesPaneTitle")));
            RibbonButton issuesButton = GenerateRibbonButton(Utility.GetStringFromResources("IssuesButtonName"), Utility.GetStringFromResources("IssuesButtonDescription"), Utility.GetStringFromResources("IssuesButtonTooltip"), issuesCommand, "IssuesIcon");
            collabPanel.Items.Add(issuesButton);
            InitialiseDockPane(Utility.GetStringFromResources("IssuesPaneTitle"), Utility.issuesId, new IssueViewerApplicationViewModel(cim, host, VERSION));

			// ... other pallet sets initialized here 

            ComponentManager.Ribbon.Tabs.Add(tab);
        }

        private RibbonButton GenerateRibbonButton(string text, string description, string tooltip, ICommand command, String imageName)
        {
            RibbonButton button = new RibbonButton
            {
                Size = RibbonItemSize.Large,
                ShowText = true,
                ShowImage = true,
                IsToolTipEnabled = true,
                Orientation = System.Windows.Controls.Orientation.Vertical
            };

            button.Text = text;
            button.Description = description;
            button.ToolTip = tooltip;
            button.CommandHandler = command;
          
            button.LargeImage = Utility.GetResourceBitmap(imageName);

            return button;
        }

        private void InitialiseDockPane(string paletteName, Guid appId, PluginViewModelBase avm)
        {
            var pluginView = new MainView { DataContext = avm };
            var ps = new PaletteSet(paletteName, appId)
            {
                Style = PaletteSetStyles.ShowCloseButton,
                TitleBarLocation = PaletteSetTitleBarLocation.Right
            };
            paletteSets.Add(ps);
            ps.AddVisual("Content", pluginView);
        }

        private void ShowDockPane(string paletteName)
        {
            PaletteSet paletteSet = paletteSets.FirstOrDefault(p => p.Name == paletteName);

            if (paletteSet != null && !paletteSet.Visible)
            {
                paletteSet.Visible = true;
            }
        }

    }
}

 

0 Likes

norman.yuan
Mentor
Mentor

When AutoCAD remembers PaletteSet's states (if the PaletteSet is ID'ed with GUID, of course), the state should also include Visibility. That is, if the PaletteSet is visible/open when AutoCAD is closed, The PaletteSet will be automatically opened next time AutoCAD starts. I am not sure why you did not see this behaviour.

 

From your code, I see you want to create a PaletteSet collection (even though the code shown here only actually creates one PaletteSet, maybe you simplified the code a bit?), and each Paletteset gets different GUID ID (Utility.issueId). I assume the Utility.issueId would provide different IDs depending on the current drawing when the DLL is loaded (initialized), for example, if the drawing is for particular client/project/drawing type..., then the issueId would be different, thus, only certain Paletteset/PaletteSets is/are expected to be open/visible when the DLL is loaded.

 

If this is the case, it might be better not let AutoCAD to remember the PaletteSet state, and implement your own state persistence logic, as the link showed in my previous reply.

 

I also prefer not to instantiate PaletteSet directly ( = new PaletteSet(...)), rather, it is better to ALWAYS derive custom PaletteSet from it.

 

Besides, I am not sure why you need a collection of PaletteSets instead of multiple Palettes (tabs in one PaletteSet). For example, if there is possibility that you need more than on Palette/Tab for different issues, you can initialize/instantiate all possible Palettes in the custom PaletteSet (in memory), and add/remove the relevant Palette or Palettes dynamically to/from the single PaletteSet based on the needs. I can imagine user would prefer one PaletteSet docked/floated rather than multiple ones taking significant AutoCAD's visual estate all over the screen.

Norman Yuan

Drive CAD With Code

EESignature

0 Likes

haroon.haider39ZXL
Contributor
Contributor

You are right I have removed the other palette sets that we are creating, to simplify it a little. Considering your points below:

 

Unique GUID

I have confirmed that we are indeed setting the same GUIDS to each of the pallete sets, every time Civil3D starts up (see the line below). i.e. We have persistent GUIDS for each of the pallete sets. Unfortunately we are still seeing the issue:

var ps = new PaletteSet(paletteName, appId)
{
Style = PaletteSetStyles.ShowCloseButton,
TitleBarLocation = PaletteSetTitleBarLocation.Right
};

Once the PaletteSet is created thought, I can't see any ID property on the object so I can't confirm that once generated that they actually have the GUID passed.

 

Deriving from PaletteSet

Thanks for the pointer. I simplified a little here as well, but we have a custom class deriving from PaletteSet that we use. I don't think it is interupting the save produced, as we have also tested directly with PaletteSet and see the same issue.

 

Pallete set with tabs vs multiple pallete sets

This is a good idea to be honest, but we are doing it this way to match up with other plugins we have made. Can definitley consider this in the future

0 Likes

norman.yuan
Mentor
Mentor
Accepted solution

Ah, I now understand your issue: other PaletteSet's states (position, docked or not) are restored when your DLL is loaded in next AutoCAD session, but not Visible. AutoCAD built-in PaletteSet, such as Properties, or many others used in Acad verticals (Map, C3D...) do automatically show if they were shown in previous session when AutoCAD quit. So, you want achieve the same effect.

 

It looks like this is something we programmer have to do ourselves, that is, handling Load/Save event to read/save the Visible state. Note: Save even ONLY occurs when AutoCAD is quitting, at which moment custom PaletteSet is to be destroyed; while it is interesting that Load event does not occur when the code in PaletteSet's constructor runs: it occurs when the PaletteSet.Visible is set to true the first time, and it only fire once (similar to WinForm's Shown event).

 

So, I implement following code that automatically decide show or not show a PaletteSet based on its Visible property in previous AutoCAD session:

 

This is the custom PaletteSet derived from PaletteSet class:

    public class TestPaletteSet : PaletteSet
    {
        public TestPaletteSet() : base("","",new Guid("6872759E-32F7-4C34-BB2F-6ACF97E68A8A"))
        {
            Style = PaletteSetStyles.ShowCloseButton;
            MinimumSize = new System.Drawing.Size(500, 500);

            Add("Test", new UserControl1());


            Load += (o, e) =>
              {
                  WasVisible = Convert.ToBoolean(e.ConfigurationSection.ReadProperty("WasVisible", false));
              };

            Save += (o, e) =>
              {
                  e.ConfigurationSection.WriteProperty("WasVisible", Visible);
              };
        }

        public bool WasVisible { private set; get; }
    }

 

This is the IExtensionApplication.Initialize() implementation to show/not show custom PaletteSet when DLL is loaded:

public void Initialize()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            ed.WriteMessage("\nInitializing...");
            
            _testPs = new TestPaletteSet();
            // This line is required to trigger Load event, so that the 
            // saved Visible property of previous session can be retrieved
            _testPs.Visible = true;

            // This line does not trigger Load event any more
            _testPs.Visible = _testPs.WasVisible;

            ed.WriteMessage("\n... initializing done");
        }

 

Hope this helps.

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes

haroon.haider39ZXL
Contributor
Contributor

Awesome, that fixed it, thanks Norman!

0 Likes