Introduction
Have you ever come across applications that are used as a service and also have a useful (or very useful) user interface but when you need them as a service they just start to claim their UI space? Let me make it simple, PowerPoint just has to show up on the screen even when you just need its automation capabilities, say to generate a PowerPoint presentation using the data from your database. Well here are a few things you can do to make it work silently. Well almost.
Environment
Like any other magic trick, you should have your tools of trade:
- PowerPoint (I am using 2003 � V11.)
- Windows 2000 or later.
- Visual Studio .NET
How it is done?
Fairly simple:
- Get the handle to the window you want to vanish.
- Set the window to be completely transparent. (This even lets the mouse clicks to pass through the window to the windows below � very cool.)
- Use the
ITaskbarList
interface to remove the application button from the taskbar (can't get cooler than that).
- Brag about it.
The walk through
I am going to modify the existing sample available on MSDN on PowerPoint automation using C#. Search for "How to use automation to create and to show a PowerPoint 2002 presentation by using Visual C# .NET 2002" on MSDN to get the un-mutilated automation sample source code.
Here is some advice, if you hate COM and would like to stay out of it completely, then this is the right time to hit the back button otherwise you might start liking it, especially when you find out how easy it is to tap its power in .NET.
Note: You can use all the code segments below and join them to get a single .cs file. So go ahead and copy them together if the world is ending for you.
Step 1
Create a new project in Visual Studio (VS). I am using a console application.
Step 2
Include the universe.
using System;
using Microsoft.Office.Core;
using PowerPointNS = PowerPoint;
using Graph;
using System.Runtime.InteropServices;
One of the great tricks that we are going to use here is to use a feature in Shell32 to make the taskbar button disappear and appear at your command. Shell32 exposes this feature through a COM interface called ITaskbarList
. Although you can look for the interface in SDK and create your own interop, it�s usually better to let Microsoft generate the interop for you. In any case the interface is very simple hence there is no chance of missing out on any marshalling specifics. I would suggest to use the .idl file that I have listed at the end of this article (save the text to a file with .idl extension) and use the MIDL tool to compile a .tlb file, which you can include as a COM component in your project using the add reference dialog. (Also try exploring other methods exposed by the interface, it's fun to play with it.)
Step 3
Now since we are going to make a few Windows API calls, here is some amount of homework we need to do to call them:
namespace PPTest
{
class Class1
{
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hWnd, int Index);
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern int SetWindowLong(IntPtr hWnd,
int Index, int Value);
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern int SetLayeredWindowAttributes(IntPtr hWnd,
int clrKey,Byte bAlpha,int dwFlags);
private const int LWA_COLORKEY = 1;
private const int LWA_ALPHA = 2;
private const int GWL_STYLE = -16;
private const int GWL_EXSTYLE = -20;
private const int WS_EX_LAYERED = 0x00080000;
Step 4
Go on and create that application object:
private static void ShowPresentation()
{
String strTemplate, strPic;
strTemplate =
"C:\\Program Files\\Microsoft Office\\Templates\\Presentation Designs\\Blends.pot";
strPic = "C:\\Windows\\Blue Lace 16.bmp";
bool bAssistantOn;
PowerPointNS.Application objApp;
PowerPointNS.Presentations objPresSet;
PowerPointNS._Presentation objPres;
PowerPointNS.Slides objSlides;
PowerPointNS._Slide objSlide;
PowerPointNS.TextRange objTextRng;
PowerPointNS.Shapes objShapes;
PowerPointNS.Shape objShape;
PowerPointNS.SlideShowWindows objSSWs;
PowerPointNS.SlideShowTransition objSST;
PowerPointNS.SlideShowSettings objSSS;
PowerPointNS.SlideRange objSldRng;
Graph.Chart objChart;
objApp = new PowerPointNS.Application();
Step 5
Do let the application window know before hand that you are going to play with it, or it might get upset. I mean you need to set the window style to have layers. This is done by setting the extended style attribute WS_EX_LAYERED
for the window. This will let us set different transparency (opacity) values for the window which in our case will be to set the window to be completely transparent. One thing to remember here is, if any window has already set styles then we don�t want to disturb them, so we get the existing setup and just add our flag to it. This is how it is done:
SetWindowLong((IntPtr)objApp.HWND, GWL_EXSTYLE,
GetWindowLong((IntPtr)objApp.HWND, GWL_EXSTYLE) | WS_EX_LAYERED);
That�s it, our PowerPoint application�s window is all set to magically disappear.
Step 6
The magic moment.
Here we set the opacity of the window to 0 using the SetLayeredWindowAttributes
method. Which means the window will no longer be visible after this step and all our mouse clicks and key events will just pass through it, as if it didn�t exist. Here is how it is done:
SetLayeredWindowAttributes((IntPtr)objApp.HWND,0,0,
LWA_ALPHA|LWA_COLORKEY);
Step 7
The mandatory noise.
If you wish to automate and create presentations dynamically, the PowerPoint object model forces you to make the application visible.
objApp.Visible = MsoTriState.msoTrue;
But since we have already set the application to be completely transparent, we don�t care even if we have to set the above flag to true
.
Step 8
The almost trick.
We managed to make the window invisible but Windows will still display a task bar button for the application object, which in our case is PowerPoint. Although the task bar button can be used to track whether the object is available or not however on a server churning out PowerPoint slides which does not require any user interaction it does not make any sense to have one. So let�s make it disappear. The catch here is that the button should appear in order to make it disappear, that�s why we had set the application object to be visible in the previous step before we can remove it from the taskbar. Well yes the button will appear for a moment before it vanishes. Remember the ITaskbarList
interface (listed at the end of this article) we talked about? Well, here is how it is used. We use the coclass declared in the interface to instantiate an object and call the DeleteTab
method on it, passing the handle of our PowerPoint application object and that�s it, three lines of code to use the COM object and say bye-bye to the taskbar button.
TaskbarList.TaskbarList tblc = new TaskbarList.TaskbarList();
tblc.HrInit();
tblc.DeleteTab(objApp.HWND );
Done!
Step 9
All the remaining unchanged code from the MSDN article. (Remember we don�t want to hide the slide show we created.)
objPresSet = objApp.Presentations;
objPres = objPresSet.Open(strTemplate,
MsoTriState.msoFalse, MsoTriState.msoTrue, MsoTriState.msoTrue);
objSlides = objPres.Slides;
objSlide = objSlides.Add(1,PowerPointNS.PpSlideLayout.ppLayoutTitleOnly);
objTextRng = objSlide.Shapes[1].TextFrame.TextRange;
objTextRng.Text = "My Sample Presentation";
objTextRng.Font.Name = "Comic Sans MS";
objTextRng.Font.Size = 48;
objSlide.Shapes.AddPicture(strPic, MsoTriState.msoFalse, MsoTriState.msoTrue,
150, 150, 500, 350);
objSlide = objSlides.Add(2, PowerPointNS.PpSlideLayout.ppLayoutTitleOnly);
objTextRng = objSlide.Shapes[1].TextFrame.TextRange;
objTextRng.Text = "My Chart";
objTextRng.Font.Name = "Comic Sans MS";
objTextRng.Font.Size = 48;
objChart = (Graph.Chart) objSlide.Shapes.AddOLEObject(150,150,480,320,
"MSGraph.Chart.8", "",
MsoTriState.msoFalse, "", 0, "",
MsoTriState.msoFalse).OLEFormat.Object;
objChart.ChartType = Graph.XlChartType.xl3DPie;
objChart.Legend.Position=Graph.XlLegendPosition.xlLegendPositionBottom;
objChart.HasTitle = true;
objChart.ChartTitle.Text = "Here it is...";
objSlide = objSlides.Add(3, PowerPointNS.PpSlideLayout.ppLayoutBlank);
objSlide.FollowMasterBackground = MsoTriState.msoFalse;
objShapes = objSlide.Shapes;
objShape = objShapes.AddTextEffect(MsoPresetTextEffect.msoTextEffect27,
"The End", "Impact", 96,
MsoTriState.msoFalse,
MsoTriState.msoFalse, 230, 200);
int[] SlideIdx = new int[3];
for(int i=0;i<3;i++) SlideIdx[i]=i+1;
objSldRng = objSlides.Range(SlideIdx);
objSST = objSldRng.SlideShowTransition;
objSST.AdvanceOnTime = MsoTriState.msoTrue;
objSST.AdvanceTime = 3;
objSST.EntryEffect = PowerPointNS.PpEntryEffect.ppEffectBoxOut;
bAssistantOn = objApp.Assistant.On;
objApp.Assistant.On = false;
objSSS = objPres.SlideShowSettings;
objSSS.StartingSlide = 1;
objSSS.EndingSlide = 3;
objSSS.Run();
objSSWs = objApp.SlideShowWindows;
while(objSSWs.Count>=1) System.Threading.Thread.Sleep(100);
if(bAssistantOn)
{
objApp.Assistant.On = true;
objApp.Assistant.Visible = false;
}
objPres.Close();
objApp.Quit();
}
[STAThread]
static void Main(string[] args)
{
ShowPresentation();
GC.Collect();
}
}
}
The ITaskBarList.idl file
Compile the .idl file using the MIDL compiler and include the generated .tlb into your project:
[
uuid(C52C7F93-54B9-11D3-ABF9-0040F6A4BFEC),
version(1.0),
helpstring("ITaskbarList - shell32")
]
library TaskbarList
{
importlib("stdole2.tlb");
interface ITaskbarList;
[
odl,
uuid(56FDF342-FD6D-11D0-958A-006097C9A090),
helpstring("ITaskbarList interface")
]
interface ITaskbarList : IUnknown
{
[helpstring("This function must be called first to validate " +
"use of other members.")]
HRESULT _stdcall HrInit();
[helpstring("This function adds a tab for hwnd to the taskbar.")]
HRESULT _stdcall AddTab([in] long hwnd);
[helpstring("This function deletes a tab for hwnd from " +
"the taskbar.")]
HRESULT _stdcall DeleteTab([in] long hwnd);
[helpstring("This function activates the tab associated with " +
"hwnd on the taskbar.")]
HRESULT _stdcall ActivateTab([in] long hwnd);
[helpstring("This function marks hwnd in the taskbar as the " +
"active tab.")]
HRESULT _stdcall SetActivateAlt([in] long hwnd);
};
[
uuid(56FDF344-FD6D-11D0-958A-006097C9A090),
helpstring("TaskbarList class")
]
coclass TaskbarList
{
[default] interface ITaskbarList;
};
};
I also suggest that you compile and run the original sample from MSDN too, and see the difference.