//Copyright (c) Microsoft Corporation. All rights reserved.
using System;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Microsoft.WindowsAPICodePack.Shell;
using MS.WindowsAPICodePack.Internal;
namespace Microsoft.WindowsAPICodePack.Taskbar
{
/// <summary>
/// Represents a tabbed thumbnail on the taskbar for a given window or a control.
/// </summary>
public class TabbedThumbnail : IDisposable
{
#region Internal members
internal IntPtr WindowHandle
{
get;
set;
}
internal IntPtr ParentWindowHandle
{
get;
set;
}
internal UIElement WindowsControl
{
get;
set;
}
internal Window WindowsControlParentWindow
{
get;
set;
}
private TaskbarWindow taskbarWindow;
internal TaskbarWindow TaskbarWindow
{
get { return taskbarWindow; }
set
{
taskbarWindow = value;
// If we have a TaskbarWindow assigned, set it's icon
if (taskbarWindow != null && taskbarWindow.TabbedThumbnailProxyWindow != null)
TaskbarWindow.TabbedThumbnailProxyWindow.Icon = Icon;
}
}
private bool addedToTaskbar;
internal bool AddedToTaskbar
{
get
{
return addedToTaskbar;
}
set
{
addedToTaskbar = value;
// The user has updated the clipping region, so invalidate our existing preview
if (TaskbarWindowManager.Instance != null && ClippingRectangle != null)
TaskbarWindowManager.Instance.InvalidatePreview(this.TaskbarWindow);
}
}
internal bool RemovedFromTaskbar
{
get;
set;
}
#endregion
#region Constructors
/// <summary>
/// Creates a new TabbedThumbnail with the given window handle of the parent and
/// a child control/window's handle (e.g. TabPage or Panel)
/// </summary>
/// <param name="parentWindowHandle">Window handle of the parent window.
/// This window has to be a top-level window and the handle cannot be null or IntPtr.Zero</param>
/// <param name="windowHandle">Window handle of the child control or window for which a tabbed
/// thumbnail needs to be displayed</param>
public TabbedThumbnail(IntPtr parentWindowHandle, IntPtr windowHandle)
{
if (parentWindowHandle == IntPtr.Zero)
throw new ArgumentException("Parent window handle cannot be zero.", "parentWindowHandle");
if (windowHandle == IntPtr.Zero)
throw new ArgumentException("Child control's window handle cannot be zero.", "windowHandle");
WindowHandle = windowHandle;
ParentWindowHandle = parentWindowHandle;
}
/// <summary>
/// Creates a new TabbedThumbnail with the given window handle of the parent and
/// a child control (e.g. TabPage or Panel)
/// </summary>
/// <param name="parentWindowHandle">Window handle of the parent window.
/// This window has to be a top-level window and the handle cannot be null or IntPtr.Zero</param>
/// <param name="control">Child control for which a tabbed thumbnail needs to be displayed</param>
/// <remarks>This method can also be called when using a WindowsFormHost control in a WPF application.
/// Call this method with the main WPF Window's handle, and windowsFormHost.Child control.</remarks>
public TabbedThumbnail(IntPtr parentWindowHandle, Control control)
{
if (parentWindowHandle == IntPtr.Zero)
throw new ArgumentException("Parent window handle cannot be zero.", "parentWindowHandle");
if (control == null)
throw new ArgumentNullException("control");
WindowHandle = control.Handle;
ParentWindowHandle = parentWindowHandle;
}
/// <summary>
/// Creates a new TabbedThumbnail with the given window handle of the parent and
/// a WPF child Window. For WindowsFormHost control, use TabbedThumbnail(IntPtr, Control) overload and pass
/// the WindowsFormHost.Child as the second parameter.
/// </summary>
/// <param name="parentWindow">Parent window for the UIElement control.
/// This window has to be a top-level window and the handle cannot be null</param>
/// <param name="windowsControl">WPF Control (UIElement) for which a tabbed thumbnail needs to be displayed</param>
/// <param name="peekOffset">Offset point used for displaying the peek bitmap. This setting is
/// recomended for hidden WPF controls as it is difficult to calculate their offset.</param>
public TabbedThumbnail(Window parentWindow, UIElement windowsControl, Vector peekOffset)
{
if (windowsControl == null)
throw new ArgumentNullException("control");
if (parentWindow == null)
throw new ArgumentNullException("parentWindow");
WindowHandle = IntPtr.Zero;
WindowsControl = windowsControl;
WindowsControlParentWindow = parentWindow;
ParentWindowHandle = (new WindowInteropHelper(parentWindow)).Handle;
PeekOffset = peekOffset;
}
#endregion
#region Public Properties
/// <summary>
/// This event is raised when the Title property changes.
/// </summary>
public event EventHandler TitleChanged;
/// <summary>
/// This event is raised when the Tooltip property changes.
/// </summary>
public event EventHandler TooltipChanged;
private string title;
/// <summary>
/// Title for the window shown as the taskbar thumbnail.
/// </summary>
public string Title
{
get
{
return title;
}
set
{
if (value != title)
{
title = value;
if (TitleChanged != null)
TitleChanged(this, EventArgs.Empty);
}
}
}
private string tooltip;
/// <summary>
/// Tooltip to be shown for this thumbnail on the taskbar.
/// By default this is full title of the window shown on the taskbar.
/// </summary>
public string Tooltip
{
get { return tooltip; }
set
{
if (value != tooltip)
{
tooltip = value;
if (TooltipChanged != null)
TooltipChanged(this, EventArgs.Empty);
}
}
}
internal Icon Icon
{
get;
private set;
}
/// <summary>
/// Sets the window icon for this thumbnail preview
/// </summary>
/// <param name="icon">System.Drawing.Icon for the window/control associated with this preview</param>
public void SetWindowIcon(Icon icon)
{
Icon = icon;
// If we have a TaskbarWindow assigned, set its icon
if (TaskbarWindow != null && TaskbarWindow.TabbedThumbnailProxyWindow != null)
TaskbarWindow.TabbedThumbnailProxyWindow.Icon = Icon;
}
/// <summary>
/// Sets the window icon for this thumbnail preview
/// </summary>
/// <param name="hIcon">Icon handle (hIcon) for the window/control associated with this preview</param>
/// <remarks>This method will not release the icon handle. It is the caller's responsibility to release the icon handle.</remarks>
public void SetWindowIcon(IntPtr hIcon)
{
if (hIcon != IntPtr.Zero)
Icon = System.Drawing.Icon.FromHandle(hIcon);
else
Icon = null;
// If we have a TaskbarWindow assigned, set it's icon
if (TaskbarWindow != null && TaskbarWindow.TabbedThumbnailProxyWindow != null)
TaskbarWindow.TabbedThumbnailProxyWindow.Icon = Icon;
}
private Rectangle? clippingRectangle;
/// <summary>
/// Specifies that only a portion of the window's client area
/// should be used in the window's thumbnail.
/// <para>A value of null will clear the clipping area and use the default thumbnail.</para>
/// </summary>
public Rectangle? ClippingRectangle
{
get { return clippingRectangle; }
set
{
clippingRectangle = value;
// The user has updated the clipping region, so invalidate our existing preview
if (TaskbarWindowManager.Instance != null)
TaskbarWindowManager.Instance.InvalidatePreview(this.TaskbarWindow);
}
}
internal IntPtr CurrentHBitmap
{
get;
set;
}
/// <summary>
/// Override the thumbnail and peek bitmap.
/// By providing this bitmap manually, Thumbnail Window manager will provide the
/// Desktop Window Manager (DWM) this bitmap instead of rendering one automatically.
/// Use this property to update the bitmap whenever the control is updated and the user
/// needs to be shown a new thumbnail on the taskbar preview (or aero peek).
/// </summary>
/// <param name="bitmap">The image to use.</param>
/// <remarks>
/// If the bitmap doesn't have the right dimensions, the DWM may scale it or not
/// render certain areas as appropriate - it is the user's responsibility
/// to render a bitmap with the proper dimensions.
/// </remarks>
public void SetImage(Bitmap bitmap)
{
if (bitmap != null)
{
SetImage(bitmap.GetHbitmap());
}
else
{
SetImage(IntPtr.Zero);
}
}
/// <summary>
/// Override the thumbnail and peek bitmap.
/// By providing this bitmap manually, Thumbnail Window manager will provide the
/// Desktop Window Manager (DWM) this bitmap instead of rendering one automatically.
/// Use this property to update the bitmap whenever the control is updated and the user
/// needs to be shown a new thumbnail on the taskbar preview (or aero peek).
/// </summary>
/// <param name="bitmapSource">The image to use.</param>
/// <remarks>
/// If the bitmap doesn't have the right dimensions, the DWM may scale it or not
/// render certain areas as appropriate - it is the user's responsibility
/// to render a bitmap with the proper dimensions.
/// </remarks>
public void SetImage(BitmapSource bitmapSource)
{
if (bitmapSource == null)
{
SetImage(IntPtr.Zero);
return;
}
using (MemoryStream memoryStream = new MemoryStream())
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(memoryStream);
memoryStream.Position = 0;
Bitmap bmp = new Bitmap(memoryStream);
if (bmp != null)
{
try
{
SetImage(bmp.GetHbitmap());
}
finally
{
//Delete the bitmap
bmp.Dispose();
bmp = null;
}
}
}
}
/// <summary>
/// Override the thumbnail and peek bitmap.
/// By providing this bitmap manually, Thumbnail Window manager will provide the
/// Desktop Window Manager (DWM) this bitmap instead of rendering one automatically.
/// Use this property to update the bitmap whenever the control is updated and the user
/// needs to be shown a new thumbnail on the taskbar preview (or aero peek).
/// </summary>
/// <param name="hBitmap">A bitmap handle for the image to use.
/// <para>When the TabbedThumbnail is finalized, this class will delete the provided hBitmap.</para></param>
/// <remarks>
/// If the bitmap doesn't have the right dimensions, the DWM may scale it or not
/// render certain areas as appropriate - it is the user's responsibility
/// to render a bitmap with the proper dimensions.
/// </remarks>
internal void SetImage(IntPtr hBitmap)
{
// Before we set a new bitmap, dispose the old one
if (CurrentHBitmap != IntPtr.Zero)
{
ShellNativeMethods.DeleteObject(CurrentHBitmap);
}
// Set the new bitmap
CurrentHBitmap = hBitmap;
// Let DWM know to invalidate its cached thumbnail/preview and ask us for a new one (i.e. the one
// user just updated)
if (TaskbarWindowManager.Instance != null)
TaskbarWindowManager.Instance.InvalidatePreview(TaskbarWindow);
}
/// <summary>
/// Specifies whether a standard window frame will be displayed
/// around the bitmap. If the bitmap represents a top-level window,
/// you would probably set this flag to <b>true</b>. If the bitmap
/// represents a child window (or a frameless window), you would
/// probably set this flag to <b>false</b>.
/// </summary>
public bool DisplayFrameAroundBitmap
{
get;
set;
}
/// <summary>
/// Invalidate any existing thumbnail preview. Calling this method
/// will force DWM to request a new bitmap next time user previews the thumbnails
/// or requests Aero peek preview.
/// </summary>
public void InvalidatePreview()
{
// invalidate the thumbnail bitmap
if (TaskbarWindowManager.Instance != null)
{
SetImage(IntPtr.Zero);
}
}
/// <summary>
/// Gets or sets the offset used for displaying the peek bitmap. This setting is
/// recomended for hidden WPF controls as it is difficult to calculate their offset.
/// </summary>
public Vector? PeekOffset
{
get;
set;
}
#endregion
#region Events
/// <summary>
/// The event that occurs when a tab is closed on the taskbar thumbnail preview.
/// </summary>
public event EventHandler<TabbedThumbnailEventArgs> TabbedThumbnailClosed;
/// <summary>
/// The event that occurs when a tab is maximized via the taskbar thumbnail preview (context menu).
/// </summary>
public event EventHandler<TabbedThumbnailEventArgs> TabbedThumbnailMaximized;
/// <summary>
/// The event that occurs when a tab is minimized via the taskbar thumbnail preview (context menu).
/// </summary>
public event EventHandler<TabbedThumbnailEventArgs> TabbedThumbnailMinimized;
/// <summary>
/// The event that occurs when a tab is activated (clicked) on the taskbar thumbnail preview.
/// </summary>
public event EventHandler<TabbedThumbnailEventArgs> TabbedThumbnailActivated;
/// <summary>
/// The event that occurs when a thumbnail or peek bitmap is requested by the user.
/// </summary>
public event EventHandler<TabbedThumbnailBitmapRequestedEventArgs> TabbedThumbnailBitmapRequested;
internal void OnTabbedThumbnailMaximized()
{
if (TabbedThumbnailMaximized != null)
{
TabbedThumbnailMaximized(this, GetTabbedThumbnailEventArgs());
}
else
{
// No one is listening to these events.
// Forward the message to the main window
CoreNativeMethods.SendMessage(ParentWindowHandle, TabbedThumbnailNativeMethods.WM_SYSCOMMAND, new IntPtr(TabbedThumbnailNativeMethods.SC_MAXIMIZE), IntPtr.Zero);
}
}
internal void OnTabbedThumbnailMinimized()
{
if (TabbedThumbnailMinimized != null)
TabbedThumbnailMinimized(this, GetTabbedThumbnailEventArgs());
else
{
// No one is listening to these events.
// Forward the message to the main window
CoreNativeMethods.SendMessage(ParentWindowHandle, TabbedThumbnailNativeMethods.WM_SYSCOMMAND, new IntPtr(TabbedThumbnailNativeMethods.SC_MINIMIZE), IntPtr.Zero);
}
}
internal void OnTabbedThumbnailClosed()
{
if (TabbedThumbnailClosed != null)
TabbedThumbnailClosed(this, GetTabbedThumbnailEventArgs());
else
{
// No one is listening to these events.
// Forward the message to the main window
CoreNativeMethods.SendMessage(ParentWindowHandle, TabbedThumbnailNativeMethods.WM_NCDESTROY, IntPtr.Zero, IntPtr.Zero);
}
// Remove it from the internal list as well as the taskbar
TaskbarManager.Instance.TabbedThumbnail.RemoveThumbnailPreview(this);
}
internal void OnTabbedThumbnailActivated()
{
if (TabbedThumbnailActivated != null)
TabbedThumbnailActivated(this, GetTabbedThumbnailEventArgs());
else
{
// No one is listening to these events.
// Forward the message to the main window
CoreNativeMethods.SendMessage(ParentWindowHandle, TabbedThumbnailNativeMethods.WM_ACTIVATEAPP, new IntPtr(1), new IntPtr(Thread.CurrentThread.GetHashCode()));
}
}
internal void OnTabbedThumbnailBitmapRequested()
{
if (TabbedThumbnailBitmapRequested != null)
{
TabbedThumbnailBitmapRequestedEventArgs eventArgs = null;
if (this.WindowHandle != IntPtr.Zero)
eventArgs = new TabbedThumbnailBitmapRequestedEventArgs(this.WindowHandle, this);
else if (this.WindowsControl != null)
eventArgs = new TabbedThumbnailBitmapRequestedEventArgs(this.WindowsControl, this);
TabbedThumbnailBitmapRequested(this, eventArgs);
}
}
private TabbedThumbnailEventArgs GetTabbedThumbnailEventArgs()
{
TabbedThumbnailEventArgs eventArgs = null;
if (this.WindowHandle != IntPtr.Zero)
eventArgs = new TabbedThumbnailEventArgs(this.WindowHandle, this);
else if (this.WindowsControl != null)
eventArgs = new TabbedThumbnailEventArgs(this.WindowsControl, this);
return eventArgs;
}
#endregion
#region IDisposable Members
/// <summary>
///
/// </summary>
~TabbedThumbnail()
{
Dispose(false);
}
/// <summary>
/// Release the native objects.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Release the native objects.
/// </summary>
/// <param name="disposing"></param>
public void Dispose(bool disposing)
{
if (disposing)
{
taskbarWindow = null;
if (Icon != null)
Icon.Dispose();
Icon = null;
title = null;
tooltip = null;
WindowsControl = null;
}
if (CurrentHBitmap != IntPtr.Zero)
{
ShellNativeMethods.DeleteObject(CurrentHBitmap);
CurrentHBitmap = IntPtr.Zero;
}
}
#endregion
}
}