Author Topic: Getting an image for a ribbon button from a resource  (Read 5712 times)

0 Members and 1 Guest are viewing this topic.

Keith Brown

  • Swamp Rat
  • Posts: 601
Getting an image for a ribbon button from a resource
« on: March 22, 2013, 06:09:24 PM »
I am trying to programmatically create a ribbon button and I have been unsuccessful at setting the image.  The GetBitmap method is unable to find my image and I have been unsuccessful in finding out why.  I have the four images added as resources to my project through the resource tab on the properties page. 

Here is my code.

Code - C#: [Select]
  1.  private static void CreateRibbon()
  2.         {
  3.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  4.             try
  5.             {
  6.                 RibbonControl ribbonControl = ComponentManager.Ribbon;
  7.  
  8.                 RibbonTab tab = new RibbonTab();
  9.                 tab.Title = "Power Toys";
  10.                 tab.Id = "PowerToysID";
  11.                 ribbonControl.Tabs.Add(tab);
  12.  
  13.                 RibbonPanelSource sourcePanel = new RibbonPanelSource();
  14.                 sourcePanel.Title = "Open a Drawing";
  15.  
  16.                 RibbonPanel panel = new RibbonPanel();
  17.                 panel.Source = sourcePanel;
  18.                 tab.Panels.Add(panel);
  19.  
  20.                 ed.WriteMessage("creating button 1");
  21.                 RibbonButton button1 = new RibbonButton();
  22.                 button1.Size = RibbonItemSize.Large;
  23.                 button1.Text = string.Format("Schedules{0}And{0}Tags", Environment.NewLine);
  24.                 button1.CommandParameter = "OpenDrawingSchedulesAndTagsUSImperial ";
  25.                 button1.ShowText = true;
  26.                 button1.AllowInToolBar = true;
  27.                 button1.Image = GetBitmap("LOGO_16x16.png");
  28.                 button1.LargeImage = GetBitmap("LOGO_32x32.png");
  29.                 button1.CommandHandler = new CommandHandler();
  30.                 button1.ShowImage = true;
  31.  
  32.                 ed.WriteMessage("creating button 2");
  33.                 RibbonButton button2 = new RibbonButton();
  34.                 button2.Size = RibbonItemSize.Large;
  35.                 button2.Text = string.Format("Schedules{0}And{0}Tags", Environment.NewLine);
  36.                 button2.CommandParameter = "OpenDrawingSchedulesAndTagsUSImperial ";
  37.                 button2.ShowText = true;
  38.                 button2.AllowInToolBar = true;
  39.                 button2.Image = GetBitmap("DWG_16x16.png");
  40.                 button2.LargeImage = GetBitmap("DWG_32x32.png");
  41.                 button2.CommandHandler = new CommandHandler();
  42.                 button2.ShowImage = true;
  43.  
  44.                 RibbonSplitButton ribSplitButton = new RibbonSplitButton();
  45.                 ribSplitButton.Text = "RibbonSplitButton";
  46.                 ribSplitButton.ShowText = true;
  47.                 ribSplitButton.IsSplit = true;
  48.                 ribSplitButton.Size = RibbonItemSize.Large;
  49.                 ribSplitButton.IsSynchronizedWithCurrentItem = true;
  50.  
  51.                 ribSplitButton.Items.Add(button1);
  52.                 ribSplitButton.Items.Add(button2);
  53.                 sourcePanel.Items.Add(ribSplitButton);
  54.                 tab.IsActive = true;
  55.             }
  56.             catch(System.Exception ex)
  57.             {
  58.                 ed.WriteMessage(string.Format("{0}{1}", Environment.NewLine, ex.Message));
  59.             }
  60.         }
  61.  

And here is my GetBitmap method.

Code - C#: [Select]
  1.         static BitmapImage GetBitmap(string fileName)
  2.         {
  3.             BitmapImage bmp = new BitmapImage();
  4.  
  5.             bmp.BeginInit();
  6.             bmp.UriSource = new Uri(string.Format("pack://application:,,,/{0};component/{1}", Assembly.GetExecutingAssembly().GetName().Name, fileName));
  7.             bmp.EndInit();
  8.             return bmp;
  9.         }
  10.  

It throws an error at the bmp.EndInit.  Basically it cannot find the image.  I have checked my resource names numerous times.  Any ideas anyone?  Is there a basic step that I have missed as this is the first time I have used a embedded resource.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Getting an image for a ribbon button from a resource
« Reply #1 on: March 22, 2013, 07:23:08 PM »
I was finally able to get it to work doing something like this.


Code - C#: [Select]
  1.         private static void CreateRibbon()
  2.         {
  3.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  4.             try
  5.             {
  6.                 RibbonControl ribbonControl = ComponentManager.Ribbon;
  7.  
  8.                 RibbonTab tab = new RibbonTab();
  9.                 tab.Title = "Power Toys";
  10.                 tab.Id = "PowerToysID";
  11.                 ribbonControl.Tabs.Add(tab);
  12.  
  13.                 RibbonPanelSource sourcePanel = new RibbonPanelSource();
  14.                 sourcePanel.Title = "Open a Drawing";
  15.  
  16.                 RibbonPanel panel = new RibbonPanel();
  17.                 panel.Source = sourcePanel;
  18.                 tab.Panels.Add(panel);
  19.  
  20.                 ed.WriteMessage("creating button 1");
  21.                 RibbonButton button1 = new RibbonButton();
  22.                 button1.Size = RibbonItemSize.Large;
  23.                 button1.Text = string.Format("Schedules{0}And{0}Tags", Environment.NewLine);
  24.                 button1.CommandParameter = "OpenDrawingSchedulesAndTagsUSImperial ";
  25.                 button1.ShowText = true;
  26.                 button1.AllowInToolBar = true;
  27.                 button1.Image = LoadImage(global::PowerToys.Properties.Resources.DWG_16x16);
  28.                 button1.LargeImage = LoadImage(global::PowerToys.Properties.Resources.DWG_32x32);
  29.                 button1.CommandHandler = new CommandHandler();
  30.                 button1.ShowImage = true;
  31.  
  32.                 ed.WriteMessage("creating button 2");
  33.                 RibbonButton button2 = new RibbonButton();
  34.                 button2.Size = RibbonItemSize.Large;
  35.                 button2.Text = string.Format("New{0}from{0}Template", Environment.NewLine);
  36.                 button2.CommandParameter = "NewFromTemplateMEP ";
  37.                 button2.ShowText = true;
  38.                 button2.AllowInToolBar = true;
  39.                 button2.Image = LoadImage(global::PowerToys.Properties.Resources.LOGO_16x16);
  40.                 button2.LargeImage = LoadImage(global::PowerToys.Properties.Resources.LOGO_32x32);
  41.                 button2.CommandHandler = new CommandHandler();
  42.                 button2.ShowImage = true;
  43.  
  44.                 RibbonSplitButton ribSplitButton = new RibbonSplitButton();
  45.                 ribSplitButton.Text = "RibbonSplitButton";
  46.                 ribSplitButton.ShowText = true;
  47.                 ribSplitButton.IsSplit = true;
  48.                 ribSplitButton.Size = RibbonItemSize.Large;
  49.                 ribSplitButton.IsSynchronizedWithCurrentItem = true;
  50.  
  51.                 ribSplitButton.Items.Add(button1);
  52.                 ribSplitButton.Items.Add(button2);
  53.                 sourcePanel.Items.Add(ribSplitButton);
  54.                 tab.IsActive = true;
  55.             }
  56.             catch(System.Exception ex)
  57.             {
  58.                 ed.WriteMessage(string.Format("{0}{1}", Environment.NewLine, ex.Message));
  59.             }
  60.         }


and this

Code - C#: [Select]
  1. private static BitmapImage LoadImage(Bitmap imageToLoad)
  2.         {
  3.             BitmapImage image2;
  4.             BitmapImage image = new BitmapImage();
  5.             try
  6.             {
  7.                 Bitmap bitmap = imageToLoad;
  8.                 MemoryStream stream = new MemoryStream();
  9.                 bitmap.Save(stream, ImageFormat.Png);
  10.                 image.BeginInit();
  11.                 image.StreamSource = stream;
  12.                 image.EndInit();
  13.                 image2 = image;
  14.             }
  15.             catch (System.Exception exception1)
  16.             {
  17.                 ProjectData.SetProjectError(exception1);
  18.                 image2 = null;
  19.                 ProjectData.ClearProjectError();
  20.             }
  21.             return image2;
  22.         }
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Getting an image for a ribbon button from a resource
« Reply #2 on: March 23, 2013, 11:13:54 AM »
So up until this point I have always built my projects and just referenced the DLL from the project directory inside of visual studio.  With the above code I have finally produced a DLL that I am using the Autoloader.  While the above code works inside of the visual studio project, as soon as I move the dll to the autoloader directory it again cannot find the images.  The commands are loading fine so I know it is not an autoloader issue.

Can anyone help and possibly explain the steps that one would use to embed an image into a dll and be able to access it in code outside of the visual studio environment?  All I am trying to do is get an image to display on an image button!

Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

TheMaster

  • Guest
Re: Getting an image for a ribbon button from a resource
« Reply #3 on: March 23, 2013, 12:35:21 PM »
So up until this point I have always built my projects and just referenced the DLL from the project directory inside of visual studio.  With the above code I have finally produced a DLL that I am using the Autoloader.  While the above code works inside of the visual studio project, as soon as I move the dll to the autoloader directory it again cannot find the images.  The commands are loading fine so I know it is not an autoloader issue.

Can anyone help and possibly explain the steps that one would use to embed an image into a dll and be able to access it in code outside of the visual studio environment?  All I am trying to do is get an image to display on an image button!

Are your bitmap images embedded in your assembly as resources?

If so, try registering your assembly for demand-loading using the registry, and try that with the assembly in a different folder from acad.exe and if that doesn't work try putting it in the same folder as acad.exe.

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Getting an image for a ribbon button from a resource
« Reply #4 on: March 24, 2013, 10:23:16 AM »
The bitmaps are embeded as resources.  Additionally if I netload the assembly which I have placed on the desktop it works fine. 

I went ahead and edited the registry so that it would it demandload from the desktop and again it could not find the images.

I moved the assembly to the same directory as Autocad, modified the registry for the new location and it still could not find them.

I am beginning to think that it is the way that I have loaded the bitmaps into visual studio.  It is the first time that I have tried embedding images so it is possible that something has gone wrong.  The reason why I suspect that this might be the case is that if i look at my assembly using RedGate Reflector then the resources are located in my dll as a property.  i.e. AssemblyName.Property.  If i look at an assembly that has a embedded resource that is working then they are embedded into the assembly as AssemblyName.My.Resource. 

When I am adding an image to my project in visual studio I am going to the properties page of the assembly and selecting resources.  I then add the resource as an image and then in the solution explorer I make sure that the image's build action is set to Resource.

I should note that it appears that my assembly is loading, the commands in the assembly run fine. It just cannot find the embedded images that i am using for my ribbon.  Once it cannot find the image the add ribbon method fails and the ribbon will not display.


And just to be sure I moved my assembly to a brand new location embedded deep in a folder structure and used netload to load it and the ribbon was created correctly with no errors.  So it appears that using demand loading in one form or another is causing to not find the resources.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

TheMaster

  • Guest
Re: Getting an image for a ribbon button from a resource
« Reply #5 on: March 24, 2013, 11:08:47 AM »
I would try adding a handler to the Application.Idle event and doing the initialization there, assuming that you're doing it from your IExtensionApplication's Initialize() method.

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Getting an image for a ribbon button from a resource
« Reply #6 on: March 25, 2013, 07:57:27 AM »
Per Tony's suggestion I added the following code and it seems to have fixed it.

Code - C#: [Select]
  1.         void IExtensionApplication.Initialize()
  2.         {
  3.             Application.Idle += callback_Idle;
  4.         }
  5.  
  6.         void IExtensionApplication.Terminate()
  7.         {}
  8.  
  9.         private void callback_Idle(Object sender, EventArgs e)
  10.         {
  11.             CreateRibbon();
  12.             Application.Idle -= callback_Idle;
  13.         }

I am not quite sure why it works now and didnt work before.  Maybe you could explain it Tony?

I am also removing the event handler inside of the event callback code.  It seems to work just fine but not sure if this is good practice or not.  I did find Tony's helper class for Application.Idle and will try to refactor my code to use that instead.

Thanks for the help Tony, I would have never figured that one out on my own.
« Last Edit: March 25, 2013, 08:01:46 AM by Keith Brown »
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

owenwengerd

  • Bull Frog
  • Posts: 451
Re: Getting an image for a ribbon button from a resource
« Reply #7 on: March 25, 2013, 01:12:09 PM »
I would remove the event handler first to ensure that the event handler doesn't get called recursively.

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: Getting an image for a ribbon button from a resource
« Reply #8 on: March 25, 2013, 02:23:01 PM »
Good point Owen.  In theory it should not but...... why tempt fate.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Getting an image for a ribbon button from a resource
« Reply #9 on: March 25, 2013, 03:06:36 PM »
I would try adding a handler to the Application.Idle event and doing the initialization there, assuming that you're doing it from your IExtensionApplication's Initialize() method.

I've seen you make this comment quite frequently lately.  Thanks for sharing this bit of insight.  The nudge in the right direction is appreciated!

TheMaster

  • Guest
Re: Getting an image for a ribbon button from a resource
« Reply #10 on: March 25, 2013, 05:30:34 PM »
Per Tony's suggestion I added the following code and it seems to have fixed it.

I am not quite sure why it works now and didnt work before.  Maybe you could explain it Tony?

The reason is most-likely that the Initialize() method of your IExtensionApplication is not called in the correct execution context.  The Idle event handler is also called after the ribbon has fully-initialized, which is something you need to be careful of when dealing with the ribbon.  You should also check to see if the ribbon is even visible or loaded since there's a chance it isn't, and in that case, you need to use event handlers to tell you if and when the ribbon does become visible.

Quote
I am also removing the event handler inside of the event callback code.  It seems to work just fine but not sure if this is good practice or not. 

Yes, it's absolutely necessary to prevent the handler from firing again, since you only need it to fire once. As Owen suggests, it's a good idea to remove the event handler first (from within the handler as you're doing), before doing anything else.  That's needed so that in the unlikely event that some code you call from the handler causes AutoCAD to process additional pending input messages, your event handler won't be reentered.

« Last Edit: March 25, 2013, 05:38:52 PM by TT »