65.9K
CodeProject 正在变化。 阅读更多。
Home

在 WPF 应用程序中使用 Vista 预览处理程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (14投票s)

2008年4月22日

CPOL

3分钟阅读

viewsIcon

132868

downloadIcon

3078

本文介绍如何在 WPF 应用程序中使用 Windows Vista 预览处理程序

引言

首先,什么是预览处理程序?预览处理程序是一个 COM 对象,在您想要显示项目预览时会被调用。换句话说,预览处理程序是文件内容的一种轻量级、丰富且只读的预览,显示在阅读窗格中。您可以在 Microsoft Outlook 2007、Windows Vista 甚至有时在 XP 中找到预览处理程序。我们可以在 WPF 应用程序中使用预览处理程序吗?也许可以。让我们看看如何做到这一点。

让我们开始吧

让我们创建一个简单的 WPF 窗口,它在左侧显示文件列表,在右侧显示项目预览。我们将使用一个简单的文件列表string集合作为我们的数据源,将其绑定到Listbox的 Items,然后将选定的项绑定到某个contentpresenter。我之前已经写过关于这种方法的博客

<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>  

我们的数据源应该随着文件系统的变化而自动更新。因此,这是一个使用FileSystemWatcher对象的好机会。

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); 
} 
} 

现在,在应用了简单的DataTemplate之后,我们可以在应用程序的左侧窗格中看到文件列表。当某个目录下的文件发生更改时,它将自动更新。

下一步是了解如何在自定义应用程序中使用预览处理程序。毕竟,预览处理程序是一个实现了以下接口的常规 COM 对象

[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); 
}

为了查找并为特定文件类型附加预览处理程序,我们只需要查看HKEY_CLASSES_ROOT并找到预览处理程序的 COM Guid(8895b1c6-b41f-4c1c-a562-0d564250836f)。此键的默认值将是实际可以预览此文件类型的 COM 对象的 Guid。让我们这样做

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());

现在,我们知道该文件可以被预览。因此,让我们初始化适当的 COM 实例作为预览处理程序

Type a = Type.GetTypeFromCLSID(g, true); 
object o = Activator.CreateInstance(a); 

预览处理程序有两种初始化方式——基于文件和基于流。每种方式都有自己的接口。因此,我们只能检查创建的对象是否实现了该接口,才能初始化处理程序。

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; 
}

在初始化了处理程序之后,我们可以设置一个句柄,指示处理程序应该放置在哪个窗口中。我们还应该向处理程序提供窗口区域的边界,以便其放置在该区域中。

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(); 
}
}

到目前为止一切顺利,但我们是在 WPF 中。因此,我们使用的ContentPresenter没有句柄!这是正确的,但是主 WPF 应用程序窗口有。所以,让我们先获取主应用程序窗口的句柄,然后创建ContentControl所占据区域的矩形边界。

为了做到这一点,我们将从ContentPresenter派生,并监听其ActualHeightActualWidth属性。首先获取窗口句柄(它在应用程序生命周期中不会改变),然后更新我们 WPF 预览处理程序的布局,使其适应控件的区域和边界。

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); 
} 
} 

现在,我们唯一需要做的就是监听ContentProperty的变化,并为显示的文件将预览处理程序附加到控件上

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

我们完成了。最后一件要做的事情是在我们的COMStream C# 类中实现IStream接口,以便能够加载流式内容(例如,用于 PDF 预览器)

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 
} 

现在我们完成了。我们可以使用非托管的预览处理程序来显示 WPF 应用程序中保存的文件的内容。此外,如果您愿意,可以创建自己的预览处理程序,它们将出现在您的 WPF 应用程序中,就像它们会自动出现在 Outlook 中一样。

历史

  • 2008年4月22日:初始发布
© . All rights reserved.