diff --git a/PhotoThumbnail/PhotoThumbnail.csproj b/PhotoThumbnail/PhotoThumbnail.csproj
index e2e1374..20a8b46 100644
--- a/PhotoThumbnail/PhotoThumbnail.csproj
+++ b/PhotoThumbnail/PhotoThumbnail.csproj
@@ -18,7 +18,7 @@
full
false
bin\Debug\
- DEBUG;TRACE
+ TRACE;DEBUG;THUMBNAIL
prompt
4
true
@@ -27,7 +27,7 @@
pdbonly
true
bin\Release\
- TRACE
+ TRACE;THUMBNAIL
prompt
4
true
@@ -46,7 +46,6 @@
-
diff --git a/ShellExtensions/Interop/Common/ShellNativeEnums.cs b/ShellExtensions/Interop/Common/ShellNativeEnums.cs
new file mode 100644
index 0000000..697c93c
--- /dev/null
+++ b/ShellExtensions/Interop/Common/ShellNativeEnums.cs
@@ -0,0 +1,377 @@
+using System;
+
+namespace ShellExtensions.Interop.Common
+{
+ ///
+ /// The STGM constants are flags that indicate
+ /// conditions for creating and deleting the object and access modes
+ /// for the object.
+ ///
+ /// You can combine these flags, but you can only choose one flag
+ /// from each group of related flags. Typically one flag from each
+ /// of the access and sharing groups must be specified for all
+ /// functions and methods which use these constants.
+ ///
+ [Flags]
+ public enum AccessModes
+ {
+ ///
+ /// Indicates that, in direct mode, each change to a storage
+ /// or stream element is written as it occurs.
+ ///
+ Direct = 0x00000000,
+
+ ///
+ /// Indicates that, in transacted mode, changes are buffered
+ /// and written only if an explicit commit operation is called.
+ ///
+ Transacted = 0x00010000,
+
+ ///
+ /// Provides a faster implementation of a compound file
+ /// in a limited, but frequently used, case.
+ ///
+ Simple = 0x08000000,
+
+ ///
+ /// Indicates that the object is read-only,
+ /// meaning that modifications cannot be made.
+ ///
+ Read = 0x00000000,
+
+ ///
+ /// Enables you to save changes to the object,
+ /// but does not permit access to its data.
+ ///
+ Write = 0x00000001,
+
+ ///
+ /// Enables access and modification of object data.
+ ///
+ ReadWrite = 0x00000002,
+
+ ///
+ /// Specifies that subsequent openings of the object are
+ /// not denied read or write access.
+ ///
+ ShareDenyNone = 0x00000040,
+
+ ///
+ /// Prevents others from subsequently opening the object in Read mode.
+ ///
+ ShareDenyRead = 0x00000030,
+
+ ///
+ /// Prevents others from subsequently opening the object
+ /// for Write or ReadWrite access.
+ ///
+ ShareDenyWrite = 0x00000020,
+
+ ///
+ /// Prevents others from subsequently opening the object in any mode.
+ ///
+ ShareExclusive = 0x00000010,
+
+ ///
+ /// Opens the storage object with exclusive access to the most
+ /// recently committed version.
+ ///
+ Priority = 0x00040000,
+
+ ///
+ /// Indicates that the underlying file is to be automatically destroyed when the root
+ /// storage object is released. This feature is most useful for creating temporary files.
+ ///
+ DeleteOnRelease = 0x04000000,
+
+ ///
+ /// Indicates that, in transacted mode, a temporary scratch file is usually used
+ /// to save modifications until the Commit method is called.
+ /// Specifying NoScratch permits the unused portion of the original file
+ /// to be used as work space instead of creating a new file for that purpose.
+ ///
+ NoScratch = 0x00100000,
+
+ ///
+ /// Indicates that an existing storage object
+ /// or stream should be removed before the new object replaces it.
+ ///
+ Create = 0x00001000,
+
+ ///
+ /// Creates the new object while preserving existing data in a stream named "Contents".
+ ///
+ Convert = 0x00020000,
+
+ ///
+ /// Causes the create operation to fail if an existing object with the specified name exists.
+ ///
+ FailIfThere = 0x00000000,
+
+ ///
+ /// This flag is used when opening a storage object with Transacted
+ /// and without ShareExclusive or ShareDenyWrite.
+ /// In this case, specifying NoSnapshot prevents the system-provided
+ /// implementation from creating a snapshot copy of the file.
+ /// Instead, changes to the file are written to the end of the file.
+ ///
+ NoSnapshot = 0x00200000,
+
+ ///
+ /// Supports direct mode for single-writer, multireader file operations.
+ ///
+ DirectSingleWriterMultipleReader = 0x00400000
+ }
+
+#if PREVIEW_HANDLER
+ ///
+ /// HRESULT Wrapper
+ ///
+ public enum HResult
+ {
+ ///
+ /// S_OK
+ ///
+ Ok = 0x0000,
+
+ ///
+ /// S_FALSE
+ ///
+ False = 0x0001,
+
+ ///
+ /// E_INVALIDARG
+ ///
+ InvalidArguments = unchecked((int)0x80070057),
+
+ ///
+ /// E_OUTOFMEMORY
+ ///
+ OutOfMemory = unchecked((int)0x8007000E),
+
+ ///
+ /// E_NOINTERFACE
+ ///
+ NoInterface = unchecked((int)0x80004002),
+
+ ///
+ /// E_FAIL
+ ///
+ Fail = unchecked((int)0x80004005),
+
+ ///
+ /// E_ELEMENTNOTFOUND
+ ///
+ ElementNotFound = unchecked((int)0x80070490),
+
+ ///
+ /// TYPE_E_ELEMENTNOTFOUND
+ ///
+ TypeElementNotFound = unchecked((int)0x8002802B),
+
+ ///
+ /// NO_OBJECT
+ ///
+ NoObject = unchecked((int)0x800401E5),
+
+ ///
+ /// Win32 Error code: ERROR_CANCELLED
+ ///
+ Win32ErrorCanceled = 1223,
+
+ ///
+ /// ERROR_CANCELLED
+ ///
+ Canceled = unchecked((int)0x800704C7),
+
+ ///
+ /// The requested resource is in use
+ ///
+ ResourceInUse = unchecked((int)0x800700AA),
+
+ ///
+ /// The requested resources is read-only.
+ ///
+ AccessDenied = unchecked((int)0x80030005)
+ }
+
+ [Flags]
+ internal enum WindowStyles
+ {
+ ///
+ /// The window has a thin-line border.
+ ///
+ Border = 0x00800000,
+
+ ///
+ /// The window has a title bar (includes the WS_BORDER style).
+ ///
+ Caption = 0x00C00000,
+
+ ///
+ /// The window is a child window.
+ /// A window with this style cannot have a menu bar.
+ /// This style cannot be used with the WS_POPUP style.
+ ///
+ Child = 0x40000000,
+
+ ///
+ /// Same as the WS_CHILD style.
+ ///
+ ChildWindow = 0x40000000,
+
+ ///
+ /// Excludes the area occupied by child windows when drawing occurs within the parent window.
+ /// This style is used when creating the parent window.
+ ///
+ ClipChildren = 0x02000000,
+
+ ///
+ /// Clips child windows relative to each other;
+ /// that is, when a particular child window receives a WM_PAINT message,
+ /// the WS_CLIPSIBLINGS style clips all other overlapping child windows out of the region of the child window to be updated.
+ /// If WS_CLIPSIBLINGS is not specified and child windows overlap, it is possible,
+ /// when drawing within the client area of a child window, to draw within the client area of a neighboring child window.
+ ///
+ ClipSiblings = 0x04000000,
+
+ ///
+ /// The window is initially disabled. A disabled window cannot receive input from the user.
+ /// To change this after a window has been created, use the EnableWindow function.
+ ///
+ Disabled = 0x08000000,
+
+ ///
+ /// The window has a border of a style typically used with dialog boxes.
+ /// A window with this style cannot have a title bar.
+ ///
+ DialogFrame = 0x0040000,
+
+ ///
+ /// The window is the first control of a group of controls.
+ /// The group consists of this first control and all controls defined after it, up to the next control with the WS_GROUP style.
+ /// The first control in each group usually has the WS_TABSTOP style so that the user can move from group to group.
+ /// The user can subsequently change the keyboard focus from one control in the group to the next control
+ /// in the group by using the direction keys.
+ ///
+ /// You can turn this style on and off to change dialog box navigation.
+ /// To change this style after a window has been created, use the SetWindowLong function.
+ ///
+ Group = 0x00020000,
+
+ ///
+ /// The window has a horizontal scroll bar.
+ ///
+ HorizontalScroll = 0x00100000,
+
+ ///
+ /// The window is initially minimized.
+ /// Same as the WS_MINIMIZE style.
+ ///
+ Iconic = 0x20000000,
+
+ ///
+ /// The window is initially maximized.
+ ///
+ Maximize = 0x01000000,
+
+ ///
+ /// The window has a maximize button.
+ /// Cannot be combined with the WS_EX_CONTEXTHELP style.
+ /// The WS_SYSMENU style must also be specifie
+ ///
+ MaximizeBox = 0x00010000,
+
+ ///
+ /// The window is initially minimized.
+ /// Same as the WS_ICONIC style.
+ ///
+ Minimize = 0x20000000,
+
+ ///
+ /// The window has a minimize button.
+ /// Cannot be combined with the WS_EX_CONTEXTHELP style.
+ /// The WS_SYSMENU style must also be specified.
+ ///
+ MinimizeBox = 0x00020000,
+
+ ///
+ /// The window is an overlapped window.
+ /// An overlapped window has a title bar and a border.
+ /// Same as the WS_TILED style.
+ ///
+ Overlapped = 0x00000000,
+
+ ///
+ /// The windows is a pop-up window.
+ /// This style cannot be used with the WS_CHILD style.
+ ///
+ Popup = unchecked((int)0x80000000),
+
+ ///
+ /// The window has a sizing border.
+ /// Same as the WS_THICKFRAME style.
+ ///
+ SizeBox = 0x00040000,
+
+ ///
+ /// The window has a window menu on its title bar.
+ /// The WS_CAPTION style must also be specified.
+ ///
+ SystemMenu = 0x00080000,
+
+ ///
+ /// The window is a control that can receive the keyboard focus when the user presses the TAB key.
+ /// Pressing the TAB key changes the keyboard focus to the next control with the WS_TABSTOP style.
+ ///
+ /// You can turn this style on and off to change dialog box navigation.
+ /// To change this style after a window has been created, use the SetWindowLong function.
+ /// For user-created windows and modeless dialogs to work with tab stops,
+ /// alter the message loop to call the IsDialogMessage function.
+ ///
+ Tabstop = 0x00010000,
+
+ ///
+ /// The window has a sizing border.
+ /// Same as the WS_SIZEBOX style.
+ ///
+ ThickFrame = 0x00040000,
+
+ ///
+ /// The window is an overlapped window.
+ /// An overlapped window has a title bar and a border.
+ /// Same as the WS_OVERLAPPED style.
+ ///
+ Tiled = 0x00000000,
+
+ ///
+ /// The window is initially visible.
+ ///
+ /// This style can be turned on and off by using the ShowWindow or SetWindowPos function.
+ ///
+ Visible = 0x10000000,
+
+ ///
+ /// The window has a vertical scroll bar.
+ ///
+ VerticalScroll = 0x00200000,
+
+ ///
+ /// The window is an overlapped window.
+ /// Same as the WS_OVERLAPPEDWINDOW style.
+ ///
+ TiledWindowMask = Overlapped | Caption | SystemMenu | ThickFrame | MinimizeBox | MaximizeBox,
+
+ ///
+ /// The window is a pop-up window.
+ /// The WS_CAPTION and WS_POPUPWINDOW styles must be combined to make the window menu visible.
+ ///
+ PopupWindowMask = Popup | Border | SystemMenu,
+
+ ///
+ /// The window is an overlapped window. Same as the WS_TILEDWINDOW style.
+ ///
+ OverlappedWindowMask = Overlapped | Caption | SystemMenu | ThickFrame | MinimizeBox | MaximizeBox,
+ }
+#endif
+}
diff --git a/ShellExtensions/Interop/Common/ShellNativeStructs.cs b/ShellExtensions/Interop/Common/ShellNativeStructs.cs
index 69d4a69..cec460a 100644
--- a/ShellExtensions/Interop/Common/ShellNativeStructs.cs
+++ b/ShellExtensions/Interop/Common/ShellNativeStructs.cs
@@ -1,125 +1,280 @@
-using System;
+#if PREVIEW_HANDLER
+using System;
+using System.Runtime.InteropServices;
namespace ShellExtensions.Interop.Common
{
+
///
- /// The STGM constants are flags that indicate
- /// conditions for creating and deleting the object and access modes
- /// for the object.
- ///
- /// You can combine these flags, but you can only choose one flag
- /// from each group of related flags. Typically one flag from each
- /// of the access and sharing groups must be specified for all
- /// functions and methods which use these constants.
+ /// Wraps the native Windows MSG structure.
///
- [Flags]
- public enum AccessModes
+ public struct Message
{
///
- /// Indicates that, in direct mode, each change to a storage
- /// or stream element is written as it occurs.
+ /// Gets the window handle
///
- Direct = 0x00000000,
+ public IntPtr WindowHandle { get; }
///
- /// Indicates that, in transacted mode, changes are buffered
- /// and written only if an explicit commit operation is called.
+ /// Gets the window message
///
- Transacted = 0x00010000,
+ public uint Msg { get; }
///
- /// Provides a faster implementation of a compound file
- /// in a limited, but frequently used, case.
+ /// Gets the WParam
///
- Simple = 0x08000000,
+ public IntPtr WParam { get; }
///
- /// Indicates that the object is read-only,
- /// meaning that modifications cannot be made.
+ /// Gets the LParam
///
- Read = 0x00000000,
+ public IntPtr LParam { get; }
///
- /// Enables you to save changes to the object,
- /// but does not permit access to its data.
+ /// Gets the time
///
- Write = 0x00000001,
+ public int Time { get; }
///
- /// Enables access and modification of object data.
+ /// Gets the point
///
- ReadWrite = 0x00000002,
+ public NativePoint Point { get; }
///
- /// Specifies that subsequent openings of the object are
- /// not denied read or write access.
+ /// Creates a new instance of the Message struct
///
- ShareDenyNone = 0x00000040,
+ /// Window handle
+ /// Message
+ /// WParam
+ /// LParam
+ /// Time
+ /// Point
+ internal Message(IntPtr windowHandle, uint msg, IntPtr wparam, IntPtr lparam, int time, NativePoint point)
+ : this()
+ {
+ WindowHandle = windowHandle;
+ Msg = msg;
+ WParam = wparam;
+ LParam = lparam;
+ Time = time;
+ Point = point;
+ }
///
- /// Prevents others from subsequently opening the object in Read mode.
+ /// Determines if two messages are equal.
///
- ShareDenyRead = 0x00000030,
+ /// First message
+ /// Second message
+ /// True if first and second message are equal; false otherwise.
+ public static bool operator ==(Message first, Message second)
+ {
+ return first.WindowHandle == second.WindowHandle
+ && first.Msg == second.Msg
+ && first.WParam == second.WParam
+ && first.LParam == second.LParam
+ && first.Time == second.Time
+ && first.Point == second.Point;
+ }
///
- /// Prevents others from subsequently opening the object
- /// for Write or ReadWrite access.
+ /// Determines if two messages are not equal.
///
- ShareDenyWrite = 0x00000020,
+ /// First message
+ /// Second message
+ /// True if first and second message are not equal; false otherwise.
+ public static bool operator !=(Message first, Message second)
+ {
+ return !(first == second);
+ }
///
- /// Prevents others from subsequently opening the object in any mode.
+ /// Determines if this message is equal to another.
///
- ShareExclusive = 0x00000010,
+ /// Another message
+ /// True if this message is equal argument; false otherwise.
+ public override bool Equals(object obj)
+ {
+ return obj != null && obj is Message message && this == message;
+ }
///
- /// Opens the storage object with exclusive access to the most
- /// recently committed version.
+ /// Gets a hash code for the message.
///
- Priority = 0x00040000,
+ /// Hash code for this message.
+ public override int GetHashCode()
+ {
+ int hash = WindowHandle.GetHashCode();
+ hash = hash * 31 + Msg.GetHashCode();
+ hash = hash * 31 + WParam.GetHashCode();
+ hash = hash * 31 + LParam.GetHashCode();
+ hash = hash * 31 + Time.GetHashCode();
+ hash = hash * 31 + Point.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// A wrapper for the native POINT structure.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct NativePoint
+ {
+ ///
+ /// Initialize the NativePoint
+ ///
+ /// The x coordinate of the point.
+ /// The y coordinate of the point.
+ public NativePoint(int x, int y)
+ : this()
+ {
+ X = x;
+ Y = y;
+ }
///
- /// Indicates that the underlying file is to be automatically destroyed when the root
- /// storage object is released. This feature is most useful for creating temporary files.
+ /// The X coordinate of the point
///
- DeleteOnRelease = 0x04000000,
+ public int X { get; set; }
///
- /// Indicates that, in transacted mode, a temporary scratch file is usually used
- /// to save modifications until the Commit method is called.
- /// Specifying NoScratch permits the unused portion of the original file
- /// to be used as work space instead of creating a new file for that purpose.
+ /// The Y coordinate of the point
///
- NoScratch = 0x00100000,
+ public int Y { get; set; }
///
- /// Indicates that an existing storage object
- /// or stream should be removed before the new object replaces it.
+ /// Determines if two NativePoints are equal.
///
- Create = 0x00001000,
+ /// First NativePoint
+ /// Second NativePoint
+ /// True if first NativePoint is equal to the second; false otherwise.
+ public static bool operator ==(NativePoint first, NativePoint second)
+ {
+ return first.X == second.X
+ && first.Y == second.Y;
+ }
///
- /// Creates the new object while preserving existing data in a stream named "Contents".
+ /// Determines if two NativePoints are not equal.
///
- Convert = 0x00020000,
+ /// First NativePoint
+ /// Second NativePoint
+ /// True if first NativePoint is not equal to the second; false otherwise.
+ public static bool operator !=(NativePoint first, NativePoint second)
+ {
+ return !(first == second);
+ }
///
- /// Causes the create operation to fail if an existing object with the specified name exists.
+ /// Determines if this NativePoint is equal to another.
///
- FailIfThere = 0x00000000,
+ /// Another NativePoint to compare
+ /// True if this NativePoint is equal obj; false otherwise.
+ public override bool Equals(object obj)
+ {
+ return obj != null && obj is NativePoint point && this == point;
+ }
///
- /// This flag is used when opening a storage object with Transacted
- /// and without ShareExclusive or ShareDenyWrite.
- /// In this case, specifying NoSnapshot prevents the system-provided
- /// implementation from creating a snapshot copy of the file.
- /// Instead, changes to the file are written to the end of the file.
+ /// Gets a hash code for the NativePoint.
///
- NoSnapshot = 0x00200000,
+ /// Hash code for the NativePoint
+ public override int GetHashCode()
+ {
+ int hash = X.GetHashCode();
+ hash = hash * 31 + Y.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// A wrapper for a RECT struct
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct NativeRect
+ {
+ ///
+ /// Position of left edge
+ ///
+ public int Left { get; set; }
///
- /// Supports direct mode for single-writer, multireader file operations.
+ /// Position of top edge
///
- DirectSingleWriterMultipleReader = 0x00400000
+ public int Top { get; set; }
+
+ ///
+ /// Position of right edge
+ ///
+ public int Right { get; set; }
+
+ ///
+ /// Position of bottom edge
+ ///
+ public int Bottom { get; set; }
+
+ ///
+ /// Creates a new NativeRect initialized with supplied values.
+ ///
+ /// Position of left edge
+ /// Position of top edge
+ /// Position of right edge
+ /// Position of bottom edge
+ public NativeRect(int left, int top, int right, int bottom)
+ : this()
+ {
+ Left = left;
+ Top = top;
+ Right = right;
+ Bottom = bottom;
+ }
+
+ ///
+ /// Determines if two NativeRects are equal.
+ ///
+ /// First NativeRect
+ /// Second NativeRect
+ /// True if first NativeRect is equal to second; false otherwise.
+ public static bool operator ==(NativeRect first, NativeRect second)
+ {
+ return first.Left == second.Left
+ && first.Top == second.Top
+ && first.Right == second.Right
+ && first.Bottom == second.Bottom;
+ }
+
+ ///
+ /// Determines if two NativeRects are not equal
+ ///
+ /// First NativeRect
+ /// Second NativeRect
+ /// True if first is not equal to second; false otherwise.
+ public static bool operator !=(NativeRect first, NativeRect second)
+ {
+ return !(first == second);
+ }
+
+ ///
+ /// Determines if the NativeRect is equal to another Rect.
+ ///
+ /// Another NativeRect to compare
+ /// True if this NativeRect is equal to the one provided; false otherwise.
+ public override bool Equals(object obj)
+ {
+ return obj != null && obj is NativeRect rect && this == rect;
+ }
+
+ ///
+ /// Creates a hash code for the NativeRect
+ ///
+ /// Returns hash code for this NativeRect
+ public override int GetHashCode()
+ {
+ int hash = Left.GetHashCode();
+ hash = hash * 31 + Top.GetHashCode();
+ hash = hash * 31 + Right.GetHashCode();
+ hash = hash * 31 + Bottom.GetHashCode();
+ return hash;
+ }
}
}
+#endif
diff --git a/ShellExtensions/Interop/HandlerNativeMethods.cs b/ShellExtensions/Interop/HandlerNativeMethods.cs
index 0b74c70..937265a 100644
--- a/ShellExtensions/Interop/HandlerNativeMethods.cs
+++ b/ShellExtensions/Interop/HandlerNativeMethods.cs
@@ -7,7 +7,29 @@ namespace ShellExtensions.Interop
{
internal static class HandlerNativeMethods
{
+#if PREVIEW_HANDLER
+ [DllImport("user32.dll")]
+ internal static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
+
+ [DllImport("user32.dll")]
+ internal static extern IntPtr GetFocus();
+
+ [DllImport("user32.dll")]
+ internal static extern void SetWindowPos(
+ IntPtr hWnd,
+ IntPtr hWndInsertAfter,
+ int x,
+ int y,
+ int cx,
+ int cy,
+ SetWindowPositionOptions flags);
+
+ internal static readonly Guid IPreviewHandlerGuid = new Guid("8895b1c6-b41f-4c1c-a562-0d564250836f");
+#endif
+
+#if THUMBNAIL
internal static readonly Guid IThumbnailProviderGuid = new Guid("e357fccd-a995-4576-b01f-234630154e96");
+#endif
internal static readonly Guid IInitializeWithFileGuid = new Guid("b7d14566-0509-4cce-a71f-0a554233bd9b");
internal static readonly Guid IInitializeWithStreamGuid = new Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f");
@@ -16,25 +38,6 @@ namespace ShellExtensions.Interop
internal static readonly Guid IMarshalGuid = new Guid("00000003-0000-0000-C000-000000000046");
}
- #region Interfaces
-
- ///
- /// ComVisible interface for native IThumbnailProvider
- ///
- [ComImport]
- [Guid("e357fccd-a995-4576-b01f-234630154e96")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- interface IThumbnailProvider
- {
- ///
- /// Gets a pointer to a bitmap to display as a thumbnail
- ///
- ///
- ///
- ///
- void GetThumbnail(uint squareLength, [Out] out IntPtr bitmapHandle, [Out] out uint bitmapType);
- }
-
///
/// Provides means by which to initialize with a file.
///
@@ -67,6 +70,26 @@ namespace ShellExtensions.Interop
void Initialize(IStream stream, AccessModes fileMode);
}
+#if THUMBNAIL
+ #region Interfaces
+
+ ///
+ /// ComVisible interface for native IThumbnailProvider
+ ///
+ [ComImport]
+ [Guid("e357fccd-a995-4576-b01f-234630154e96")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ interface IThumbnailProvider
+ {
+ ///
+ /// Gets a pointer to a bitmap to display as a thumbnail
+ ///
+ ///
+ ///
+ ///
+ void GetThumbnail(uint squareLength, [Out] out IntPtr bitmapHandle, [Out] out uint bitmapType);
+ }
+
#endregion
//
@@ -89,4 +112,178 @@ namespace ShellExtensions.Interop
///
HasAlphaChannel = 2,
}
+#endif
+
+#if PREVIEW_HANDLER
+ [ComImport]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")]
+ interface IObjectWithSite
+ {
+ void SetSite([In, MarshalAs(UnmanagedType.IUnknown)] object pUnkSite);
+ void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite);
+ }
+
+ [ComImport]
+ [Guid("00000114-0000-0000-C000-000000000046")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ interface IOleWindow
+ {
+ void GetWindow(out IntPtr phwnd);
+ void ContextSensitiveHelp([MarshalAs(UnmanagedType.Bool)] bool fEnterMode);
+ }
+
+ [ComImport]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [Guid("8895b1c6-b41f-4c1c-a562-0d564250836f")]
+ interface IPreviewHandler
+ {
+ void SetWindow(IntPtr hwnd, ref NativeRect rect);
+ void SetRect(ref NativeRect rect);
+ void DoPreview();
+ void Unload();
+ void SetFocus();
+ void QueryFocus(out IntPtr phwnd);
+ [PreserveSig]
+ HResult TranslateAccelerator(ref Message pmsg);
+ }
+
+ [ComImport]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [Guid("fec87aaf-35f9-447a-adb7-20234491401a")]
+ interface IPreviewHandlerFrame
+ {
+ void GetWindowContext(IntPtr pinfo);
+ [PreserveSig]
+ HResult TranslateAccelerator(ref Message pmsg);
+ }
+
+ [ComImport]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [Guid("8327b13c-b63f-4b24-9b8a-d010dcc3f599")]
+ interface IPreviewHandlerVisuals
+ {
+ void SetBackgroundColor(NativeColorRef color);
+ void SetFont(ref LogFont plf);
+ void SetTextColor(NativeColorRef color);
+ }
+
+ #region Structs
+
+ ///
+ /// Class for marshaling to native LogFont struct
+ ///
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ public class LogFont
+ {
+ ///
+ /// Font height
+ ///
+ public int Height { get; set; }
+
+ ///
+ /// Font width
+ ///
+ public int Width { get; set; }
+
+ ///
+ /// Font escapement
+ ///
+ public int Escapement { get; set; }
+
+ ///
+ /// Font orientation
+ ///
+ public int Orientation { get; set; }
+
+ ///
+ /// Font weight
+ ///
+ public int Weight { get; set; }
+
+ ///
+ /// Font italic
+ ///
+ public byte Italic { get; set; }
+
+ ///
+ /// Font underline
+ ///
+ public byte Underline { get; set; }
+
+ ///
+ /// Font strikeout
+ ///
+ public byte Strikeout { get; set; }
+
+ ///
+ /// Font character set
+ ///
+ public byte CharacterSet { get; set; }
+
+ ///
+ /// Font out precision
+ ///
+ public byte OutPrecision { get; set; }
+
+ ///
+ /// Font clip precision
+ ///
+ public byte ClipPrecision { get; set; }
+
+ ///
+ /// Font quality
+ ///
+ public byte Quality { get; set; }
+
+ ///
+ /// Font pitch and family
+ ///
+ public byte PitchAndFamily { get; set; }
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ private string faceName = string.Empty;
+
+ ///
+ /// Font face name
+ ///
+ public string FaceName { get => faceName; set => faceName = value; }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NativeColorRef
+ {
+ public uint Dword { get; set; }
+ }
+
+ #endregion
+
+ [Flags]
+ internal enum SetWindowPositionOptions
+ {
+ AsyncWindowPos = 0x4000,
+ DeferErase = 0x2000,
+ DrawFrame = FrameChanged,
+ FrameChanged = 0x0020, // The frame changed: send WM_NCCALCSIZE
+ HideWindow = 0x0080,
+ NoActivate = 0x0010,
+ CoCopyBits = 0x0100,
+ NoMove = 0x0002,
+ NoOwnerZOrder = 0x0200, // Don't do owner Z ordering
+ NoRedraw = 0x0008,
+ NoResposition = NoOwnerZOrder,
+ NoSendChanging = 0x0400, // Don't send WM_WINDOWPOSCHANGING
+ NoSize = 0x0001,
+ NoZOrder = 0x0004,
+ ShowWindow = 0x0040
+ }
+
+ internal enum SetWindowPositionInsertAfter
+ {
+ NoTopMost = -2,
+ TopMost = -1,
+ Top = 0,
+ Bottom = 1
+ }
+#endif
}
diff --git a/ShellExtensions/PreviewHandlers/ManagedInitializationInterfaces.cs b/ShellExtensions/PreviewHandlers/ManagedInitializationInterfaces.cs
new file mode 100644
index 0000000..b7b86bc
--- /dev/null
+++ b/ShellExtensions/PreviewHandlers/ManagedInitializationInterfaces.cs
@@ -0,0 +1,42 @@
+#if PREVIEW_HANDLER
+using System.IO;
+
+namespace ShellExtensions
+{
+ ///
+ /// This interface exposes the function for initializing the
+ /// Preview Handler with a .
+ /// 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:
+ ///
+ ///
+ ///
+ ///
+ public interface IPreviewFromStream
+ {
+ ///
+ /// Provides the to the item from which a preview should be created.
+ ///
+ /// Stream to the previewed file, this stream is only available in the scope of this method.
+ void Load(Stream stream);
+ }
+
+ ///
+ /// This interface exposes the function for initializing the
+ /// Preview Handler with a .
+ /// 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:
+ ///
+ ///
+ ///
+ ///
+ public interface IPreviewFromFile
+ {
+ ///
+ /// Provides the to the item from which a preview should be created.
+ ///
+ /// File information to the previewed file.
+ void Load(FileInfo info);
+ }
+}
+#endif
\ No newline at end of file
diff --git a/ShellExtensions/PreviewHandlers/PreviewHandler.cs b/ShellExtensions/PreviewHandlers/PreviewHandler.cs
new file mode 100644
index 0000000..9e87656
--- /dev/null
+++ b/ShellExtensions/PreviewHandlers/PreviewHandler.cs
@@ -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
+{
+ ///
+ /// 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 ,
+ /// and implement 1 or more of the following interfaces:
+ /// ,
+ /// ,
+ /// .
+ ///
+ public abstract class PreviewHandler : ICustomQueryInterface, IPreviewHandler, IPreviewHandlerVisuals, IOleWindow, IObjectWithSite, IInitializeWithStream, IInitializeWithFile
+ {
+ private bool _isPreviewShowing;
+ private IntPtr _parentHwnd;
+ private IPreviewHandlerFrame _frame;
+
+ ///
+ /// Gets whether the preview is currently showing
+ ///
+ public bool IsPreviewShowing => _isPreviewShowing;
+
+ ///
+ /// Called immediately before the preview is to be shown.
+ ///
+ protected virtual void Initialize() { }
+
+ ///
+ /// Called when the preview is no longer shown.
+ ///
+ protected virtual void Uninitialize() { }
+
+#region Required functions - Abstract functions
+
+ ///
+ /// This should return the window handle to be displayed in the Preview.
+ ///
+ protected abstract IntPtr Handle { get; }
+
+ ///
+ /// Called to update the bounds and position of the preview control
+ ///
+ ///
+ protected abstract void UpdateBounds(NativeRect bounds);
+
+ ///
+ /// Called when an exception occurs during the initialization of the control
+ ///
+ ///
+ protected abstract void HandleInitializeException(Exception caughtException);
+
+ ///
+ /// Called when the preview control obtains focus.
+ ///
+ protected abstract void SetFocus();
+
+ ///
+ /// Called when a request is received to set or change the background color according to the user's preferences.
+ ///
+ /// An int representing the ARGB color
+ protected abstract void SetBackground(int argb);
+
+ ///
+ /// Called when a request is received to set or change the foreground color according to the user's preferences.
+ ///
+ /// An int representing the ARGB color
+ protected abstract void SetForeground(int argb);
+
+ ///
+ /// Called to set the font of the preview control according to the user's preferences.
+ ///
+ ///
+ protected abstract void SetFont(LogFont font);
+
+ ///
+ /// Called to set the parent of the preview control.
+ ///
+ ///
+ 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
+
+ ///
+ /// Called when the assembly is registered via RegAsm.
+ ///
+ /// Type to register.
+ [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));
+ }
+ }
+ }
+
+ ///
+ /// Called when the assembly is Unregistered via RegAsm.
+ ///
+ /// Type to unregister
+ [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
\ No newline at end of file
diff --git a/ShellExtensions/PreviewHandlers/PreviewHandlerAttribute.cs b/ShellExtensions/PreviewHandlers/PreviewHandlerAttribute.cs
new file mode 100644
index 0000000..e01dacf
--- /dev/null
+++ b/ShellExtensions/PreviewHandlers/PreviewHandlerAttribute.cs
@@ -0,0 +1,48 @@
+#if PREVIEW_HANDLER
+using System;
+
+namespace ShellExtensions
+{
+ ///
+ /// This class attribute is applied to a Preview Handler to specify registration parameters.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+ public sealed class PreviewHandlerAttribute : Attribute
+ {
+ ///
+ /// Creates a new instance of the attribute.
+ ///
+ /// Name of the Handler
+ /// Semi-colon-separated list of file extensions supported by the handler.
+ /// A unique guid used for process isolation.
+ 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;
+ }
+
+ ///
+ /// Gets the name of the handler.
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// Gets the semi-colon-separated list of extensions supported by the preview handler.
+ ///
+ public string Extensions { get; private set; }
+
+ ///
+ /// Gets the AppId associated with the handler for use with the surrogate host process.
+ ///
+ public string AppId { get; private set; }
+
+ ///
+ /// Disables low integrity-level process isolation.
+ /// This should be avoided as it could be a security risk.
+ ///
+ public bool DisableLowILProcessIsolation { get; set; }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/ShellExtensions/PreviewHandlers/WinFormsPreviewHandler.cs b/ShellExtensions/PreviewHandlers/WinFormsPreviewHandler.cs
new file mode 100644
index 0000000..ee1627c
--- /dev/null
+++ b/ShellExtensions/PreviewHandlers/WinFormsPreviewHandler.cs
@@ -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
+{
+ ///
+ /// 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 ,
+ /// and implement 1 or more of the following interfaces:
+ /// ,
+ /// ,
+ /// .
+ ///
+ public abstract class WinFormsPreviewHandler : PreviewHandler, IDisposable
+ {
+ ///
+ /// This control must be populated by the deriving class before the preview is shown.
+ ///
+ public UserControl Control { get; protected set; }
+
+ protected void ThrowIfNoControl()
+ {
+ if (Control == null)
+ {
+ throw new InvalidOperationException(LocalizedMessages.PreviewHandlerControlNotInitialized);
+ }
+ }
+
+ ///
+ /// Called when an exception is thrown during itialization of the preview control.
+ ///
+ ///
+ 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
\ No newline at end of file
diff --git a/ShellExtensions/PreviewHandlers/WpfPreviewHandler.cs b/ShellExtensions/PreviewHandlers/WpfPreviewHandler.cs
new file mode 100644
index 0000000..dd0aeb1
--- /dev/null
+++ b/ShellExtensions/PreviewHandlers/WpfPreviewHandler.cs
@@ -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
+{
+ ///
+ /// 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 ,
+ /// and implement 1 or more of the following interfaces:
+ /// ,
+ /// ,
+ /// .
+ ///
+ public abstract class WpfPreviewHandler : PreviewHandler, IDisposable
+ {
+ HwndSource _source = null;
+ private IntPtr _parentHandle = IntPtr.Zero;
+ private NativeRect _bounds;
+
+ ///
+ /// This control must be populated by the deriving class before the preview is shown.
+ ///
+ public UserControl Control { get; protected set; }
+
+ ///
+ /// Throws an exception if the Control property has not been populated.
+ ///
+ protected void ThrowIfNoControl()
+ {
+ if (Control == null)
+ {
+ throw new InvalidOperationException(LocalizedMessages.PreviewHandlerControlNotInitialized);
+ }
+ }
+
+ ///
+ /// Updates the placement of the Control.
+ ///
+ 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
+
+ ///
+ /// Preview handler control finalizer
+ ///
+ ~WpfPreviewHandler()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Disposes the control
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Provides means to dispose the object.
+ /// When overriden, it is imperative that base.Dispose(true) is called within the implementation.
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && _source != null)
+ {
+ _source.Dispose();
+ }
+ }
+
+#endregion
+ }
+}
+#endif
\ No newline at end of file
diff --git a/ShellExtensions/Resources/LocalizedMessages.cs b/ShellExtensions/Resources/LocalizedMessages.cs
index 1d54508..05f1458 100644
--- a/ShellExtensions/Resources/LocalizedMessages.cs
+++ b/ShellExtensions/Resources/LocalizedMessages.cs
@@ -2,12 +2,22 @@
{
internal sealed class LocalizedMessages
{
+#if PREVIEW_HANDLER
+ public const string PreviewHandlerControlNotInitialized = "Control has not yet been assigned. Methods requiring it cannot be called.";
+ public const string PreviewHandlerInterfaceNotImplemented = "{0} must implement one or more of IPreviewFromStream, IPreviewFromShellObject or IPreviewFromFile.";
+ public const string PreviewHandlerInvalidAttributes = "PreviewHandler '{0}' must have exactly one PreviewHandler attribute.";
+ public const string PreviewHandlerUnsupportedInterfaceCalled = "Unable to call interface {0} because it is not supported on this object.";
+ public const string WpfPreviewHandlerNoHandle = "Cannot retrieve handle because proxy window has not been created.";
+#endif
+
public const string StorageStreamBufferOverflow = "The sum of offset and count must be less than or equal to the size of the buffer.";
public const string StorageStreamCountLessThanZero = "Count must be greater than or equal to zero.";
public const string StorageStreamIsReadonly = "The stream was initialized as read-only.";
public const string StorageStreamOffsetLessThanZero = "Offset must be greater than or equal to zero.";
+#if THUMBNAIL
public const string ThumbnailProviderDisabledProcessIsolation = "{0} does not implement IThumbnailFromStream and so requires DisableProcessIsolation set to true.";
public const string ThumbnailProviderInterfaceNotImplemented = "{0} must implement one or more of IThumbnailFromStream, IThumbnailFromShellObject or IThumbnailFromFile.";
+#endif
}
}
diff --git a/ShellExtensions/ShellExtensions.projitems b/ShellExtensions/ShellExtensions.projitems
index a95c407..cba7531 100644
--- a/ShellExtensions/ShellExtensions.projitems
+++ b/ShellExtensions/ShellExtensions.projitems
@@ -9,8 +9,14 @@
ShellExtensions
-
+
+
+
+
+
+
+
diff --git a/ShellExtensions/ThumbnailProviders/ManagedInitializationInterfaces.cs b/ShellExtensions/ThumbnailProviders/ManagedInitializationInterfaces.cs
index 1b53d86..2f4b6bb 100644
--- a/ShellExtensions/ThumbnailProviders/ManagedInitializationInterfaces.cs
+++ b/ShellExtensions/ThumbnailProviders/ManagedInitializationInterfaces.cs
@@ -1,4 +1,5 @@
-using System.Drawing;
+#if THUMBNAIL
+using System.Drawing;
using System.IO;
namespace ShellExtensions
@@ -52,3 +53,4 @@ namespace ShellExtensions
Bitmap ConstructBitmap(FileInfo info, int sideSize);
}
}
+#endif
\ No newline at end of file
diff --git a/ShellExtensions/ThumbnailProviders/ThumbnailProvider.cs b/ShellExtensions/ThumbnailProviders/ThumbnailProvider.cs
index 1cac4df..b97e3db 100644
--- a/ShellExtensions/ThumbnailProviders/ThumbnailProvider.cs
+++ b/ShellExtensions/ThumbnailProviders/ThumbnailProvider.cs
@@ -1,4 +1,5 @@
-using Microsoft.Win32;
+#if THUMBNAIL
+using Microsoft.Win32;
using ShellExtensions.Interop;
using ShellExtensions.Interop.Common;
using ShellExtensions.Resources;
@@ -280,3 +281,4 @@ namespace ShellExtensions
#endregion
}
}
+#endif
\ No newline at end of file
diff --git a/ShellExtensions/ThumbnailProviders/ThumbnailProviderAttribute.cs b/ShellExtensions/ThumbnailProviders/ThumbnailProviderAttribute.cs
index 4727a46..38a32f3 100644
--- a/ShellExtensions/ThumbnailProviders/ThumbnailProviderAttribute.cs
+++ b/ShellExtensions/ThumbnailProviders/ThumbnailProviderAttribute.cs
@@ -1,8 +1,9 @@
-using System;
+#if THUMBNAIL
+using System;
namespace ShellExtensions
{
- ///
+ ///
/// This class attribute is applied to a Thumbnail Provider to specify registration parameters
/// and aesthetic attributes.
///
@@ -134,3 +135,4 @@ namespace ShellExtensions
VideoSprockets = 3
}
}
+#endif
\ No newline at end of file