Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / Windows Forms
Article

Using Vista Preview Handlers in a WPF Application

Rate me:
Please Sign up or sign in to vote.
4.72/5 (15 votes)
22 Apr 2008CPOL3 min read 127.2K   3K   29   26
This article is about how to use Windows Vista Preview handlers within a WPF application

Introduction

First of all, what is a preview handler? Preview handler is a COM object, that is called when you want to display the preview of your item. In 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 our WPF application? Probably we can. Let’s see how we can do it.

Let's Do It

Let's create a simple WPF window that displays a file list from the left and preview of items on the right side. We'll use a simple file list string collection as our datasource, bind it to Listbox Items and then bind the selected item to some contentpresenter. I blogged about this approach earlier.

XML
<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 with changes of the file system. So, this is a very good chance to use the FileSystemWatcher object.

C#
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 a simple DataTemplate, we can see the file list in the left pane of our application. It will be updated automatically upon file change in a certain directory.

The next step is to understand how to use preview handlers within a custom application. After all, a preview handler is a regular COM object that implements the following interfaces:

C#
[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 the preview handler to a specific file type, all we have to do is to look into HKEY_CLASSES_ROOT and find COM Guid of the 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 file. Let's do it:

C#
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 the appropriate COM instance for the preview handler:

C#
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 its own interface. So, we can only check if the object created implements this interface to be able to initialize the handler.

C#
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 a handle to the window we want the handler to sit in. We should also provide bounds of region of the window to the handler to be placed in.

C#
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, the 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 listen to its ActualHeight and ActualWidth properties. First get the window handler (it won't be changed during the application life cycle), then update the layout of our WPF preview handler for the region and bounds of the control.

C#
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 attach the preview handlers for the displayed file to the control:

C#
if (e.Property == ContentControl.ContentProperty) 
{ 
mainWindowHandle.AttachPreview(e.NewValue.ToString(),actualRect); 
}

We're done. The last thing to do is to implement the IStream interface in our COMStream C# class in order to be able to load streaming content (for example, for the PDF previewer):

C#
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're finished. We can use unmanaged preview handlers to display content of our files, held by the 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.

History

  • 22nd April, 2008: Initial post

License

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


Written By
Architect Better Place
Israel Israel
Hello! My name is Tamir Khason, and I am software architect, project manager, system analyst and [of course] programmer. In addition to writing big amount of documentation, I also write code, a lot of code. I used to work as a freelance architect, project manager, trainer, and consultant here, in Israel, but recently join the company with extremely persuasive idea - to make a world better place. I have very pretty wife and 3 charming kids, but unfortunately almost no time for them.

To be updated within articles, I publishing, visit my blog or subscribe RSS feed. Also you can follow me on Twitter to be up to date about my everyday life.

Comments and Discussions

 
QuestionDownloaded ZIP. Won't run. Pin
Alan Baljeu25-Feb-16 6:27
Alan Baljeu25-Feb-16 6:27 
QuestionFound Bug in Initialize IStream Pin
Andi Lexen30-Sep-15 3:35
Andi Lexen30-Sep-15 3:35 
QuestionWindows 8 - PreviewHandler is not working for pdf Pin
Joy George K8-Jan-14 3:27
professionalJoy George K8-Jan-14 3:27 
Questioncreating preview handler for outlook using c++ (how to host the filename.dll in outlook for creating preview in preview pane of outlook) Pin
Member 1005345328-Jun-13 1:40
Member 1005345328-Jun-13 1:40 
GeneralMy vote of 5 Pin
apoplex200821-Nov-12 13:45
apoplex200821-Nov-12 13:45 
Questionpreview error Pin
Member 789437627-Aug-12 23:51
Member 789437627-Aug-12 23:51 
Questiongetting error while previewing... Pin
Member 789437619-Aug-12 18:45
Member 789437619-Aug-12 18:45 
QuestionPDF Preview Pin
Arun Raj R A25-Aug-11 21:53
Arun Raj R A25-Aug-11 21:53 
QuestionIt is working fine for me, but I am facing a problem with the preivew control UI. Please help Pin
Ravi Shankar K25-Jul-11 9:34
Ravi Shankar K25-Jul-11 9:34 
AnswerRe: It is working fine for me, but I am facing a problem with the preivew control UI. Please help Pin
Member 789437620-Aug-12 23:35
Member 789437620-Aug-12 23:35 
GeneralIInitializeWithStream streamInit = o as IInitializeWithStream returns null Pin
santoshrvinnu15-Apr-11 4:44
santoshrvinnu15-Apr-11 4:44 
Questionhow to preview or Open media files and Images "using Vista Preview Handlers in a WPF Application" Pin
hemant.tawri3-Mar-11 3:19
hemant.tawri3-Mar-11 3:19 
GeneralWindows 7 Pin
Anu Bhaskar1-Oct-09 8:24
Anu Bhaskar1-Oct-09 8:24 
Has any one gotten this to work in Windows 7?

In RC 1, the file names show up where the preview is supposed to show, and in RC2 the program breaks before displaying anything.

I am yet to try it with the RTM version...should hopefully get around to installing that by this weekend.

Thanks,
Anu
QuestionGreat work! Pin
localadam14-Apr-09 17:15
localadam14-Apr-09 17:15 
Questionpreviewer resizing itself? [modified] Pin
Jonah Simpson30-Mar-09 11:04
Jonah Simpson30-Mar-09 11:04 
QuestionRe: previewer resizing itself? Pin
Jonah Simpson30-Mar-09 11:12
Jonah Simpson30-Mar-09 11:12 
GeneralSystem.NotSupportedException: unknown pixel format Pin
halson28-Jan-09 20:19
halson28-Jan-09 20:19 
Generalcast problems Pin
haarongonzalez23-Jul-08 10:16
haarongonzalez23-Jul-08 10:16 
GeneralUnable to cast COM object of type 'System.__ComObject' Pin
jayds122-Apr-08 23:21
jayds122-Apr-08 23:21 
GeneralRe: Unable to cast COM object of type 'System.__ComObject' Pin
Tamir Khason23-Apr-08 1:46
Tamir Khason23-Apr-08 1:46 
GeneralRe: Unable to cast COM object of type 'System.__ComObject' Pin
jayds123-Apr-08 2:31
jayds123-Apr-08 2:31 
GeneralRe: Unable to cast COM object of type 'System.__ComObject' Pin
Tamir Khason23-Apr-08 2:35
Tamir Khason23-Apr-08 2:35 
GeneralRe: Unable to cast COM object of type 'System.__ComObject' Pin
jayds123-Apr-08 3:25
jayds123-Apr-08 3:25 
GeneralRe: Unable to cast COM object of type 'System.__ComObject' Pin
Tamir Khason23-Apr-08 19:50
Tamir Khason23-Apr-08 19:50 
GeneralRe: Unable to cast COM object of type 'System.__ComObject' Pin
Phlik12-Mar-09 4:35
Phlik12-Mar-09 4:35 

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.