using System.IO;
using System.Reflection;
using Microsoft.WindowsAPICodePack.Shell;
namespace Clipz
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Microsoft.WindowsAPICodePack.Taskbar;
using NativeMethods;
public partial class Form1 : Form
{
public const string ModeCommandArgument = "-1";
public const string ClearCommandArgument = "-2";
private readonly ClipboardManager _clipboardManager = new ClipboardManager();
private readonly Dictionary<TabbedThumbnail, Control> _thumbnails = new Dictionary<TabbedThumbnail, Control>();
private IntPtr _clipboardObserverHandle;
private bool _loaded;
private bool _copying;
private RenderMode _mode = RenderMode.Auto;
private JumpList _jumpList;
private ThumbnailToolbarButton _previous;
private ThumbnailToolbarButton _next;
private ThumbnailToolbarButton _delete;
private ThumbnailToolbarButton _copy;
private enum RenderMode
{
Paged,
Auto
}
/// <summary>
/// Initializes a new instance of the <see cref="Form1"/> class.
/// </summary>
public Form1()
{
InitializeComponent();
_clipboardManager.ItemAdded += _clipboardManager_ItemAdded;
_clipboardManager.ItemRemoved += _clipboardManager_ItemRemoved;
_clipboardManager.ItemsCleared += _clipboardManager_ItemsCleared;
_clipboardManager.SelectedIndexChanged += _clipboardManager_SelectedIndexChanged;
PreviewImageBox.Image = Resources.Resources.Clipboard;
Icon = Resources.Resources.CopyIcon;
_trayIcon.Visible = true;
_trayIcon.Icon = Resources.Resources.CopyIcon;
_trayIcon.ShowBalloonTip(2000, "Clipz", Resources.Resources.BalloonTip, ToolTipIcon.Info);
// Comment these 4 lines out to see the UI. This is done to keep the window active/visible
// but off the screen. Minimized or hidden areas of the screen prevent the thumbnail images
// from repainting.
//Left = int.MaxValue;
//Top = int.MaxValue;
//Height = Screen.PrimaryScreen.Bounds.Height;
//Width = Screen.PrimaryScreen.Bounds.Width;
}
/// <summary>
/// Changes the mode taskbar display mode.
/// </summary>
public void ChangeMode()
{
if (_mode == RenderMode.Paged)
{
//Set the thumbnail clip to the parent window
TaskbarManager.Instance.TabbedThumbnail.SetThumbnailClip(Handle, ClientRectangle);
// Add a thumbnail preview for each clipboard item
foreach (var key in _thumbnails.Keys)
{
if (!TaskbarManager.Instance.TabbedThumbnail.IsThumbnailPreviewAdded(key))
{
TaskbarManager.Instance.TabbedThumbnail.AddThumbnailPreview(key);
}
}
_mode = RenderMode.Auto;
}
else
{
SetEmptyPreview();
_thumbnails.Keys.ToList().ForEach(t => TaskbarManager.Instance.TabbedThumbnail.RemoveThumbnailPreview(t));
_mode = RenderMode.Paged;
}
usePagedPreviewToolStripMenuItem.Text = _mode == RenderMode.Auto
? Resources.Resources.PagedPreviewText
: Resources.Resources.AutoPreviewText;
CreateJumplist();
}
/// <summary>
/// Clears the clipboard item contents.
/// </summary>
public void ClearContents()
{
var itemCount = FlowPanel.Controls.Count;
for (int i = 0; i < itemCount; i++ )
{
_clipboardManager.Remove(0);
}
}
/// <summary>
/// Overrides WndProc to intercept Windows messages, specifically the draw clipboard and
/// change chain messages. These are the two messages necessary for capturing and
/// handling clipboard changes. Decorated with DebuggerNonUserCode for easier
/// debugging due to multithreading.
/// </summary>
/// <param name="message">The Windows message.</param>
[DebuggerNonUserCode]
protected override void WndProc(ref Message message)
{
if (!_loaded || _copying) { base.WndProc(ref message); }
// Check whether a command line argument was sent via another
// attempted instance of the application.
if (message.Msg == User32.ChangeModeMessage)
{
ChangeMode();
return;
}
if (message.Msg == User32.ClearContents)
{
ClearContents();
return;
}
// Monitor clipboard changes.
switch ((WindowsMessages)message.Msg)
{
case WindowsMessages.WM_DRAWCLIPBOARD:
UpdateClipboard();
User32.SendMessage(_clipboardObserverHandle, message.Msg, message.WParam, message.LParam);
break;
case WindowsMessages.WM_CHANGECBCHAIN:
if (message.WParam == _clipboardObserverHandle)
{
_clipboardObserverHandle = message.LParam;
}
else
{
User32.SendMessage(_clipboardObserverHandle, message.Msg, message.WParam, message.LParam);
}
break;
default:
base.WndProc(ref message);
break;
}
}
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Form.Load"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnLoad(EventArgs e)
{
// Subscribe to Windows clipboard notifications
_clipboardObserverHandle = User32.SetClipboardViewer(Handle);
_previous = new ThumbnailToolbarButton(Resources.Resources.PreviousIcon, Resources.Resources.PreviousTooltip);
_next = new ThumbnailToolbarButton(Resources.Resources.NextIcon, Resources.Resources.NextTooltip);
_delete = new ThumbnailToolbarButton(Resources.Resources.DeleteIcon, Resources.Resources.DeleteTooltip);
_copy = new ThumbnailToolbarButton(Resources.Resources.CopyIcon, Resources.Resources.CopyTooltip);
TaskbarManager.Instance.ThumbnailToolbars.AddButtons(Handle, new[] { _previous, _delete, _copy, _next });
_previous.Enabled = false;
_next.Enabled = false;
_delete.Enabled = false;
_copy.Enabled = false;
_previous.Click += _previous_Click;
_next.Click += _next_Click;
_delete.Click += _delete_Click;
_copy.Click += _copy_Click;
_loaded = true;
base.OnLoad(e);
}
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Form.Closing"/> event.
/// </summary>
/// <param name="e">A <see cref="T:System.ComponentModel.CancelEventArgs"/> that contains the event data.</param>
protected override void OnClosing(CancelEventArgs e)
{
_trayIcon.Dispose();
User32.ChangeClipboardChain(Handle, _clipboardObserverHandle);
base.OnClosing(e);
}
/// <summary>
/// Change the preview window to the selected clipboard item.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Clipz.ClipboardItemIndexEventArgs"/> instance containing the event data.</param>
private void _clipboardManager_SelectedIndexChanged(object sender, ClipboardItemIndexEventArgs e)
{
SetPreview(e.CurrentItemIndex, e.CanMoveNext, e.CanMovePrevious);
}
/// <summary>
/// Adds a new picturebox to the flow panel and set the preview window or thumbnail
/// preview image to the new item's thumbnail bitmap.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Clipz.ClipboardItemAddedEventArgs"/> instance containing the event data.</param>
private void _clipboardManager_ItemAdded(object sender, ClipboardItemAddedEventArgs e)
{
var image = new PictureBox
{
Size = new Size(150, 150),
Image = e.Thumbnail
};
FlowPanel.Controls.Add(image);
// Subscribe to the close and activate events
var thumbnail = new TabbedThumbnail(Handle, image);
thumbnail.SetImage(e.Thumbnail);
thumbnail.TabbedThumbnailClosed += thumbnail_TabbedThumbnailClosed;
thumbnail.TabbedThumbnailActivated += thumbnail_TabbedThumbnailActivated;
_thumbnails.Add(thumbnail, image);
if (_mode == RenderMode.Auto)
{
TaskbarManager.Instance.TabbedThumbnail.AddThumbnailPreview(thumbnail);
}
else
{
SetPreview(e.CurrentItemIndex, e.CanMoveNext, e.CanMovePrevious);
}
}
/// <summary>
/// Resets to the placeholder image when no items are available.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Clipz.ClipboardItemAddedEventArgs"/> instance containing the event data.</param>
private void _clipboardManager_ItemsCleared(object sender, EventArgs e)
{
PreviewImageBox.Image = Resources.Resources.Clipboard;
_delete.Enabled = false;
_copy.Enabled = false;
}
/// <summary>
/// Remove the clipboard item.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Clipz.ClipboardItemRemovedEventArgs"/> instance containing the event data.</param>
private void _clipboardManager_ItemRemoved(object sender, ClipboardItemRemovedEventArgs e)
{
FlowPanel.Controls.RemoveAt(e.RemovedItemIndex);
_thumbnails.Remove(_thumbnails.Skip(e.RemovedItemIndex).First().Key);
}
/// <summary>
/// Cycles to the next preview image in scrolling mode.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.WindowsAPICodePack.Taskbar.ThumbnailButtonClickedEventArgs"/> instance containing the event data.</param>
private void _next_Click(object sender, ThumbnailButtonClickedEventArgs e)
{
_clipboardManager.Next();
}
/// <summary>
/// Cycles to the previous preview image in scrolling mode.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.WindowsAPICodePack.Taskbar.ThumbnailButtonClickedEventArgs"/> instance containing the event data.</param>
private void _previous_Click(object sender, ThumbnailButtonClickedEventArgs e)
{
_clipboardManager.Previous();
}
/// <summary>
/// Copy the current item back to the Windows clipboard.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.WindowsAPICodePack.Taskbar.ThumbnailButtonClickedEventArgs"/> instance containing the event data.</param>
private void _copy_Click(object sender, ThumbnailButtonClickedEventArgs e)
{
_copying = true;
_clipboardManager.Copy();
_copying = false;
}
/// <summary>
/// Remove the current clipboard item (scrolling mode).
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.WindowsAPICodePack.Taskbar.ThumbnailButtonClickedEventArgs"/> instance containing the event data.</param>
private void _delete_Click(object sender, ThumbnailButtonClickedEventArgs e)
{
_clipboardManager.RemoveCurrent();
}
/// <summary>
/// Sets the preview picture box image to the image that will be displayed in
/// the Windows taskbar preview.
/// </summary>
/// <param name="itemIndex">Index of the item.</param>
/// <param name="nextEnabled">if set to <c>true</c> next is enabled.</param>
/// <param name="prevEnabled">if set to <c>true</c> previous is enabled.</param>
private void SetPreview(int itemIndex, bool nextEnabled, bool prevEnabled)
{
try
{
PreviewImageBox.Image = ((PictureBox) FlowPanel.Controls[itemIndex]).Image;
_next.Enabled = nextEnabled;
_previous.Enabled = prevEnabled;
_delete.Enabled = true;
_copy.Enabled = true;
}
catch(ArgumentOutOfRangeException)
{
}
}
/// <summary>
/// A thumbnail preview tab was clicked. Copy that item's data to the clipboard.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.WindowsAPICodePack.Taskbar.TabbedThumbnailEventArgs"/> instance containing the event data.</param>
private void thumbnail_TabbedThumbnailActivated(object sender, TabbedThumbnailEventArgs e)
{
var index = FlowPanel.Controls.IndexOf(_thumbnails[e.TabbedThumbnail]);
Copy(index);
}
/// <summary>
/// A thumbnail preview tab was closed. Remove that item from memory.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.WindowsAPICodePack.Taskbar.TabbedThumbnailEventArgs"/> instance containing the event data.</param>
private void thumbnail_TabbedThumbnailClosed(object sender, TabbedThumbnailEventArgs e)
{
var control = _thumbnails[e.TabbedThumbnail];
var index = FlowPanel.Controls.IndexOf(control);
_clipboardManager.Remove(index);
}
/// <summary>
/// Tells the clipboard manager that new clipboard contents are available.
/// </summary>
private void UpdateClipboard()
{
// Don't enter until the window is loaded and activated.
if (!_loaded)
{
return;
}
lock (this)
{
_clipboardManager.UpdateClipboard();
}
}
/// <summary>
/// Copies the selected item's original contents back into the Windows clipboard.
/// </summary>
/// <param name="index">The index.</param>
private void Copy(int index)
{
_copying = true;
_clipboardManager.Copy(index);
_copying = false;
}
/// <summary>
/// Sets the preview picture box to the placeholder image.
/// </summary>
private void SetEmptyPreview()
{
// Set the application's taskbar preview to render only the area bounded by the
// preview picture box. This is how other applications render content like video
// clips or browser tabs without rendering the rest of the application's window.
var pictureboxBounds = new Rectangle(630, 0, 150, 150);
TaskbarManager.Instance.TabbedThumbnail.SetThumbnailClip(Handle, pictureboxBounds);
}
/// <summary>
/// Handles the Shown event of the Form1 control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void Form1_Shown(object sender, EventArgs e)
{
// HACK: force the screen to redraw so the preview image is available
// for thumbnail rendering when the form loads.
Height += 1;
SetEmptyPreview();
ChangeMode();
}
/// <summary>
/// Creates a taskbar jumplist. Jump lists need to be recreated each time a command is invoked.
/// </summary>
private void CreateJumplist()
{
_jumpList = JumpList.CreateJumpList();
// Don't display the "Frequent" or "Recent" documents categories
_jumpList.KnownCategoryToDisplay = JumpListKnownCategoryType.Neither;
// The jump list link's IconReference needs the icon's physical file path
var path = Path.GetDirectoryName(Application.ExecutablePath);
var text = _mode == RenderMode.Auto
? Resources.Resources.PagedPreviewText
: Resources.Resources.AutoPreviewText;
// Add the jump list link with a command of -1. That command, when clicked, is passed in
// to the exe as a command line agrument.
_jumpList.AddUserTasks(new JumpListLink(Assembly.GetExecutingAssembly().Location, text)
{
Arguments = ModeCommandArgument,
IconReference = new IconReference(Path.Combine(path, "CopyIcon.ico"), 0)
});
// Same thing, but a command to clear all clipboard items
_jumpList.AddUserTasks(new JumpListLink(Assembly.GetExecutingAssembly().Location, "Clear Contents")
{
Arguments = ClearCommandArgument,
IconReference = new IconReference(Path.Combine(path, "CopyIcon.ico"), 0)
});
// Calling refresh displays the jumt list links
_jumpList.Refresh();
}
/// <summary>
/// Handles the Click event of the exitToolStripMenuItem control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
/// <summary>
/// Toggles between scrolling mode and auto mode. Scrolling mode displays one preview image at
/// a time with toolbar buttons. Auto mode displays one preview thumbnail per clipboard item.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void usePagedPreviewToolStripMenuItem_Click(object sender, EventArgs e)
{
ChangeMode();
}
/// <summary>
/// Handles the Click event of the clearToolStripMenuItem control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void clearToolStripMenuItem_Click(object sender, EventArgs e)
{
ClearContents();
}
}
}