Using Vista Preview Handlers in WPF application
First of all what is Preview Handler? Preview Handler is COM object, that called when you want to display the preview of your item. Other words, Preview Handlers are lightweight, rich and read-only previews of file’s content in a reading pane. You can find preview handlers in Microsoft Outlook 2007, Windows Vista and, even sometimes in XP. Can we use preview handlers within your WPF application? Probably we can. Let’s see how we can do it.
Let’s create simple WPF window, that displays file list from left and preview of items in right side. We’ll use simple file list string collection as our datasource, bind it to Listbox Items and then bind selected item to some contentpresenter. I blogged about this approach earlier.
<Grid DataContext={StaticResource files}>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".2*"/>
<ColumnDefinition Width=".8*"/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource={Binding} IsSynchronizedWithCurrentItem="True" />
<ContentPresenter Grid.Column=”1” Content={Binding Path=/}/>
<GridSplitter Width="5"/>
</Grid>
Our data source should be updated automatically within changes of file system. So, this is very good chance to use FileSystemWatcher object.
class ListManager:ThreadSafeObservableCollection<string>
{
string dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
public ListManager()
{
FileSystemWatcher fsw = new FileSystemWatcher(dir);
fsw.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
fsw.Created += new FileSystemEventHandler(fsw_Created);
fsw.Deleted += new FileSystemEventHandler(fsw_Deleted);fsw.EnableRaisingEvents = true;
string[] files = Directory.GetFiles(dir);
for (int i = 0; i < files.Length; i++)
{
base.Add(files[i]);
}}
void fsw_Deleted(object sender, FileSystemEventArgs e)
{
base.Remove(e.FullPath);
}void fsw_Created(object sender, FileSystemEventArgs e)
{
base.Add(e.FullPath);
}
}
Now, after applying simple DataTemplate, we can see file list in the left pane of our application. It will be updated automatically upon files change in certain directory.
Next step is to understand how to use Preview Handlers within custom application. After all, preview handler is regular COM object, that implements following interfaces
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("8895b1c6-b41f-4c1c-a562-0d564250836f")]
interface IPreviewHandler
{
void SetWindow(IntPtr hwnd, ref RECT rect);
void SetRect(ref RECT rect);
void DoPreview();
void Unload();
void SetFocus();
void QueryFocus(out IntPtr phwnd);
[PreserveSig]
uint TranslateAccelerator(ref MSG pmsg);
}[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("b7d14566-0509-4cce-a71f-0a554233bd9b")]
interface IInitializeWithFile
{
void Initialize([MarshalAs(UnmanagedType.LPWStr)] string pszFilePath, uint grfMode);
}[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
interface IInitializeWithStream
{
void Initialize(IStream pstream, uint grfMode);
}
In order to find and attach preview handler to specific file type, all we have to do is to look into HKEY_CLASSES_ROOT and find COM Guid of preview handler (8895b1c6-b41f-4c1c-a562-0d564250836f). The default value of this key will be the Guid of COM object, that actually can preview this type of files. Let’s do it
string CLSID = "8895b1c6-b41f-4c1c-a562-0d564250836f";
Guid g = new Guid(CLSID);
string[] exts = fileName.Split(‘.’);
string ext = exts[exts.Length - 1];
using (RegistryKey hk = Registry.ClassesRoot.OpenSubKey(string.Format(@".{0}\ShellEx\{1:B}", ext, g)))
{
if (hk != null)
{
g = new Guid(hk.GetValue("").ToString());
Now, we know, that this file can be previewed, thus let’s initialize appropriate COM instance for preview handler
Type a = Type.GetTypeFromCLSID(g, true);
object o = Activator.CreateInstance(a);
There are two kinds of initializations for preview handlers – file and stream based. Each one has it’s own interface. So, we can only check if the object created implements this interface to be able to initialize the handler
IInitializeWithFile fileInit = o as IInitializeWithFile;
IInitializeWithStream streamInit = o as IInitializeWithStream;bool isInitialized = false;
if (fileInit != null)
{
fileInit.Initialize(fileName, 0);
isInitialized = true;
}
else if (streamInit != null)
{
COMStream stream = new COMStream(File.Open(fileName, FileMode.Open));
streamInit.Initialize((IStream)streamInit, 0);
isInitialized = true;
}
After we initialized the handler we can set handle to the window we want the handler to sit in. Also we should provide bounds of region of the window to handler be placed in.
if (isInitialized)
{
pHandler = o as IPreviewHandler;
if (pHandler != null)
{
RECT r = new RECT(viewRect);
pHandler.SetWindow(handler, ref r);
pHandler.SetRect(ref r);pHandler.DoPreview();
}
}
So far so good, but we’re in WPF. Thus ContentPresenter we’re using has no handle! That’s right, but the main WPF application window has. So, let’s first get the main application window handle, then create rectangle bounds of the region, occupied by ContentControl.
In order to do it, we’ll derive from ContentPresenter and will listen to ActualtHeight and ActualeWidth property of it. First get the window handler (it wont be changed during the application life cycle), then update layout of our WPF Preview Handler for region and bounds of the control.
class WPFPreviewHandler : ContentPresenter
{
IntPtr mainWindowHandle = IntPtr.Zero;
Rect actualRect = new Rect();protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == ContentControl.ActualHeightProperty | e.Property == ContentControl.ActualWidthProperty)
{
if (mainWindowHandle == IntPtr.Zero)
{
HwndSource hwndSource = PresentationSource.FromVisual(App.Current.MainWindow) as HwndSource;
mainWindowHandle = hwndSource.Handle;
}
else
{
Point p0 = this.TranslatePoint(new Point(),App.Current.MainWindow);
Point p1 = this.TranslatePoint(new Point(this.ActualWidth,this.ActualHeight),App.Current.MainWindow);
actualRect = new Rect(p0, p1);
mainWindowHandle.InvalidateAttachedPreview(actualRect);
}
}
…public static void InvalidateAttachedPreview(this IntPtr handler, Rect viewRect)
{
if (pHandler != null)
{
RECT r = new RECT(viewRect);
pHandler.SetRect(ref r);
}
}
Now, the only thing we have to do is to listen for ContentProperty change and attache the preview handlers for displayed file to the control
if (e.Property == ContentControl.ContentProperty)
{
mainWindowHandle.AttachPreview(e.NewValue.ToString(),actualRect);
}
We done. Last thing to do is to implement IStream interface in our COMStream C# class in order to be able to load streaming content (for example for PDF previewer)
public sealed class COMStream : IStream, IDisposable
{
Stream _stream;~COMStream()
{
if (_stream != null)
{
_stream.Close();
_stream.Dispose();
_stream = null;
}
}private COMStream() { }
public COMStream(Stream sourceStream)
{
_stream = sourceStream;
}#region IStream Members
public void Clone(out IStream ppstm)
{
throw new NotSupportedException();
}public void Commit(int grfCommitFlags)
{
throw new NotSupportedException();
}public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
{
throw new NotSupportedException();
}public void LockRegion(long libOffset, long cb, int dwLockType)
{
throw new NotSupportedException();
}[SecurityCritical]
public void Read(byte[] pv, int cb, IntPtr pcbRead)
{
int count = this._stream.Read(pv, 0, cb);
if (pcbRead != IntPtr.Zero)
{
Marshal.WriteInt32(pcbRead, count);
}
}public void Revert()
{
throw new NotSupportedException();
}[SecurityCritical]
public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
{
SeekOrigin origin = (SeekOrigin)dwOrigin;
long pos = this._stream.Seek(dlibMove, origin);
if (plibNewPosition != IntPtr.Zero)
{
Marshal.WriteInt64(plibNewPosition, pos);
}
}public void SetSize(long libNewSize)
{
this._stream.SetLength(libNewSize);
}public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
{
pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();
pstatstg.type = 2;
pstatstg.cbSize = this._stream.Length;
pstatstg.grfMode = 0;
if (this._stream.CanRead && this._stream.CanWrite)
{
pstatstg.grfMode |= 2;
}
else if (this._stream.CanWrite && !_stream.CanRead)
{
pstatstg.grfMode |= 1;
}
else
{
throw new IOException();
}}
public void UnlockRegion(long libOffset, long cb, int dwLockType)
{
throw new NotSupportedException();
}[SecurityCritical]
public void Write(byte[] pv, int cb, IntPtr pcbWritten)
{
this._stream.Write(pv, 0, cb);
if (pcbWritten != IntPtr.Zero)
{
Marshal.WriteInt32(pcbWritten, cb);
}
}#endregion
#region IDisposable Members
public void Dispose()
{
if (this._stream != null)
{
this._stream.Close();
this._stream.Dispose();
this._stream = null;
}
}#endregion
}
And now we finished. We can use unmanaged preview handlers to display content of our files, hold by WPF application. Also, if you want, you can create your own preview handlers and they’ll appear in your WPF application as well as they’ll magically appear in Outlook. Following full source code for this article
Good day, Happy Passover and, as always, be good people.
April 17th, 2008 · Comments (9)
9 Responses to “Using Vista Preview Handlers in WPF application”
Leave a Reply
Discover other tags
My tools
- .NET Framework Detector
- Duplicate images finder
- Exchange Security Policy for Windows Mobile Devices Fix
- Gas Price Windows Vista SideBar gadget
- Israel Traffic Information Windows Vista SideBar gadget
- Localization fix for SAP ES Explorer for Visual Studio
- LocTester
- RTL and LTR in Windows Live Writer
- Silverlight controls library
- Snipping tool integration plugin for WLW
- USB FM receiver library
- Vista Battery Saver
- WebCam control for WPF
- Windows Live SkyDrive attachment for Windows Live Writer
- Wireless Migrator
- WPF Virtual Keyboard





January 1st, 2009 at 12:44 am
Hi, John
I checked this with Foxit handlers and it works for me. I think, your problem is FileStream provider. Look into source code to find commented area about it
January 1st, 2009 at 12:44 am
Tamir
Thanks for this wonderful piece. I tested your code with Foxit’s PDF preview handler. It does’nt seem to work. Do you know why ?
The preview handler works with Windows Explorer. It seems to return null for IInitializeWithFile when I use with your code.
timheuer.com/…/13945.aspx
John T.
January 1st, 2009 at 1:05 am
[...] then the original file. Those files are, actually, printout of the original document. Also we know how to use Windows Vista Preview Handler to view original MS Office files inside WPF application. So why not to work with the originals? Why not to convert Microsoft Office document into [...]
March 31st, 2009 at 1:16 pm
Hi Tamir!
I’ve been playing with this example and I’ve noticed a couple problems that I was hoping you could help me with.
Specifically, when I try to run the Preview Handler for PowerPoint or PDF they’re constrained in size by the rect appropriately, but don’t seem to be positioned properly (top left corner is set to 0,0 which causes the Preview Handler to be directly over top of the list for selecting the file to preview)
When I try to run the preview handlers for Visio or Word, getting focus causes them to resize to the full size of the window.
I’m running your example pretty much verbatim (other than a bug fix to IStreamProvider so the PDF reader works).
My environment is .NET 3.5 SP1, Office 2007 SP1, 32-bit Vista Ultimate, VS 2008 SP1
Any ideas?
March 31st, 2009 at 1:18 pm
Hi Tamir!
I’ve been playing with this example and I’ve noticed a couple problems that I was hoping you could help me with.
Specifically, when I try to run the Preview Handler for PowerPoint or PDF they’re constrained in size by the rect appropriately, but don’t seem to be positioned properly (top left corner is set to 0,0 which causes the Preview Handler to be directly over top of the list for selecting the file to preview)
When I try to run the preview handlers for Visio or Word, getting focus causes them to resize to the full size of the window.
I’m running your example pretty much verbatim (other than a bug fix to IInitializeWithStream so the PDF reader works).
My environment is .NET 3.5 SP1, Office 2007 SP1, 32-bit Vista Ultimate, VS 2008 SP1
Any ideas?
September 8th, 2009 at 4:01 am
i need to display selected file detail in file explorer
just as in win vista and win 7
file explorer is opening as in win vista or win 7
but it is not showing detail of selected file
help………….
mail me
thakur_amit15@rediffmail.com
September 29th, 2009 at 4:05 pm
Hello,
Thanks for this write-up.
I am trying to run your solution on Windows 7. The solution compiles fine (in VS 2008), but when I run the solution, an OutOfMemoryException is thrown on the line “pHandler.DoPreview();”. Interestingly, if I stop the program and then run it again, it breaks at an earlier line: “fileInit.Initialize(fileName, 0);” saying the COM Server is giving an error.
Even more interestingly, MS Excel pops up when I run the app, and then Windows tells me that Excel is not responding.
I am wondering if there is some issue w/ the excel file formats being a previewable format in Windows 7 (I think it wasn’t in Vista, but not sure).
Has any one been able to run the solution in Windows 7 successfully?
Thanks in advance.
-Anu
May 11th, 2010 at 10:09 am
Question to Jonah Simpson
What is the bug fix to IStreamProvider so PDF would work ?
Is there a solution to the focus bug ?
February 6th, 2012 at 2:01 am
I used the code for previewing MS office word.I suppose that its holding the WINWORD.EXE process.Even if I closes the document,the process continues to run.Is the previewer responsible for that?