add preview handler

This commit is contained in:
2021-12-28 16:53:45 +08:00
parent 0f48dbe845
commit c03de86d02
14 changed files with 1613 additions and 91 deletions

View File

@ -0,0 +1,42 @@
#if PREVIEW_HANDLER
using System.IO;
namespace ShellExtensions
{
/// <summary>
/// This interface exposes the <see cref="Load"/> function for initializing the
/// Preview Handler with a <typeparamref name="Stream"/>.
/// This interface can be used in conjunction with the other intialization interfaces,
/// but only 1 will be accessed according to the priorities preset by the Windows Shell:
/// <typeparamref name="IPreviewFromStream"/>
/// <typeparamref name="IPreviewFromShellObject"/>
/// <typeparamref name="IPreviewFromFile"/>
/// </summary>
public interface IPreviewFromStream
{
/// <summary>
/// Provides the <typeparamref name="Stream"/> to the item from which a preview should be created.
/// </summary>
/// <param name="stream">Stream to the previewed file, this stream is only available in the scope of this method.</param>
void Load(Stream stream);
}
/// <summary>
/// This interface exposes the <see cref="Load"/> function for initializing the
/// Preview Handler with a <typeparamref name="FileInfo"/>.
/// This interface can be used in conjunction with the other intialization interfaces,
/// but only 1 will be accessed according to the priorities preset by the Windows Shell:
/// <typeparamref name="IPreviewFromStream"/>
/// <typeparamref name="IPreviewFromShellObject"/>
/// <typeparamref name="IPreviewFromFile"/>
/// </summary>
public interface IPreviewFromFile
{
/// <summary>
/// Provides the <typeparamref name="FileInfo"/> to the item from which a preview should be created.
/// </summary>
/// <param name="info">File information to the previewed file.</param>
void Load(FileInfo info);
}
}
#endif

View File

@ -0,0 +1,377 @@
#if PREVIEW_HANDLER
using Microsoft.Win32;
using ShellExtensions.Interop;
using ShellExtensions.Interop.Common;
using ShellExtensions.Resources;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace ShellExtensions
{
/// <summary>
/// This is the base class for all preview handlers and provides their basic functionality.
/// To create a custom preview handler a class must derive from this, use the <typeparamref name="PreviewHandlerAttribute"/>,
/// and implement 1 or more of the following interfaces:
/// <typeparamref name="IPreviewFromStream"/>,
/// <typeparamref name="IPreviewFromShellObject"/>,
/// <typeparamref name="IPreviewFromFile"/>.
/// </summary>
public abstract class PreviewHandler : ICustomQueryInterface, IPreviewHandler, IPreviewHandlerVisuals, IOleWindow, IObjectWithSite, IInitializeWithStream, IInitializeWithFile
{
private bool _isPreviewShowing;
private IntPtr _parentHwnd;
private IPreviewHandlerFrame _frame;
/// <summary>
/// Gets whether the preview is currently showing
/// </summary>
public bool IsPreviewShowing => _isPreviewShowing;
/// <summary>
/// Called immediately before the preview is to be shown.
/// </summary>
protected virtual void Initialize() { }
/// <summary>
/// Called when the preview is no longer shown.
/// </summary>
protected virtual void Uninitialize() { }
#region Required functions - Abstract functions
/// <summary>
/// This should return the window handle to be displayed in the Preview.
/// </summary>
protected abstract IntPtr Handle { get; }
/// <summary>
/// Called to update the bounds and position of the preview control
/// </summary>
/// <param name="bounds"></param>
protected abstract void UpdateBounds(NativeRect bounds);
/// <summary>
/// Called when an exception occurs during the initialization of the control
/// </summary>
/// <param name="caughtException"></param>
protected abstract void HandleInitializeException(Exception caughtException);
/// <summary>
/// Called when the preview control obtains focus.
/// </summary>
protected abstract void SetFocus();
/// <summary>
/// Called when a request is received to set or change the background color according to the user's preferences.
/// </summary>
/// <param name="color">An int representing the ARGB color</param>
protected abstract void SetBackground(int argb);
/// <summary>
/// Called when a request is received to set or change the foreground color according to the user's preferences.
/// </summary>
/// <param name="color">An int representing the ARGB color</param>
protected abstract void SetForeground(int argb);
/// <summary>
/// Called to set the font of the preview control according to the user's preferences.
/// </summary>
/// <param name="font"></param>
protected abstract void SetFont(LogFont font);
/// <summary>
/// Called to set the parent of the preview control.
/// </summary>
/// <param name="handle"></param>
protected abstract void SetParentHandle(IntPtr handle);
#endregion
#region IPreviewHandler Members
void IPreviewHandler.SetWindow(IntPtr hwnd, ref NativeRect rect)
{
_parentHwnd = hwnd;
UpdateBounds(rect);
SetParentHandle(_parentHwnd);
}
void IPreviewHandler.SetRect(ref NativeRect rect)
{
UpdateBounds(rect);
}
void IPreviewHandler.DoPreview()
{
_isPreviewShowing = true;
try
{
Initialize();
}
catch (Exception exc)
{
HandleInitializeException(exc);
}
}
void IPreviewHandler.Unload()
{
Uninitialize();
_isPreviewShowing = false;
}
void IPreviewHandler.SetFocus()
{
SetFocus();
}
void IPreviewHandler.QueryFocus(out IntPtr phwnd)
{
phwnd = HandlerNativeMethods.GetFocus();
}
HResult IPreviewHandler.TranslateAccelerator(ref Message pmsg)
{
return _frame != null ? _frame.TranslateAccelerator(ref pmsg) : HResult.False;
}
#endregion
#region IPreviewHandlerVisuals Members
void IPreviewHandlerVisuals.SetBackgroundColor(NativeColorRef color)
{
SetBackground((int)color.Dword);
}
void IPreviewHandlerVisuals.SetTextColor(NativeColorRef color)
{
SetForeground((int)color.Dword);
}
void IPreviewHandlerVisuals.SetFont(ref LogFont plf)
{
SetFont(plf);
}
#endregion
#region IOleWindow Members
void IOleWindow.GetWindow(out IntPtr phwnd)
{
phwnd = Handle;
}
void IOleWindow.ContextSensitiveHelp(bool fEnterMode)
{
// Preview handlers don't support context sensitive help. (As far as I know.)
throw new NotImplementedException();
}
#endregion
#region IObjectWithSite Members
void IObjectWithSite.SetSite(object pUnkSite)
{
_frame = pUnkSite as IPreviewHandlerFrame;
}
void IObjectWithSite.GetSite(ref Guid riid, out object ppvSite)
{
ppvSite = _frame;
}
#endregion
#region IInitializeWithStream Members
void IInitializeWithStream.Initialize(System.Runtime.InteropServices.ComTypes.IStream stream, AccessModes fileMode)
{
IPreviewFromStream preview = this as IPreviewFromStream;
if (preview == null)
{
throw new InvalidOperationException(
string.Format(System.Globalization.CultureInfo.InvariantCulture,
LocalizedMessages.PreviewHandlerUnsupportedInterfaceCalled,
nameof(IPreviewFromStream)));
}
using (var storageStream = new StorageStream(stream, fileMode != AccessModes.ReadWrite))
{
preview.Load(storageStream);
}
}
#endregion
#region IInitializeWithFile Members
void IInitializeWithFile.Initialize(string filePath, AccessModes fileMode)
{
IPreviewFromFile preview = this as IPreviewFromFile;
if (preview == null)
{
throw new InvalidOperationException(
string.Format(System.Globalization.CultureInfo.InvariantCulture,
LocalizedMessages.PreviewHandlerUnsupportedInterfaceCalled,
nameof(IPreviewFromFile)));
}
preview.Load(new FileInfo(filePath));
}
#endregion
#region ComRegistration
/// <summary>
/// Called when the assembly is registered via RegAsm.
/// </summary>
/// <param name="registerType">Type to register.</param>
[ComRegisterFunction]
private static void Register(Type registerType)
{
if (registerType != null && registerType.IsSubclassOf(typeof(PreviewHandler)))
{
object[] attrs = registerType.GetCustomAttributes(typeof(PreviewHandlerAttribute), true);
if (attrs != null && attrs.Length == 1)
{
PreviewHandlerAttribute attr = attrs[0] as PreviewHandlerAttribute;
ThrowIfNotValid(registerType);
RegisterPreviewHandler(registerType.GUID, attr);
}
else
{
throw new NotSupportedException(
string.Format(System.Globalization.CultureInfo.InvariantCulture,
LocalizedMessages.PreviewHandlerInvalidAttributes, registerType.Name));
}
}
}
/// <summary>
/// Called when the assembly is Unregistered via RegAsm.
/// </summary>
/// <param name="registerType">Type to unregister</param>
[ComUnregisterFunction]
private static void Unregister(Type registerType)
{
if (registerType != null && registerType.IsSubclassOf(typeof(PreviewHandler)))
{
object[] attrs = registerType.GetCustomAttributes(typeof(PreviewHandlerAttribute), true);
if (attrs != null && attrs.Length == 1)
{
PreviewHandlerAttribute attr = attrs[0] as PreviewHandlerAttribute;
UnregisterPreviewHandler(registerType.GUID, attr);
}
}
}
private static void RegisterPreviewHandler(Guid previewerGuid, PreviewHandlerAttribute attribute)
{
string guid = previewerGuid.ToString("B");
// Create a new prevhost AppID so that this always runs in its own isolated process
using (RegistryKey appIdsKey = Registry.ClassesRoot.OpenSubKey("AppID", true))
using (RegistryKey appIdKey = appIdsKey.CreateSubKey(attribute.AppId))
{
appIdKey.SetValue("DllSurrogate", @"%SystemRoot%\system32\prevhost.exe", RegistryValueKind.ExpandString);
}
// Add preview handler to preview handler list
using (RegistryKey handlersKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", true))
{
handlersKey.SetValue(guid, attribute.Name, RegistryValueKind.String);
}
// Modify preview handler registration
using (RegistryKey clsidKey = Registry.ClassesRoot.OpenSubKey("CLSID"))
using (RegistryKey idKey = clsidKey.OpenSubKey(guid, true))
{
idKey.SetValue("DisplayName", attribute.Name, RegistryValueKind.String);
idKey.SetValue("AppID", attribute.AppId, RegistryValueKind.String);
idKey.SetValue("DisableLowILProcessIsolation", attribute.DisableLowILProcessIsolation ? 1 : 0, RegistryValueKind.DWord);
using (RegistryKey inproc = idKey.OpenSubKey("InprocServer32", true))
{
inproc.SetValue("ThreadingModel", "Apartment", RegistryValueKind.String);
}
}
foreach (string extension in attribute.Extensions.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
Trace.WriteLine("Registering extension '" + extension + "' with previewer '" + guid + "'");
// Set preview handler for specific extension
using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(extension))
using (RegistryKey shellexKey = extensionKey.CreateSubKey("shellex"))
using (RegistryKey previewKey = shellexKey.CreateSubKey(HandlerNativeMethods.IPreviewHandlerGuid.ToString("B")))
{
previewKey.SetValue(null, guid, RegistryValueKind.String);
}
}
}
private static void UnregisterPreviewHandler(Guid previewerGuid, PreviewHandlerAttribute attribute)
{
string guid = previewerGuid.ToString("B");
foreach (string extension in attribute.Extensions.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
Trace.WriteLine("Unregistering extension '" + extension + "' with previewer '" + guid + "'");
using (RegistryKey shellexKey = Registry.ClassesRoot.OpenSubKey(extension + "\\shellex", true))
{
shellexKey.DeleteSubKey(HandlerNativeMethods.IPreviewHandlerGuid.ToString(), false);
}
}
using (RegistryKey appIdsKey = Registry.ClassesRoot.OpenSubKey("AppID", true))
{
appIdsKey.DeleteSubKey(attribute.AppId, false);
}
using (RegistryKey classesKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", true))
{
classesKey.DeleteValue(guid, false);
}
}
private static void ThrowIfNotValid(Type type)
{
var interfaces = type.GetInterfaces();
if (!interfaces.Any(x => x is IPreviewFromStream || x is IPreviewFromFile))
{
throw new NotImplementedException(
string.Format(System.Globalization.CultureInfo.InvariantCulture,
LocalizedMessages.PreviewHandlerInterfaceNotImplemented,
type.Name));
}
}
#endregion
#region ICustomQueryInterface Members
CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv)
{
ppv = IntPtr.Zero;
// Forces COM to not use the managed (free threaded) marshaler
if (iid == HandlerNativeMethods.IMarshalGuid)
{
return CustomQueryInterfaceResult.Failed;
}
if ((iid == HandlerNativeMethods.IInitializeWithStreamGuid && !(this is IPreviewFromStream)) ||
(iid == HandlerNativeMethods.IInitializeWithFileGuid && !(this is IPreviewFromFile)))
{
return CustomQueryInterfaceResult.Failed;
}
return CustomQueryInterfaceResult.NotHandled;
}
#endregion
}
}
#endif

View File

@ -0,0 +1,48 @@
#if PREVIEW_HANDLER
using System;
namespace ShellExtensions
{
/// <summary>
/// This class attribute is applied to a Preview Handler to specify registration parameters.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class PreviewHandlerAttribute : Attribute
{
/// <summary>
/// Creates a new instance of the attribute.
/// </summary>
/// <param name="name">Name of the Handler</param>
/// <param name="extensions">Semi-colon-separated list of file extensions supported by the handler.</param>
/// <param name="appId">A unique guid used for process isolation.</param>
public PreviewHandlerAttribute(string name, string extensions, string appId)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Extensions = extensions ?? throw new ArgumentNullException(nameof(extensions));
AppId = appId ?? throw new ArgumentNullException(nameof(appId));
DisableLowILProcessIsolation = false;
}
/// <summary>
/// Gets the name of the handler.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the semi-colon-separated list of extensions supported by the preview handler.
/// </summary>
public string Extensions { get; private set; }
/// <summary>
/// Gets the AppId associated with the handler for use with the surrogate host process.
/// </summary>
public string AppId { get; private set; }
/// <summary>
/// Disables low integrity-level process isolation.
/// <remarks>This should be avoided as it could be a security risk.</remarks>
/// </summary>
public bool DisableLowILProcessIsolation { get; set; }
}
}
#endif

View File

@ -0,0 +1,119 @@
#if PREVIEW_HANDLER
using ShellExtensions.Interop;
using ShellExtensions.Interop.Common;
using ShellExtensions.Resources;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ShellExtensions
{
/// <summary>
/// This is the base class for all WinForms-based preview handlers and provides their basic functionality.
/// To create a custom preview handler that contains a WinForms user control,
/// a class must derive from this, use the <typeparamref name="PreviewHandlerAttribute"/>,
/// and implement 1 or more of the following interfaces:
/// <typeparamref name="IPreviewFromStream"/>,
/// <typeparamref name="IPreviewFromShellObject"/>,
/// <typeparamref name="IPreviewFromFile"/>.
/// </summary>
public abstract class WinFormsPreviewHandler : PreviewHandler, IDisposable
{
/// <summary>
/// This control must be populated by the deriving class before the preview is shown.
/// </summary>
public UserControl Control { get; protected set; }
protected void ThrowIfNoControl()
{
if (Control == null)
{
throw new InvalidOperationException(LocalizedMessages.PreviewHandlerControlNotInitialized);
}
}
/// <summary>
/// Called when an exception is thrown during itialization of the preview control.
/// </summary>
/// <param name="caughtException"></param>
protected override void HandleInitializeException(Exception caughtException)
{
if (caughtException == null) { throw new ArgumentNullException(nameof(caughtException)); }
Control = new UserControl();
Control.Controls.Add(new TextBox
{
ReadOnly = true,
Multiline = true,
Dock = DockStyle.Fill,
Text = caughtException.ToString(),
BackColor = Color.OrangeRed
});
}
protected override void UpdateBounds(NativeRect bounds)
{
Control.Bounds = Rectangle.FromLTRB(bounds.Left, bounds.Top, bounds.Right, bounds.Bottom);
Control.Visible = true;
}
protected override void SetFocus()
{
Control.Focus();
}
protected override void SetBackground(int argb)
{
Control.BackColor = Color.FromArgb(argb);
}
protected override void SetForeground(int argb)
{
Control.ForeColor = Color.FromArgb(argb);
}
protected override void SetFont(Interop.LogFont font)
{
Control.Font = Font.FromLogFont(font);
}
protected override IntPtr Handle
{
get
{
{
return Control.Handle;
}
}
}
protected override void SetParentHandle(IntPtr handle)
{
HandlerNativeMethods.SetParent(Control.Handle, handle);
}
#region IDisposable Members
~WinFormsPreviewHandler()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && Control != null)
{
Control.Dispose();
}
}
#endregion
}
}
#endif

View File

@ -0,0 +1,186 @@
#if PREVIEW_HANDLER
using ShellExtensions.Interop;
using ShellExtensions.Interop.Common;
using ShellExtensions.Resources;
using System;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
namespace ShellExtensions.PreviewHandlers
{
/// <summary>
/// This is the base class for all WPF-based preview handlers and provides their basic functionality.
/// To create a custom preview handler that contains a WPF user control,
/// a class must derive from this, use the <typeparamref name="PreviewHandlerAttribute"/>,
/// and implement 1 or more of the following interfaces:
/// <typeparamref name="IPreviewFromStream"/>,
/// <typeparamref name="IPreviewFromShellObject"/>,
/// <typeparamref name="IPreviewFromFile"/>.
/// </summary>
public abstract class WpfPreviewHandler : PreviewHandler, IDisposable
{
HwndSource _source = null;
private IntPtr _parentHandle = IntPtr.Zero;
private NativeRect _bounds;
/// <summary>
/// This control must be populated by the deriving class before the preview is shown.
/// </summary>
public UserControl Control { get; protected set; }
/// <summary>
/// Throws an exception if the Control property has not been populated.
/// </summary>
protected void ThrowIfNoControl()
{
if (Control == null)
{
throw new InvalidOperationException(LocalizedMessages.PreviewHandlerControlNotInitialized);
}
}
/// <summary>
/// Updates the placement of the Control.
/// </summary>
protected void UpdatePlacement()
{
if (_source != null)
{
HandlerNativeMethods.SetParent(_source.Handle, _parentHandle);
HandlerNativeMethods.SetWindowPos(_source.Handle, new IntPtr((int)SetWindowPositionInsertAfter.Top),
0, 0, Math.Abs(_bounds.Left - _bounds.Right), Math.Abs(_bounds.Top - _bounds.Bottom), SetWindowPositionOptions.ShowWindow);
}
}
protected override void SetParentHandle(IntPtr handle)
{
_parentHandle = handle;
UpdatePlacement();
}
protected override void Initialize()
{
if (_source == null)
{
ThrowIfNoControl();
HwndSourceParameters p = new HwndSourceParameters
{
WindowStyle = (int)(WindowStyles.Child | WindowStyles.Visible | WindowStyles.ClipSiblings),
ParentWindow = _parentHandle,
Width = Math.Abs(_bounds.Left - _bounds.Right),
Height = Math.Abs(_bounds.Top - _bounds.Bottom)
};
_source = new HwndSource(p);
_source.CompositionTarget.BackgroundColor = Brushes.WhiteSmoke.Color;
_source.RootVisual = (Visual)Control.Content;
}
UpdatePlacement();
}
protected override IntPtr Handle
{
get
{
{
if (_source == null)
{
throw new InvalidOperationException(LocalizedMessages.WpfPreviewHandlerNoHandle);
}
return _source.Handle;
}
}
}
protected override void UpdateBounds(NativeRect bounds)
{
_bounds = bounds;
UpdatePlacement();
}
protected override void HandleInitializeException(Exception caughtException)
{
if (caughtException == null) { return; }
TextBox text = new TextBox
{
IsReadOnly = true,
MaxLines = 20,
Text = caughtException.ToString()
};
Control = new UserControl() { Content = text };
}
protected override void SetFocus()
{
Control.Focus();
}
protected override void SetBackground(int argb)
{
Control.Background = new SolidColorBrush(Color.FromArgb(
(byte)((argb >> 24) & 0xFF), //a
(byte)((argb >> 16) & 0xFF), //r
(byte)((argb >> 8) & 0xFF), //g
(byte)(argb & 0xFF))); //b
}
protected override void SetForeground(int argb)
{
Control.Foreground = new SolidColorBrush(Color.FromArgb(
(byte)((argb >> 24) & 0xFF), //a
(byte)((argb >> 16) & 0xFF), //r
(byte)((argb >> 8) & 0xFF), //g
(byte)(argb & 0xFF))); //b
}
protected override void SetFont(LogFont font)
{
if (font == null) { throw new ArgumentNullException(nameof(font)); }
Control.FontFamily = new FontFamily(font.FaceName);
Control.FontSize = font.Height;
Control.FontWeight = font.Weight > 0 && font.Weight < 1000 ?
System.Windows.FontWeight.FromOpenTypeWeight(font.Weight) :
System.Windows.FontWeights.Normal;
}
#region IDisposable Members
/// <summary>
/// Preview handler control finalizer
/// </summary>
~WpfPreviewHandler()
{
Dispose(false);
}
/// <summary>
/// Disposes the control
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Provides means to dispose the object.
/// When overriden, it is imperative that base.Dispose(true) is called within the implementation.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposing && _source != null)
{
_source.Dispose();
}
}
#endregion
}
}
#endif