|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionFirst 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 ItLet'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 <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 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 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: [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 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: 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. 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. 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 In order to do it, we'll derive from 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 if (e.Property == ContentControl.ContentProperty)
{
mainWindowHandle.AttachPreview(e.NewValue.ToString(),actualRect);
}
We're done. The last thing to do is to implement the 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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||