C# – Print all MS Office 2010/2013 ImageMso to files

Last month I received a project for converting an Access application from version 97 to version 2010. Thanks to good MS Access I don’t have to change a lot of things. Just replace some not working components like ListView, TreeView, ImageList with the new ones and edit some code behind so that the program works. For example ListView component needs header definition if we want to access it according to columns. There is only one big problem which takes me a lot of time is the menu. Menu is still supported in Office 2010 but with a lot of limitations because he is now replaced by ribbon bar.

1. Introduction

I must rewrite old menu completely in XML code to display it as ribbon in XML. Just two days for researching how ribbon bar works, I can achieve the requirements. Below is a small quotation of the complete code

<group id="customGroupDatei" label="Datei">
	<button id="customButton2" label="Projekt öffnen" size="normal" onAction="RibbonProjektOeffnen" imageMso="BlogOpenExisting" />
	<button id="customButton3" label="Projektinformation" size="normal" onAction="RibbonProjektInformationen" imageMso="BuildingBlockProperties" />
	<button idMso="FilePrintPreview"/>
	<button idMso="FilePrintQuick" label="Drucken"/>
	<button id="customButton10" label="Berichtswesen" size="normal" onAction="RibbonBerichtswesen" imageMso="BuildingBlocksOrganizer" />
	<button idMso="FileCloseDatabase" label="Beenden"/>
</group>

If you want how to customize your ribbon bar in Office, you can read this book RibbonX: Customizing the Office 2007 Ribbon
As you can see that in XML code I can define the name, label, size, action and image of each button. Name, label and size are easy. I just copy from the old menu to new ribbon. The action is same too. There is only one annoyed thing is the images of menu. I don’t have any documentation about which icon ids were used in last version. Therefore I must find old icons in new icon gallery by searching icon by icon because I don’t want to make a big change on menu. The user will hardly find the old functions if I change all of them. That is a high time-consumed work, if you look at the Icon Gallery which Microsoft provides

http://www.microsoft.com/download/en/details.aspx?id=21103

The icons were host on big button with description of theirs Id. We cannot search in this gallery.
Let’s calculate: Old menu has about 40 Icons. Icon gallery of Office has about 2000 Icons. That means I must compare 40*2000 times. Each time takes in average 1 second. For complete comparison, I need 80000 seconds = 1333 minutes = 22 hours. 3 days non-stop working, just looking at computer monitor and I will finish my job. No way!
I give this job to a student and do other important things but what should I do when I received another project like this? I don’t want to give the students stress again with this boring job. Therefore I spend my free time to write a small code to export all available icons to files whose name is the icons identification so that I can search them easily. To list all available icons of Office 2010, you must download the gallery on the link above. Change the .docx file to .zip and you’ll find a file name customUI14.xml which contains all of icon identifiers.

<group id="group0" >
	<topItems>
		<layoutContainer id="layout0" layoutChildren="horizontal" expand="neither" >
			<button id="image0" imageMso="_0" style="large" label="_0" />
			<button id="image1" imageMso="_0PercentComplete" style="large" label="_0PercentComplete" />
			<button id="image2" imageMso="_1" style="large" label="_1" />
			<button id="image3" imageMso="_100PercentComplete" style="large" label="_100PercentComplete" />
			<button id="image4" imageMso="_2" style="large" label="_2" />
			<button id="image5" imageMso="_25PercentComplete" style="large" label="_25PercentComplete" />
		</layoutContainer>
	</topItems>
</group>

Ok, now what I must do is reading this file, parse all identifications, extract the images and save them to files

try
{
	if (Directory.Exists("Images"))
		Directory.Delete("Images", true);
	Directory.CreateDirectory("Images");
	string content = File.ReadAllText("customUI14.xml");
	Regex regex = new Regex("imageMso=\"(.*?)\" ");
	foreach (Match match in regex.Matches(content))
	{
		ConvertPixelByPixel(Application.CommandBars.GetImageMso(match.Groups[1].Value, 32, 32)).Save("Images\\" + match.Groups[1].Value + ".png", ImageFormat.Png);
	}
}
catch (Exception ex)
{
	MessageBox.Show(ex.Message);
}

So the code structure is pretty simple but there is two important things that I would like to discuss.
First, the GetImageMso receives identifier for the control and its dimensions specified by width and height. This function works only in Office environment. That means only when we run this code in Office Add-In or in VBA Code, we can get the result back. If not, we’ll have an exception. We cannot create an Office application through Interop and then call this function from that application. Therefore I must create an Office Add-Ins project and use this code from this. Here is part of code to create/remove Add-In menu

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
	AddMenuBar();
}

private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
	RemoveMenubar();
}

private void AddMenuBar()
{ 
	try
	{                
		Office.CommandBarPopup foundMenu = (Office.CommandBarPopup)
			this.Application.CommandBars.ActiveMenuBar.
			FindControl(Office.MsoControlType.msoControlPopup,
			missing, menuTag, true, true);
		while (foundMenu != null)
		{
			foundMenu.Delete(true);
		}
		menuBar = this.Application.CommandBars.ActiveMenuBar;
		newMenuBar = (Office.CommandBarPopup)menuBar.Controls.Add(
			Office.MsoControlType.msoControlPopup, missing,
			missing, missing, false);
		if (newMenuBar != null)
		{
			newMenuBar.Caption = "Print ImageMso To HTML";
			newMenuBar.Tag = menuTag;
			btnPrint = (Office.CommandBarButton)newMenuBar.Controls.
			Add(Office.MsoControlType.msoControlButton, missing,
				missing, 1, true);
			btnPrint.Style = Office.MsoButtonStyle.
				msoButtonIconAndCaption;
			btnPrint.Caption = "Print";
			btnPrint.FaceId = 610;
			btnPrint.Click += new Office._CommandBarButtonEvents_ClickEventHandler(btnPrint_Click);
			newMenuBar.Visible = true;
		}                
	}
	catch (Exception ex)
	{
		//This MessageBox is visible if there is an error
		System.Windows.Forms.MessageBox.Show("Error: " + ex.Message.ToString(), "Error Message Box", MessageBoxButtons.OK, 
		MessageBoxIcon.Exclamation);
	}
}

private void RemoveMenubar()
{
	// If the menu already exists, remove it.
	try
	{
		Office.CommandBarPopup foundMenu = (Office.CommandBarPopup)
			this.Application.CommandBars.ActiveMenuBar.
			FindControl(Office.MsoControlType.msoControlPopup,
			missing, menuTag, true, true);
		if (foundMenu != null)
		{
			foundMenu.Delete(true);
		}
	}
	catch (Exception ex)
	{
		System.Windows.Forms.MessageBox.Show(ex.Message);
	}
}

Second, GetImageMso returns IPictureDisp which is a COM type. I must convert it to normal Image/Bitmap (CLR Type) so that I can save them to hard disk because indeed I don’t know how to save IPictureDisp to hard disk. The function ConvertPixelByPixel helps me to convert IPictureDisp to Bitmap without losing Alpha channel and therefore the icons looks beautiful as they are in the gallery.

public static Bitmap ConvertPixelByPixel(IPictureDisp ipd)
{
	DIBSECTION dibsection = new DIBSECTION();
	GetObjectDIBSection((IntPtr)ipd.Handle, Marshal.SizeOf(dibsection), ref dibsection);
	int width = dibsection.dsBm.bmWidth;
	int height = dibsection.dsBm.bmHeight;
	Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
	unsafe
	{
		RGBQUAD* pBits = (RGBQUAD*)(void*)dibsection.dsBm.bmBits;
		for (int x = 0; x < dibsection.dsBmih.biWidth; x++)
			for (int y = 0; y < dibsection.dsBmih.biHeight; y++)
			{
				int offset = y * dibsection.dsBmih.biWidth + x;
				if (pBits[offset].rgbReserved != 0)
				{
					bitmap.SetPixel(x, y, Color.FromArgb(pBits[offset].rgbReserved, pBits[offset].rgbRed, pBits[offset].rgbGreen, pBits[offset].rgbBlue));
				}
				else
				{
					bitmap.SetPixel(x, y, Color.FromArgb(255, pBits[offset].rgbRed, pBits[offset].rgbGreen, pBits[offset].rgbBlue));
				}
			}
	}
	return bitmap;
}

So from now on, I have a list of icons in image files whose names are their identifiers and can easily search to what I want.

2. Download

2.1 Office 2010 icons

Mirror 1: “Office 2010 Icon Gallery Files“.
Mirror 2: “Office 2010 Icon Gallery Files“.

2.2 Office 2013 icons

Mirror 1: Office 2013 Icon Gallery Files
Mirror 2: Office 2013 Icon Gallery Files

2.3 Source code

Source code: https://bitbucket.org/hintdesk/dotnet-c-print-all-ms-office-imagemso-to-files

3. Updates

3.1 Update 06.03.2013

If you want to get icons from Office 2013, you can’t use the project above because it’s a Word 2010 add-in. You can use this project instead Print ImageMso To HTML 2013. This project can be compiled but I can’t be sure if it really works. Fix error yourself if there’s any ;). The new project uses “Word 2013 AddIn” template with Visual Studio 2013

Word 2013 AddIn Template

To get this template available in Visual Studio you have to install “Microsoft Office Developer Tools for Visual Studio” with Web Platform Installer

Microsoft Office Developer Tools for Visual Studio with Web Platform Installer

3.2 Update 07.08.2015

Link for downloading Office 2013 Icons.

18 thoughts on “C# – Print all MS Office 2010/2013 ImageMso to files”

  1. @Brent: Remove the “else”-clause from this if-clause and check if it works


    if (pBits[offset].rgbReserved != 0)
    {
    bitmap.SetPixel(x, y, Color.FromArgb(pBits[offset].rgbReserved, pBits[offset].rgbRed, pBits[offset].rgbGreen, pBits[offset].rgbBlue));
    }
    else
    {
    bitmap.SetPixel(x, y, Color.FromArgb(255, pBits[offset].rgbRed, pBits[offset].rgbGreen, pBits[offset].rgbBlue));
    }

  2. Hey Rongchaua, Megaupload has been shut down by the FBI and DoJ. Is there any chance you can replace the gallery link with mediafire or some other still accessible source?

  3. It appears that all images are 32×32 — aren’t a lot of the images 16×16 which are used on the right-click, context menus?

  4. @Peter: I don’t have Office 2013 to extract icon for you. But I think do the steps above for Office 2003, it will work too.

  5. Thanks for the reply. When I try to run the code in VS 2012 I get a message “You cannot debug or run this project, because the required version of the Microsoft Office application is not installed.”

    It seems as if the compiler knows that the code is aimed at at earlier Office and will not run for Office 2013.

  6. @Peter: I have updated my post for Office 2013. I don’t have Office 2013 to test but I think it should work now. The reason that it won’t work before because the Visual Studio project was targeted for Word 2010 AddIn. We just need to create a project for 2013 and copy old code to new one.

  7. Thanks very much for all your wonderful assistance.

    However, there still seems to be a small problem. When I try to load the project, I get the following error: “A problem occurred while trying to set the “References” parameter for the IDE’s in-process compiler. Error HRESULT E_FAIL has been returned from a call to a COM component.”

    Perhaps a simple thing for a brilliant mind like yours to fix, but regrettably way beyond my skills 🙂

  8. @Peter: Your last error was because of not available template. Read my update and install AddIn Template for Office 2013. As I suggested in comment above, just create a new project (Word AddIn 2013) at your computer and copy the code to that new project. Regards.

  9. @Admin: OK, I have given up trying to get these images. Finally got the program to run, only to get the dreaded HRESULT E_FAIL message when trying to load MS Word 2013. Then tried transferring the code to my laptop, ran the program, ended up with CustomUI14.xml not found, and now my laptop also gives me the HRESULT E_FAIL message when opening Word. Cannot pretend to be a happy camper right now 🙁

  10. OK, got it working. The error messages persist but I managed to get the images extracted after grabbing a copy of the missing file from my desktop.

    Thanks once again for an excellent piece of code!!

  11. Can you just upload the icons with transparent backgrounds to some host for download rather than asking people to painfully run this code?

  12. @Nick: Just before make a complaint , please read first (also in Comments section)
    For Office 2010 I already uploaded files to file hosting services.
    For Office 2013 I don’t have any Office 2013 for extracting the icon files. Therefore I just convert the code with the new template for Office 2013. Who needs, can extract icon himself. That’s all what I can 😉

  13. Just wondered why icons A – LevelingOptions (almost half the total amount of icons) are missing from the “Office 2010 Icon Gallery Files“ download. The 2013 download seems to have them all.
    Thanks anyway for the downloads, saves me a huge amount of time.

Leave a Reply

Your email address will not be published. Required fields are marked *