Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

Embedding PowerPoint presentation player into a WPF application

Rate me:
Please Sign up or sign in to vote.
4.86/5 (21 votes)
18 Oct 2010CPOL29 min read 212.3K   51   79
In this article I explain how to embed presentation player into a WPF application and describe the way this solution was found

Introduction

On one occasion I came across an interesting and, I can even say, challenging task of building a customized player of PowerPoint presentations. This task emerged as part of a project which my teammates and I developed at Reliable Systems. Our customer wanted to have an application that would allow its users to customize the order in which slides are displayed by designing a workflow with conditional points and branching. 

Some reader may reasonably suggest that customization of slide display order could be implemented by introducing an add-on module to the PowerPoint. I’m sure they are right, as the latest versions of the Microsoft Office applications provide rich API for extending their features; moreover, nowadays add-ons for Office applications can be developed either in C#, VB.NET or any other CLR-compliant language. But our customer wanted to have a separate application rather than a customized version of PowerPoint. Also he wanted the application to display the presentation in two separate windows simultaneously; one window was supposed to act as a controller form for switching between slides and the other just a non-interactive slideshow view. We were not sure whether this feature could be implemented by introducing an add-on for PowerPoint (you may argue if that’s not true, I’m still not sure) and for us that was another important argument in favor of building a separate application.

We chose to create a WPF application to have access to the rich set of GUI development features provided by this technology. Also we decided to re-host the Visual Studio workflow designer in our application along with our custom classes for the workflow and its activities. Actually the job of creating custom workflow activity classes is pretty straightforward and there’s not much to discuss. Re-hosting of workflow designer is not that straightforward, but it has already been described in details by Bruce Bukovics in his book and I guess I couldn’t add any valuable information to it. The real challenge of our project was to work out how we were going to import and, more important, play presentations.

Issues with direct COM interoperability

It was important to find a way to acquire relevant information about a given presentation (number of slides, their names and thumbnail images) to build workflow for it. This task seems quite straightforward at first sight as there are .NET wrapper assemblies for COM interfaces which can be used to implement interaction with office applications like opening presentations programmatically, acquiring data from a presentation, launching slideshow etc. While that’s absolutely right, but there are some peculiar features which in the end made it necessary to look for alternative solutions.

First, in order to acquire data from a presentation, it must be opened in PowerPoint. Apparently the best way for the purposes of our application was to do it quietly, so that the user wouldn’t see the PowerPoint application window opening in the background and then closing in a few seconds. It was a great disappointment to find that although PowerPoint’s COM interface and its managed wrapper seem to provide the ability to run the application invisibly, the feature is not indeed supported by PowerPoint 2007. If you run code like this

C#
using Microsoft.Office.Core;
using Microsoft.Office.Interop.PowerPoint;
……
Application app = new ApplicationClass {Visible = MsoTriState.msoFalse};

you will get the following exception:

System.Runtime.InteropServices.COMException 
  Message="Application (unknown member) : Invalid request.  Hiding the application window is not allowed."
  Source="Microsoft Office PowerPoint 2007"
  ErrorCode=-2147188160
  StackTrace:
       at Microsoft.Office.Interop.PowerPoint.ApplicationClass.set_Visible(MsoTriState Visible)
       ....

So the constructor for ApplicationClass must always be called with the argument MsoTriState.msoTrue. I was by no means satisfied with such state of things and started looking for solutions to fix the situation, but none of the proposed solutions provided satisfactory results and in the end I had to abandon the idea of interoperating with PowerPoint directly through COM.

Hosting presentation player in application window

There was also another big question to be answered: how to embed presentation player into our application? I suppose most of you remember the good old OLE technology which allowed embedding Excel spreadsheets into Word documents etc. As the technology evolved and new impressive features were added it was renamed to Active Document Hosting. We still can see an example of using this powerful technology in action when opening, for instance, a PDF document from Internet Explorer. As we do it, the browser window assumes some features of the GUI of Adobe Reader and the document is rendered inside the browser window (Figure 1). This happens because Internet Explorer acts as an Active Document Host and Adobe Reader as an Active Document Server. Indeed, in this scenario Adobe Reader really serves Internet Explorer, it is run in the background and allows Internet Explorer to re-host most of its GUI providing all its features. From user’s point of view it looks as if Internet Explorer was smart enough to be capable of rendering PDF documents on its own; yet it wouldn’t be able to do so if Adobe Reader wasn’t installed on your machine.

Figure1.png

Figure 1. PDF document re-hosted by Internet Explorer.

Before MS Office 2007 the same scenario worked for all MS Office documents, including PowerPoint presentations. If you have MS Office 2000, XP or 2003 installed on your machine and try to open an MS Office document from Internet Explorer, it will be rendered in the browser window the same way as we’ve just seen it for a PDF document. Before MS Office 2007 all we would have to do is to place a Browser component onto a form (or use the Browser AcitveX if we were developing an unmanaged application), then write code to open the required presentation in it and we would have the same Active Document Hosting magic work for us.

The bad news is that we couldn’t use this approach since we were to deal with PowerPoint 2007 presentations and with MS Office 2007 the story becomes totally different. If we try to open a document of any MS Office 2007 application from Internet Explorer, we’ll see that the document opens in an external window. The MS Office team decided to give up support for Active Document Hosting in the 2007 and later versions due to compatibility issues; the reasons for doing this are explained here. This article includes description of a workaround solution to that issue which requires changing registry settings and actually its use is not recommended. The idea of changing registry settings globally for the sake of one single application didn’t look attractive and the discouraging notes about possible issues when using the workaround made me abandon the idea of using the Browser component for hosting presentations. Probably that was for the better because eventually a better solution was found which didn’t require any modifications to the system registry and therefore didn’t affect other applications.

Using the Office Viewer Component

Looking for possible alternatives on the internet, I found the Office Viewer Component. Technically speaking this is an ActiveX control which is somehow capable of hosting MS Office documents. Using this component I successfully embedded PowerPoint presentation player into our prototype application by placing an instance of Office Viewer onto the main form. The C# code for opening a presentation and switching to slide show mode looked like this:

C#
private AxOfficeViewer.AxOfficeViewer axOfficeViewer1;
private Microsoft.Office.Interop.PowerPoint.Presentation _presentation;

public void OpenDocument(string fileName)
{
    axOfficeViewer1.Visible = false;

    axOfficeViewer1.Open(fileName);
    axOfficeViewer1.Visible = true;
    axOfficeViewer1.SlideShowPlay(false, false, false, false);

    _presentation =
        axOfficeViewer1.ActiveDocument as Presentation;
}

I had to make the Office Viewer instance invisible before opening the presentation and show it again just before starting the slideshow so that the user couldn’t see intermediate state of PowerPoint GUI with all its toolbars and ribbon. This guaranteed that the Office Viewer would become visible already running a slideshow with no unnecessary GUI elements visible. As you can see from code, the Office View has a special method for starting slideshow which has four Boolean parameters, which indicate whether the slideshow must be opened in full screen mode, whether a web toolbar must be visible, whether the slideshow must be “looped” and whether a vertical scrollbar must be visible.

Switching between slides would be done pretty straightforward:

C#
public void SlideGotoPage(int pageNumber)
{
    if (axOfficeViewer1.IsOpened)
        axOfficeViewer1.SlideGotoPage(pageNumber);
}

public void SlideGotoNext()
{
    if (axOfficeViewer1.IsOpened)
        axOfficeViewer1.SlideGotoNext();
}

This allowed me to display certain slides in specific order during the slideshow. Also to prevent PowerPoint from reacting to mouse clicks I placed a transparent panel on top of the Office Viewer instance which would intercept the mouse events and “swallow” them.

As you can see from the code, it was possible to get reference to Presentation interface immediately after opening the presentation. This enabled me to acquire the attributes of the presentation when importing it into a new project. Acquiring the number of slides and their names is pretty easy as the Presentation interface has Slides property which gets a sequence of slide descriptors having, among others, the Name property. As for thumbnail images, I had to implement the procedure of capturing them by exporting each slide image to a temporary file which was to be deleted once the application no longer needed it. The code for capturing thumbnail images looked like this:

C#
public Image[] GetSlideImages()
{
    if (_presentation == null)
        return null;

    var imageList = new List<Image>();

    foreach (Slide slide in _presentation.Slides)
    {
        var fileName = Path.Combine(
            Path.GetTempPath(),
            string.Format("Slide{0:00}.jpg", slide.SlideNumber));

        slide.Export(fileName, "JPG", 800, 600);

        imageList.Add(Image.FromFile(fileName));
    }

    return imageList.ToArray();
}

Up to this point everything was just fine. Then I decided to implement in the POC version the feature of displaying the slideshow in two windows simultaneously and at this very point I became aware of some limitations of the Office Viewer control. The component doesn't actually allow having more than one instance working at the same time. You can create multiple instances, but you will have only one of them really working; all the rest of them will become “dead” and won’t even redraw correctly. I examined a couple of similar components which also allow hosting the GUI of PowerPoint and other Active Document Server applications inside an ActiveX control and they turned out to have the same limitation.

It was suggested to implement simultaneous display of presentation slideshow in two separate windows by having an instance of the Office Viewer component on one form then capturing the contents of this window and showing it through an image box on the other form. Capturing the image was done by using this code calling some Windows API functions for manipulating graphics:

C#
#region Imported native types and methods

[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;

    public int Width
    {
        get { return Right - Left; }
    }

    public int Height
    {
        get { return Bottom - Top; }
    }
}

[DllImport("user32.dll")]
public static extern int GetClientRect(int hwnd, [MarshalAs(UnmanagedType.Struct)] ref Rect lpRect);


[DllImport("user32.dll")]
private static extern int ReleaseDC(int hwnd, IntPtr hdc);

[DllImport("user32.dll")]
public static extern IntPtr GetDC(int hwnd);

[DllImport("gdi32.dll")]
public static extern int StretchBlt(IntPtr hdc, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, int dwRop);

#endregion

public Image GetCapturedImage()
{
    if (_presentation == null)
        return null;

    if (_presentation.SlideShowWindow == null)
        return null;

    var slideShowWindowRect = new Rect();

    GetClientRect(_presentation.SlideShowWindow.HWND, ref slideShowWindowRect);

    var image = new Bitmap(slideShowWindowRect.Width,
        slideShowWindowRect.Height, PixelFormat.Format32bppArgb);

    var sourceHdc = GetDC(_presentation.SlideShowWindow.HWND);

    try
    {
        using (Graphics graphics = Graphics.FromImage(image))
        {
            IntPtr hDestHdc = graphics.GetHdc();

            try
            {
                StretchBlt(hDestHdc, 0, 0, slideShowWindowRect.Width, slideShowWindowRect.Height, sourceHdc, 0,
                           0, slideShowWindowRect.Width, slideShowWindowRect.Height, SrcCopy);
            }
            finally
            {
                graphics.ReleaseHdc(hDestHdc);
            }
        }
    }
    finally
    {
        ReleaseDC(_presentation.SlideShowWindow.HWND, sourceHdc);
    }

    return image;
}

It’s essential that in this code we get the handle of the window displaying presentation as _presentation.SlideShowWindow.HWND instead of much simpler this.Handle. Trying to obtain the image from this.Handle resulted in getting a black rectangle.

By invoking this method quite frequently I did achieve a high degree of synchronization of images in both windows, even when slides involved some animation (Figure 2). 

Figure2.png

Figure 2. Synchronized slideshow in two windows.

The problem with the above presented code is that it works fine under Windows Visa and Windows 7 only if you use a modern theme like Aero. If you switch so a simplified theme like Windows Classic or run the application under Windows XP (with any theme), you might see such a weird image (Figure 3). 

Figure3.png

Figure 3. Curious side effect of copying window image 

The problem is that for simplified themes the system doesn’t keep the image of every single window in memory, it just “remembers” the resulting projection of all windows to the screen according to their Z-order. Executing our code for capturing image in this situation resulted in capturing the contents of all windows overlapping the target window along with the contents of the window itself, which sometimes would produce some quizzical effects. Anyway the method turned out to be unsuitable for our task.

Creating a hybrid solution

As you see, at this point we had in our projects much more problems than effective solutions and the chances of being successful in creating the kind of application that we needed looked rather dubious. An effective solution was found as we examined Visual C++ sample projects searching for some ideas on interacting with an Active Document Server through OLE. One particular project named MFCBind drew my attention (it is located in MFC\OLE folder of the Visual C++ samples). The name of this project contains the word “bind” as it is intended for binding several Active Documents into one compound document. As I tried to add a PowerPoint presentation to such a compound document, I discovered that MFCBind application was capable of doing what we expected from the web browser – it did support hosting of Office 2007 documents in its main window even with default registry settings (Figure 4). When the hosted presentation became active, the original toolbar of MFCBind was replaced by the PowerPoint’s ribbon. However, after I started the slideshow by pressing F5, the ribbon became invisible (Figure 5). 

Figure4.png

Figure 4. PowerPoint presentation hosted by MFCBind 

Figure5.png

Figure 5. Hosted presentation in slideshow mode 

So, there was sample application with source code which was capable of solving the problem we’d been striving to solve almost in the same fashion as we needed. Naturally I started exploring the code of MFCBind application. Like most MFC applications, MFCBind is designed according to Document-View-Template pattern, which has always been recommended for MFC-based application as a way for decoupling data from presentation logic. In this particular case the whole magic dwells in the document class: actually the class CMFCBindDoc is derived from COleDocument and it acts as container for instances of CMFCBindCntrItem class, descendant of COleDocObjectItem; together these classes provide all the functionality required for hosting Active Documents. You see, C++ developers have a ready-made solution for a task like ours. 

As we wanted to develop our product in C#, we needed to find a way to combine the ready-made solution provided by MFC library and in the meantime still use C#, WPF, Workflow Designer and other features of the .NET Framework. The following options were considered:

  1. Create a reusable component (ActiveX) in C++ using MFC which could later be embedded into a .NET-base application;
  2. Learn how the features of Active Document Hosting infrastructure are implemented in MFC and reproduce similar solution for .NET in C#;
  3. Use an unmanaged C++ application as the host responsible for creating the main window and views for hosting Active Documents and decorate is with more sophisticated complex GUI elements implemented by means of WPF.

I examined the feasibility of these options one by one. The first one had to be abandoned, because the MFC class COleDocument which provides the essential features of Active Document Hosting was designed to be used according to Document-View-Template pattern, which has nothing to do with creating an ActiveX control. The second option was also discarded, as I found the Active Document Hosting infrastructure being documented very poorly; actually all I found were lists of the COM interfaces, which must be implemented by an Active Document Server and an Active Document Host respectively in this book, and a too brief description of those interfaces in the MSDN library with no particular details of proper implementation. Examining the source code of the MFC library didn’t add any valuable information as the implementation of the interfaces is spread among numerous classes which makes it incomprehensible without proper documentation. Also implementing the whole of the Active Document Hosting infrastructure would be an enormous task for a team of our scale in a reasonable period of time. So I thought the third option, however bizarre it might seem at first sight, the only way for us to unify MFC/C++ and WPF/C# in one application.

In order to prove that this option will work for I had to ensure that we could have the following scenarios working:

  1. We needed to be able to obtain a reference to Presentation interface once a PowerPoint presentation has been opened. Having this reference would enable us to acquire attributes of the presentation which are required for importing the presentation into a new project;
  2. We needed to be able to start slideshow programmatically the same way as we did it with the Office Viewer component;
  3. When opening a presentation, we would like to have it displayed immediately in slideshow mode so that the user wasn’t bothered by watching intermediate transitions of the GUI status such as drawing and subsequently hiding the ribbon and layout transformations caused by these operations;
  4. We needed to prevent PowerPoint from reacting to user events like pressing hot keys and mouse clicking so that it would be completely under the control of our application
  5. Finally, we needed to integrate the child window, in which the presentation was hosted, with WPF UI elements.

The first task was resolved very easily, as you can see from the following code:

C++
// Create new item connected to this document.
pItem = new CMFCBindCntrItem(this);
ASSERT_VALID(pItem);

CString fileName = openFileDialog.GetPathName();
ASSERT(fileName.GetLength() > 0);

if (!pItem->CreateFromFile(fileName))
{
    delete pItem;
    return;
}

ASSERT_VALID(pItem);

// make sure we deactivate any active items first.
COleClientItem* pActiveItem = GetInPlaceActiveItem(pViewObjCont);
if (pActiveItem != NULL)
    pActiveItem->Deactivate();

pItem->Activate(OLEIVERB_SHOW, pViewObjCont);
ASSERT_VALID(pItem);

// set selection to last inserted item
pViewObjCont->m_pSelection = (CMFCBindCntrItem*) pItem;
UpdateAllViews(NULL);

PowerPoint::_PresentationPtr presentation = pItem->m_lpObject;

After binding an instance of CMFCBindCntrItem to a PowerPoint presentation file we have a reference to the opened presentation stored in m_lpObject member variable of that class. This variable is of type LPOLEOBJECT, which is actually a pointer to IOleObject interface. Using smart pointer we can cast this instance to Presentation interface in a pretty straightforward manner. Thus the first task is resolved. 

Resolving the second one was not that simple and straightforward. You may remember that the Office Viewer component has a useful method SlideShowPlay which allows starting slideshow in either full-screen or in-place mode. The COM interfaces of PowerPoint also have some members for starting slideshow; here is a piece of code demonstrating how it can be done through them:

C++
PowerPoint::SlideShowSettingsPtr ssSettings =
presentation->GetSlideShowSettings();
    ssSettings->PutShowType(PowerPoint::PpSlideShowType::ppShowTypeKiosk);
    ssSettings->Run();

We get an object implementing SlideShowSettings interface, then we can configure the slideshow by changing values of some parameters which indicate whether the slideshow must be in full-screen mode or not, whether the horizontal scrollbar must be visible or not etc. (this is quite similar to the parameters for the SlideShowPlay method). In this sample code we change the value of the ShowType property which indicates whether the slideshow will be displayed in full-screen mode. This property is of enumerated type PpSlideShowType slideshow and it can accept the following values: ppShowTypeSpeaker, ppShowTypeWindow and ppShowTypeKiosk. With both ppShowTypeSpeaker and ppShowTypeKiosk values the slideshow is started in full-screen mode, with ppShowTypeWindow the slideshow is started in a newly created PowerPoint application window; there are no other options available through COM interface.

I found a workaround solution to achieve the same behavior, as we saw in the Office Viewer component, with the help of Spy++ application. I noticed that the slideshow started in response either to pressing the F5 key or to clicking on the corresponding button in the ribbon. I tried to generate a similar keyboard event, but this didn’t work. As for emulating a click on the ribbon button this seemed impossible, as the ribbon has probably been implemented as a WPF control and its buttons are not windows from the operating system’s viewpoint. Actually a more elegant solution was found. Using Spy++ I could examine the tree of children windows which were created inside our view after the presentation was hosted in it (Figure 6) and monitor the messages which were sent to them as I started slideshow. In both cases I found that a WM_COMMAND message with wParam=0x100AC was sent to the window, which in Figure 6 is identified by 000F1418 value, immediately before the slideshow started. I concluded that this message was the signal for the child window to switch to slideshow mode. (There is also a remarkable thing about that window: being a descendant of our application window it is owned by PowerPoint process.)

Figure6.png

Figure 6. Window tree in Spy++

I attempted to reproduce an identical message in my code and send it to the hosted window:

C++
// locate the host window of the PowerPoint presentation
HWND hPowerPointSiteOwner = FindWindowEx(pView->m_hWnd, NULL, _T("childClass"), NULL);
 
ASSERT(hPowerPointSiteOwner);
 
HWND hPowerPointSite = FindWindowEx(hPowerPointSiteOwner, NULL, _T("childClass"),
   NULL);
 
ASSERT(hPowerPointSite);
 
// start the slideshow
SendMessage(hPowerPointSite, WM_COMMAND, 0x100AC, NULL);

In this code I, having the handle for the view window (which in Figure 6 is identified by 000E1102 handle value), first locate the anonymous window of the “childClass” window class, then locate its only child window by passing the same window class name and, having got its handle, I send the message. Well, it worked!

The next problem I had to solve was to prevent the PowerPoint ribbon from appearing inside our application window before the slideshow started. Even though I started slideshow immediately after hosting the presentation in our window, the ribbon became visible for a very short time causing layout transformations inside our application window. Again Spy++ was very helpful to solve this issue. I discovered that after a presentation was hosted inside our application window, PowerPoint created five auxiliary children windows inside our application window. These windows are named “MsoDockLeft”, “MsoDockRight”, “MsoDockTop”, “MsoDockBotton” and “MsoWorkPane”. The ribbon was nested inside “MsoDockTop” window (Figure 7). Apparently PowerPoint is smart enough to perform layout transformations in the parent window to fit its panels alongside other children windows. 

Figure7.png

Figure 7. Auxiliary children windows created by PowerPoint 

I needed to intercept the moment when PowerPoint nested those windows and get rid of them somehow. Luckily in Microsoft Windows a window is notified when a new child window is created for it through WM_PARENTNOTIFY message; this allowed me to intercept the moment when any of those windows was created even though the construction was performed in another process. I decided that the best way to get rid of them gently was to make them invisible and change their owner:

C++
void CMainFrame::OnParentNotify(UINT message, LPARAM lParam)
{
	if (message == WM_CREATE)
	{
		HWND hWnd = (HWND)lParam;
		
		CString className;
		GetClassName(hWnd, className.GetBufferSetLength(MAX_PATH), MAX_PATH);
		className.ReleaseBuffer();
 
		if (className == _T("MsoCommandBarDock") || className == _T("MsoWorkPane"))
		{
			if (::IsWindowVisible(hWnd))
				::ShowWindow(hWnd, SW_HIDE);
 
			::SetParent(hWnd, HWND_MESSAGE);
		}
	}
	
	CFrameWnd::OnParentNotify(message, lParam);
}

In this piece of code we specify HWND_MESSAGE as the handle of the new owner for those windows. This constant identifies the special message-only window which is invisible by definition. This worked again and we didn’t see any sign of the ribbon in our application any more. Please note that PowerPoint embeds the ribbon and other child windows into the frame window, not the view, so WM_PARENTNOTIFY message must be handled by the frame window.

We are coming to the fourth task which is to prevent PowerPoint from reacting to user’s actions like pressing hot keys and mouse clicking. With the help of Spy++ I found out that PowerPoint intercepted pressing hot keys subsequently dispatching notifications to one of the child windows hosted in our view. The notifications are sent as WM_COMMAND messages; earlier I used similar notification to start slideshow. But having started slideshow, I had to prevent the user from stopping it, e.g. by pressing Esc, because we needed to have full control over the hosted presentation’s behavior.

Figure8.png

Figure 8. The tree of rehosted PowerPoint windows

Using Spy++ I explored the tree of windows as it looked during slideshow mode (Figure 8). In this screenshot the 00470E2C window is our view which is the host for presentation windows, all its descendant windows are created by PowerPoint when the presentation is hosted by our view. Monitoring messages consumed by these descendant windows revealed that WM_COMMAND message was sent to the window which in Figure 7 is identified as 005F0EE0. So to prevent hosted presentation from reacting to hot keys we had to override handling of WM_COMMAND by that window. This could be accomplished by subclassing the window, i.e. replacing its message-handling procedure with our custom function which would handle some specific messages and invoke the default procedure for processing all other messages. As a matter of fact subclassing is a relatively simple technique when dealing with windows owned by the same process, but in this case I had to subclass a window which was created and owned by PowerPoint, not by our process; therefore I couldn’t subclass it using the straightforward approach. But we could do it on behalf of PowerPoint. 

To achieve this I had to create a DLL, load it into the address space of PowerPoint process and then invoke a function implemented in the DLL which would subclass the specified window. This technique is very well described in this article, but I’d like to point out that the approach that I used is slightly different from the one described by Robert Kuster. He suggests placing the code, which must be executed in the context of another process, into DllMain function, so that the code was executed as soon as the DLL was loaded into the process. I placed the subclassing code in a separate function in the DLL and called it on behalf of another process. I’m sure this is a better way because DllMain is invoked by the operating system and you cannot pass any data into it directly, whereas any other function from a DLL can be invoked deliberately with appropriate data arguments passed into it. 

Once a DLL is loaded into another process, you can get its address in the address space of that process, as described by Robert

C++
	HANDLE hProcess = OpenProcess(
        PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
		FALSE, wndProcessId);
	
	// 1. Allocate memory in the remote process for szLibPath
	pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(libPath), MEM_COMMIT, PAGE_READWRITE );
 
	// 2. Write szLibPath to the allocated memory
	::WriteProcessMemory( hProcess, pLibRemote, (void*)libPath, sizeof(libPath), NULL );
 
	// Load our DLL into the remote process (via CreateRemoteThread & LoadLibrary)
	hThread = ::CreateRemoteThread( hProcess, NULL, 0,
		(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
#ifndef UNICODE
		"LoadLibraryA" ),
#else 
		"LoadLibraryW" ),
#endif
		pLibRemote, 0, NULL );
 
	::WaitForSingleObject( hThread, INFINITE );
 
	// Get handle of the loaded module
	::GetExitCodeThread( hThread, &hLibModule );

Here I assume that libPath contains the full path to the DLL which is loaded into another process. After the code has been executed, we have the address to the DLL in hLibModule. Unfortunately in this situation we cannot get the address of a function from the loaded DLL simply by calling GetProcAddress, as it would return NULL. I used a workaround to obtain the address of required function in the DLL loaded into another process: first I loaded the same DLL into “my” process, second I got the address of the required function by calling GetProcAddress, third I subtracted the address of DLL from the address of the function; the result is the offset for the function:

C++
HMODULE hLocalPWSLib = LoadLibrary(libPath);
FARPROC doSubclassingProc = GetProcAddress(hLocalPWSLib, "DoSubclassing");
FARPROC doSubclassingProcOffset = (FARPROC)((int)doSubclassingProc - (int)hLocalPWSLib);
FreeLibrary(hLocalPWSLib);

Having this offset I could calculate the address of this function for another process. My code for invoking a function on behalf of another process looked like this:

C++
SubclassData * pData = (SubclassData*)VirtualAllocEx(hProcess, NULL, sizeof(SubclassData),
                            MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, &pData->hPowerPointSite, &hPowerPointSite, sizeof(HWND),
            NULL);
WriteProcessMemory(hProcess, &pData->hSlideShowWindow, &hSlideShowWindow, sizeof(HWND),
            NULL);

hThread = CreateRemoteThread(hProcess, NULL, 0,
    (LPTHREAD_START_ROUTINE)((int)hLibModule + (int)doSubclassingProcOffset),
    pData, 0, NULL);

WaitForSingleObject(hThread, INFINITE);

DWORD result;
::GetExitCodeThread(hThread, &result);

::CloseHandle(hThread);
::VirtualFreeEx( hProcess, pData, sizeof(SubclassData), MEM_RELEASE );

So I allocated a structure in the address space of PowerPoint process, wrote data into its fields and called the required function via CreateRemoteThread passing a pointer to the structure as the argument. When invoked, DoSubclassing function subclassed the specified window by assigning a new message-handling procedure which would “swallow” all WM_COMMAND messages:

C++
if (msg == WM_COMMAND)
    return NULL;

By doing this I prevented the hosted presentation from reacting to hot keys, but I also had to suppress reaction to mouse messages. I found by using Spy++ that mouse messages were handled by another window, which in Figure 7 is identified as 006F181E and I applied the same subclassing technique to it, but this time I didn’t want mouse messages to be simply “swallowed“, I wanted them to be posted to my view window so that mouse clicks on presentation window could be handled by my application:

C++
if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK || msg == WM_LBUTTONUP
    || msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK || msg == WM_RBUTTONUP)
{
    DWORD powerPointProcessId;
    GetWindowThreadProcessId(hWnd, &powerPointProcessId);

    DWORD parentProcessId = powerPointProcessId;
    HWND hCurWnd = hWnd;
    HWND hParent;
    while (parentProcessId == powerPointProcessId)
    {
        hParent = GetParent(hCurWnd);

        if (hParent)
            GetWindowThreadProcessId(hParent, &parentProcessId);
        else
            break;

        hCurWnd = hParent;
    }

    if (hParent)
        PostMessage(hParent, msg, wParam, lParam);

    return NULL;
}

This code performs navigation up the tree of windows trying to find the first ancestor window which is not owned by PowerPoint process. From Figure 7 you can see that the first ancestor owned by our process is the view window, so the view will get the mouse message.

There was also another subtlety about subclassing this window. In my application the code for subclassing was executed immediately after starting slideshow and while debugging the application I found that while the 005F0EE0 window always existed by that time, the 006F181E window might occasionally be missing probably due to race conditions between my application and PowerPoint. So executing this code for the view window:

C++
HWND hPowerPointSiteOwner = ::FindWindowEx(m_hWnd, NULL, _T("childClass"), NULL);
hPowerPointSite = ::FindWindowEx(hPowerPointSiteOwner, NULL, _T("childClass"), NULL);
hSlideShowWindow = ::FindWindowEx(hPowerPointSite, NULL, _T("paneClassDC"), NULL);

sometimes could result in having hSlideShowWindow equal to NULL. To handle this situation I had to add some redundancy. First I tried to locate a window of "paneClassDC" class immediately after starting slideshow and if it was found I called subclassing code for it. But I also had to put additional logic for handling WM_PARENTNOTIFY message into the subclassing procedure which was assigned to the 005F0EE0 window which handles WM_COMMAND messages and at the same time is the parent window of the 006F181E /"paneClassDC" window. If the window hadn’t been created by the time we invoked subclassing function for the 005F0EE0 window, the new window procedure of 005F0EE0 intercepted the moment when 006F181E was created later and invoked subclassing function for it then:

C++
if (msg == WM_PARENTNOTIFY && wParam == WM_CREATE)
{
    HWND hChildWindow = (HWND)lParam;

    char className[MAX_PATH];

    GetClassNameA(hChildWindow, className, MAX_PATH);

        if (strcmp(className, "paneClassDC") == 0)
    {
        SubclassWindow(hChildWindow, (FARPROC)SubclassPresentationWindowProc);

        return NULL;
    }
}

The same approach was used for hiding the vertical scroll bar which by default is visible alongside the slideshow: I tried to locate and dismantle it immediately after starting the slideshow and at the same time I intercepted the WM_PARENTNOTIFY message if it was created later (by “dismantling” I mean hiding and changing the owner to HWND_MESSAGE the same way as I did for the ribbon).

So the fourth task was solved.

The last task of the above given list was to integrate somehow this MFC application with WPF controls. Before discussing the solution let us review what we’ve got up to this moment: there is an MFC/C++ unmanaged application based on document-view architecture which on startup creates the main window, and the class implementing the main window is responsible for creating the view, in which later on a presentation is hosted. The logic of creating the view is implemented in the MFC library and cannot be delegated to any other class as the main window is responsible for this task by design. But once the view has been created, we can do whatever we need to it, we can even change its parent window, which is initially the main window. Based on this simple idea I proposed the following solution: in the hybrid application combining both MFC and WPF we must use the MFC-related part as the host into which WPF controls will be integrated. The main window created by C++ code must be initially empty – no menu, no toolbars, no status bar; all these elements must be implemented using WPF, because the latter technology provides much richer tools both for designing beautiful and sophisticated GUI and for such auxiliary tasks as localization. So, apparently we’re going to have a composite WPF control containing menu, toolbar, status bar and anything else we need and this control will be nested into the main window at startup. What about the view then? Well, as the main window is responsible for creating it, it’s still going to create it; but immediately after construction the view must be nested inside the WPF composite control and have its parent window changed.

However monstrous and sophisticated it may seem at first glance, the implementation of this idea turned out to be pretty straightforward. The original MFC project was configured to use managed .NET classes. In the handler for WM_CREATE message there was added the code to create an HwndSource instance as a child of the main window. Then there was instantiated the above mentioned composite WPF control (it was implemented in a separate assembly to which a reference was added in the host application project) and the newly created instance was specified as the root visual element for the HwndSource. By this we ensure that the composite control will be nested into the main windows.

The newly constructed view is wrapped into an instance of a custom managed class derived from HwndHost and the wrapper instance is passed to the WPF control to be nested into one of its children container controls. The code for the HwndHost-derived wrapper looks like this:

MC++
#using <PresentationFramework.dll>
#using <PresentationCore.dll>
#using <WindowsBase.dll>
 
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Controls;
using namespace System::Windows::Interop;
using namespace System::Runtime::InteropServices;
 
class CMFCBindView;
 
ref class SlideShowHwndHost : public HwndHost
{
public:
	SlideShowHwndHost(CMFCBindView* pView, int width, int height) 
		: _hParent(NULL)
		, _width(width)
		, _height(height)
	{
		if (pView == NULL)
			throw gcnew ArgumentNullException(gcnew String("pView"));
 
		_pView = pView;
	}
 
protected:
	virtual HandleRef BuildWindowCore(HandleRef hwndParent) override
	{
		_hParent = (HWND)hwndParent.Handle.ToInt32();
		
		::SetParent(_pView->m_hWnd, _hParent);
		::SetWindowPos(_pView->m_hWnd, NULL, 0, 0, _width, _height, SWP_SHOWWINDOW);
 
		return HandleRef(this, IntPtr(_pView->m_hWnd));
	}
 
	virtual void DestroyWindowCore(HandleRef hwnd) override
	{
		::DestroyWindow((HWND)hwnd.Handle.ToInt32());
	}
 
private: 
	CMFCBindView * _pView;
	HWND _hParent;
	int _width;
	int _height;
};

As HwndHost class is derived from the Visual class, it can be nested into any WPF container control. E.g. in my POC project I created a composite control with menu, grid and a frame nested in one of the grid’s cells (Figure 9). 

Figure9.png

Figure 9. Sample composite control 

The code for nesting an instance of HwndHost-derived wrapper is extremely simple:

C#
viewHost.Content = child;

where viewHost is the frame (see Figure 9) and child is the wrapper.

As you may see from Figure 10, in the resulting GUI WPF controls and unmanaged windows are seamlessly integrated with each other and we’ve got exactly what we wanted. 

Figure10.png

Figure 10. Main window of the hybrid application with hosted presentation

Just one more trait: in order to provide communication between managed and unmanaged code we need to keep references between objects. E.g. when the user clicks on the “Open” menu item, the event is handled in C# code, but then the unmanaged code must executed in order to open the presentation file and nest it into the view. To make this communication easier I created in C++ code a managed class which implements a managed interface defined in the same assembly as the composite WPF control. The class is a wrapper for the unmanaged class implementing the main window; it is used as a bridge between managed and unmanaged layers of the application, it is instantiated immediately before creating the compound WPF control and it is passed to the control’s constructor as reference to the interface. The control keeps this reference and can invoke methods impelemented in the wrapper when required. Here is the definition of the interface and its impelementation:

C#
namespace SampleWpfUserControlLibrary
{
    public interface IApplicationHostWindow
    {
        void OpenDocument();
        void Exit();
    }
}
C++
using namespace SampleWpfUserControlLibrary;
 
ref class ApplicationHostWrapper : IApplicationHostWindow
{
public:
	ApplicationHostWrapper(CMainFrame * pMainFrame)
	{
		_pMainFrame = pMainFrame;
	}
	
	virtual void __clrcall Exit() sealed
	{
		_pMainFrame->SendMessage(WM_CLOSE);
	}
 
	virtual void __clrcall OpenDocument() sealed
	{
		_pMainFrame->OpenDocument();
	}
 
private:
	CMainFrame * _pMainFrame;
};

By now I’ve implemented all the features that we would have using the Office Viewer ActiveX; also, I could run any number of our application instances and view slideshow in all of them simultaneously, which is not possible when using the Office Viewer. But what about showing a second window with slideshow synchronised with the main window? If you remember, the impossibility of implementing this feature made me abandon the Office Viewer and start looking for alternative solutions. Well, actually in this hybrid solution this task was solved in a very simple and straightforward manner: when opening a presentation I would just create a second frame window, second OLE document and second view and open the presentation file once again. When the user clicks on the presentation view in the main window, the message-handling code finds the reference to the second document and instructs it to switch to the next slide. In the meanwhile the second view is configured not to process mouse clicks. You can see the details of this implementation in the attached sample projects. There’s just one drawback with this simultaneous slideshow in two windows: when viewing a presentation which contains animations you may find that dynamic slides are not synchronised. This is due to the fact that the function which instructs PowerPoint to switch to the next slide works synchronously and returns only after all graphic transitions to the next slide have been made. I haven’t found a solution for that issue yet. 

Demo project 

The demo project for this article can be downloaded from here. It contains a Visual Studio 2008 solution with two C++ projects (application and dll for subclassing) and one C# project (class library with the WPF composite control). You can build and run it. From the main window menu select File\Open and specify a PowerPoint presentation. Notice that you need PowerPoint installed on your computer to run this demo. 

Conclusion  

The article and the demo project prove that it is possible to embed a PowerPoint presentation player into a custom application having full programmatic control over its behaviour. When opening a presentation it’s not always necessary to display it, you can do it invisibly just to acquire some data from the file (in our case all we need to achieve that is to make the view invisible), while it is impossible to do so when interacting with PowerPoint through its COM interface, as I mentioned earlier, because PowerPoint application window always becomes visible. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Luxoft Professional, Deutsche Bank ODC
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionRe: How to pause/resume the powerpoint presentation Pin
rupeshbari8311-Jan-16 19:51
rupeshbari8311-Jan-16 19:51 
QuestionMagic! but how can I use the UserControl SampleUserControl in my WPF application Pin
Kevin881922-Oct-12 15:27
Kevin881922-Oct-12 15:27 
QuestionEmbedding it to a VB.NET Form Pin
Marvin Pereda10-Oct-12 17:18
Marvin Pereda10-Oct-12 17:18 
QuestionCOM Interop without showing PowerPoint works! Pin
Marc Greiner at home8-Oct-12 21:46
Marc Greiner at home8-Oct-12 21:46 
AnswerRe: COM Interop without showing PowerPoint works! Pin
Marvin Pereda10-Oct-12 18:39
Marvin Pereda10-Oct-12 18:39 
GeneralRe: COM Interop without showing PowerPoint works! Pin
Marc Greiner at home10-Oct-12 21:27
Marc Greiner at home10-Oct-12 21:27 
GeneralRe: COM Interop without showing PowerPoint works! Pin
Marvin Pereda10-Oct-12 21:37
Marvin Pereda10-Oct-12 21:37 
GeneralRe: COM Interop without showing PowerPoint works! Pin
Marvin Pereda10-Oct-12 21:50
Marvin Pereda10-Oct-12 21:50 
GeneralRe: COM Interop without showing PowerPoint works! Pin
Marc Greiner at home10-Oct-12 22:28
Marc Greiner at home10-Oct-12 22:28 
GeneralRe: COM Interop without showing PowerPoint works! Pin
Marvin Pereda10-Oct-12 22:34
Marvin Pereda10-Oct-12 22:34 
GeneralRe: COM Interop without showing PowerPoint works! Pin
Todd Fairfoul14-Jun-13 8:17
Todd Fairfoul14-Jun-13 8:17 
QuestionEnable Right Click Pin
Marvin Pereda19-Sep-12 18:43
Marvin Pereda19-Sep-12 18:43 
QuestionMy vote of 2 Pin
Valentin Billotte5-Jul-12 6:02
Valentin Billotte5-Jul-12 6:02 
AnswerRe: My vote of 2 Pin
Vsevolod Belousov5-Jul-12 9:54
Vsevolod Belousov5-Jul-12 9:54 
GeneralRe: My vote of 2 Pin
Valentin Billotte5-Jul-12 10:15
Valentin Billotte5-Jul-12 10:15 
QuestionPlease help. Pin
woogoob17-May-12 8:15
woogoob17-May-12 8:15 
AnswerRe: Please help. Pin
Marvin Pereda12-Sep-12 4:17
Marvin Pereda12-Sep-12 4:17 
GeneralMy vote of 5 Pin
DaanDL20-Feb-12 20:55
DaanDL20-Feb-12 20:55 
Questionppt viewer Pin
DaanDL20-Feb-12 5:57
DaanDL20-Feb-12 5:57 
AnswerRe: ppt viewer Pin
Vsevolod Belousov20-Feb-12 8:24
Vsevolod Belousov20-Feb-12 8:24 
GeneralRe: ppt viewer Pin
DaanDL20-Feb-12 21:36
DaanDL20-Feb-12 21:36 
GeneralRe: ppt viewer Pin
Vsevolod Belousov21-Feb-12 3:38
Vsevolod Belousov21-Feb-12 3:38 
GeneralRe: ppt viewer Pin
DaanDL21-Feb-12 3:54
DaanDL21-Feb-12 3:54 
GeneralRe: ppt viewer Pin
Vsevolod Belousov21-Feb-12 3:57
Vsevolod Belousov21-Feb-12 3:57 
QuestionCould you please give us a sample demo with source code for this project? Pin
kingmax5421200816-Feb-12 14:05
kingmax5421200816-Feb-12 14:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.