Очень много приходится работать с текстовыми данными, такими как код, статьи, посты и т.д. В то время когда жил под Linux — пользовался менеджерами истории буфера обмена, которые запоминали, то что попадало в виде текста в буфер и по клику в трее я мог вернуть нужное значение в буфер, не возвращаясь к источнику.
Недавно пришлось большую часть времени проводить в Windows, удовлетворяющей альтернативы для такого простого приложения я не нашел. Что-то в найденных вариантах не устраивало: не свободное ПО, много ненужного функционала (который просто мешал) или работали неудобно для меня (например: получить предыдущее значение буфера, приходилось открывать окно программы). Недолго думая решил сделать, так как мне хотелось.
Поскольку, приложение должно работать исключительно в Windows, было принято решение написать ее на C# — к тому же, никогда ранее на нем ничего не писал — появился повод попробовать.
Задача
- Программа должна слушать и запоминать изменения в текстовом буфере обмена.
- Через контекстное меню в трее или через окно программы по клику на выбранном элементе истории, нужное значение должно автоматически попадать назад в буфер.
- История не должна пропадать после перезагрузки системы
Интерфейс
- Возможность поставить приложение на автозагрузку в реестр.
- Изменить количество хранимой истории.
- Изменить количество последних элементов буфера выводимых через контекстное меню в трее.
- Принудительная очистка истории.
- Выход из программы, поскольку при закрытии программа должна сворачиваться в трей.
Выкладывать буду основные части кода с небольшими пояснениями. В конце под спойлер выложен полный листинг программы, а так же ссылка на полный проект упакованный в zip архив + отдельно ссылка на скомпилированную версию программы в виде exe файла.
Непосредственно сам код
Изобретать велосипед не хотелось для хранения настроек программы, поэтому «размера истории» и «количество выводимых элементов в трее» — использовал Properties.Settings.Default:
// изменение размера истории
private void history_size_ValueChanged(object sender, EventArgs e)
{
Properties.Settings.Default.history_size = (int)history_size.Value;
Properties.Settings.Default.Save();
Console.WriteLine("Размер истории изменен: " + Properties.Settings.Default.history_size);
reload_list_clipboard(); // Обновляем ListBox
}
// изменение количества записей БО в трее
private void size_tray_ValueChanged(object sender, EventArgs e)
{
Properties.Settings.Default.size_tray = (int)size_tray.Value;
Properties.Settings.Default.Save();
Console.WriteLine("Количество элементов в трее изменено: " + Properties.Settings.Default.size_tray);
reload_tray(); // Обновляем Трей
}
Автозагрузка, программы, как было оговорено ранее, реализована через реестр.
// Событие изменения статуса флажка автозагрузки
// Если флажок - прописываем в реестр на автозагрузку
private void autoload_CheckedChanged(object sender, EventArgs e)
{
RegistryKey reg = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWAREMicrosoftWindowsCurrentVersionRun", true);
if (reg.GetValue(VirtualClipBoard_Name) != null)
{
try
{
reg.DeleteValue(VirtualClipBoard_Name);
Console.WriteLine("Программа " + VirtualClipBoard_Name + " удалена из автозагрузки в реестре");
}
catch
{
Console.WriteLine("Ошибка удаления " + VirtualClipBoard_Name + " из автозагрузки в реестре");
}
}
if(autoload.Checked)
{
reg.SetValue(VirtualClipBoard_Name, Application.ExecutablePath);
Console.WriteLine("Программа " + VirtualClipBoard_Name + " записана в автозагрузку через реестр");
}
reg.Close();
}
При закрытии окна, программа должна сворачиваться в трей, а не завершать работу
// Сворачивать в трей вместо закрытия программы
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = true;
ShowInTaskbar = false;
Hide();
}
Метод для завершения работы программы
// Завершение работы программы по закрытию через кнопку
private void exit_Click(object sender, EventArgs e)
{
Application.Exit();
}
Поскольку, проверять по таймеру изменения в буфере обмена — это извращение + неоправданная трата ресурсов, мы будем использовать User32.dll. Для этого нам необходимо добавить наше окно к цепочки окон буфера обмена и используя WndProc принимать сообщения, а точнее WM_DRAWCLIPBOARD = 0x0308, которое уведомит нас об изменении в буфере обмена.
Итак, 1. Подключаем библиотеки:
[DllImport("User32.dll")]
protected static extern int SetClipboardViewer(int hWndNewViewer);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
2. Реализация метода WndProc принимающего сообщения и отправляющее их далее по цепочке:
// дескриптор окна
private IntPtr nextClipboardViewer;
// Константы
public const int WM_DRAWCLIPBOARD = 0x0308;
public const int WM_CHANGECBCHAIN = 0x030D;
// Метод для реагирование на изменение вбуфере обмена и т.д.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
{
ClipboardChanged();
SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
}
case WM_CHANGECBCHAIN:
{
if (m.WParam == nextClipboardViewer)
{
nextClipboardViewer = m.LParam;
}
else
{
SendMessage(nextClipboardViewer, WM_CHANGECBCHAIN, m.WParam, m.LParam);
}
m.Result = IntPtr.Zero;
break;
}
default:
{
base.WndProc(ref m);
break;
}
}
}
3. В методе загрузки формы зарегистрируем наше окно:
nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
4. Теперь необходимо реагировать на изменение в буфере и записывать их в историю, для этого создадим метод ClipboardChanged. Запись истории будет происходить в словарь Dictionary<int, string> VirtualClipBoard_History = new Dictionary<int, string>();, если количество элементов в словаре больше, чем размер истории, в методе также введем очистку от более старых элементов. Так же, для возможности получить историю предыдущей сессии, будем записывать новые элементы в файл VirtualClipBoard_DAT.
// Реагируем на обновление буфераобмена
private void ClipboardChanged()
{
if (Clipboard.ContainsText() && Clipboard.GetText().Length > 0 && VirtualClipBoard_TARGET != Clipboard.GetText())
{
VirtualClipBoard_TARGET = Clipboard.GetText();
// Записываем новый элемент в словарь
VirtualClipBoard_History.Add((VirtualClipBoard_History.Last().Key + 1), VirtualClipBoard_TARGET);
reload_tray(); // Обноавляемменю в трее
reload_list_clipboard(); // Обновляем ListBox
// Отчистка словаря от лишних элементов
if (VirtualClipBoard_History.Count() > Properties.Settings.Default.history_size)
{
int clear_items_count = VirtualClipBoard_History.Count() - Properties.Settings.Default.history_size;
var list = VirtualClipBoard_History.Keys.ToList();
list.Sort();
foreach (var key in list)
{
VirtualClipBoard_History.Remove(key);
if (clear_items_count == 1) { break; } else { clear_items_count--; }
}
}
// Записываем новый элемент в файл истории
StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, true, System.Text.Encoding.UTF8);
writer.WriteLine(@"<item>" + VirtualClipBoard_TARGET.Replace(@"<", @"<").Replace(@">", @">") + @"</item>");
writer.Close();
Console.WriteLine("В историю добавлен новый элемент: " + VirtualClipBoard_TARGET);
}
}
Для вывода элементов в трей создадим метод reload_tray. Использовать будем ContextMenuStrip. Для каждого ToolStripMenuItem будем использовать TAG, в который будем передавать ключ в словаре, чтоб при выборе элемента в контекстном меню легко вытащить нужный элемент из словаря.
// Перезагрузка элементов для трей
private void reload_tray()
{
ContextMenuStrip contextMenu = new ContextMenuStrip();
ToolStripMenuItem menuItem;
int free_slot_to_tray = Properties.Settings.Default.size_tray;
var list = VirtualClipBoard_History.OrderByDescending(x => x.Key);
foreach (var item in list)
{
menuItem = new ToolStripMenuItem();
menuItem.Tag = item.Key;
if (item.Value.Length > 60)
{
menuItem.Text = item.Value.Replace("n", "t").Replace("r", "t").Substring(0, 60);
} else {
menuItem.Text = item.Value.Replace("n", "t").Replace("r", "t");
}
menuItem.Click += new System.EventHandler(menu_item_click);
contextMenu.Items.Add(menuItem);
if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; }
}
// Разделитель
contextMenu.Items.Add(new ToolStripSeparator());
// Свернуть/Развернуть
menuItem = new ToolStripMenuItem();
menuItem.Text = "Настройки";
menuItem.Click += new System.EventHandler(menu_item_config);
contextMenu.Items.Add(menuItem);
// Выход из программы
menuItem = new ToolStripMenuItem();
menuItem.Text = "Выход";
menuItem.Click += new System.EventHandler(exit_Click);
contextMenu.Items.Add(menuItem);
_notifyIcon.ContextMenuStrip = contextMenu;
}
События обработки клика в контекстном меню в трее:
// Событие по клику на элемент контекстного меню в трее
private void menu_item_click(object sender, EventArgs e)
{
Clipboard.SetText(VirtualClipBoard_History[(int)(sender as ToolStripMenuItem).Tag]);
}
Генерация списка для ListBox в самой форме программы. Для удобства будем использовать дополнительный словарь VirtualClipBoard_Index_ListBox = new Dictionary<int, int>(); чтоб связать элемент ListBox c элементом в словаре истории VirtualClipBoard_History:
// Перезагрузка элементов в ListBox
private void reload_list_clipboard()
{
VirtualClipBoard_Index_ListBox = new Dictionary<int, int>();
int list_target_item = 0; // индекс текущего элемента в ListBox
list_clipboard.Items.Clear(); // Очищаем список
String string_name_ite;
int free_slot_to_tray = Properties.Settings.Default.history_size;
var list = VirtualClipBoard_History.OrderByDescending(x => x.Key);
foreach (var item in list)
{
if (item.Value.Length > 150)
{
string_name_ite = item.Value.Replace("n", "t").Replace("r", "t").Substring(0, 60);
}
else
{
string_name_ite = item.Value.Replace("n", "t").Replace("r", "t");
}
list_clipboard.Items.Add(string_name_ite);
VirtualClipBoard_Index_ListBox.Add(list_target_item, item.Key);
if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; }
list_target_item++; // Увеличиваем индекс текущего элемента в ListBox
}
}
При выборе элемента в ListBox — событие list_clipboard_SelectedIndexChanged:
// Выбор элемента в ListBox
private void list_clipboard_SelectedIndexChanged(object sender, EventArgs e)
{
Clipboard.SetText(VirtualClipBoard_History[VirtualClipBoard_Index_ListBox[list_clipboard.SelectedIndex]]);
}
Во время загрузки программы, считываем историю из файла VirtualClipBoard_DAT и парсим данные, как XML.
Элементы истории буфера обмена хранить отдельно в каждой строчке в нашем случае не получится, поскольку элементы могут иметь символы перевода каретки (n), которые могут повредить нам целостность данных при считывании.
Если использовать serialize, то можно потерять на быстродействии программы при каждом изменении буфера.
Правильней конечно было бы сохранять историю в файл при закрытии программы, но мне этот вариант что-то не по душе пришел в этом случае, поэтому организовал именно таким способом (кого-то могу этим зацепить — сразу прошу прощения).
Кодировать данные так же не хотел, хотелось оставить исходный вид данных, чтоб при необходимости можно было читать файл истории через блокнот.
// Загружаем историю из файла
String XMLString = "";
XMLString += @"<items>";
if (File.Exists(VirtualClipBoard_DAT))
{
StreamReader stream = new StreamReader(VirtualClipBoard_DAT);
while (stream.Peek() > -1)
{
XMLString += stream.ReadLine() + "n";
}
stream.Close();
XMLString += @"</items>";
int index_new_history = 2;
XDocument doc = XDocument.Parse(XMLString);
var items = doc.Element("items").Elements("item");
foreach (XElement item in items)
{
VirtualClipBoard_History.Add(index_new_history, item.Value);
index_new_history++; // увеличиваем индекс новому элементу
}
}
Весь код проекта
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Data.Sql;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
namespace VirtualClipBoard
{
public partial class VirtualClipBoard : Form
{
String VirtualClipBoard_Name = "VirtualClipBoard"; // название программы
public String VirtualClipBoard_TARGET; // последний значение текстового БО
public String VirtualClipBoard_DAT; // путь к файлу истории
Dictionary<int, string> VirtualClipBoard_History = new Dictionary<int, string>(); // История нашего буфера
Dictionary<int, int> VirtualClipBoard_Index_ListBox; // список индексов в связки с ключами истории буфера
// Подключение библиотек WIN
[DllImport("User32.dll")]
protected static extern int SetClipboardViewer(int hWndNewViewer);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
// Наш Form
public VirtualClipBoard()
{
InitializeComponent();
load_configs();
nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
reload_tray(); // Обноавляемменю в трее
reload_list_clipboard(); // Обновляем ListBox
_notifyIcon.Text = VirtualClipBoard_Name;
_notifyIcon.MouseDoubleClick += new MouseEventHandler(_notifyIcon_MouseDoubleClick);
}
// Перезагрузка элементов в ListBox
private void reload_list_clipboard()
{
VirtualClipBoard_Index_ListBox = new Dictionary<int, int>();
int list_target_item = 0; // индекс текущего элемента в ListBox
list_clipboard.Items.Clear(); // Очищаем список
String string_name_ite;
int free_slot_to_tray = Properties.Settings.Default.history_size;
var list = VirtualClipBoard_History.OrderByDescending(x => x.Key);
foreach (var item in list)
{
if (item.Value.Length > 150)
{
string_name_ite = item.Value.Replace("n", "t").Replace("r", "t").Substring(0, 60);
}
else
{
string_name_ite = item.Value.Replace("n", "t").Replace("r", "t");
}
list_clipboard.Items.Add(string_name_ite);
VirtualClipBoard_Index_ListBox.Add(list_target_item, item.Key);
if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; }
list_target_item++; // Увеличиваем индекс текущего элемента в ListBox
}
}
// Выбор элемента в ListBox
private void list_clipboard_SelectedIndexChanged(object sender, EventArgs e)
{
Clipboard.SetText(VirtualClipBoard_History[VirtualClipBoard_Index_ListBox[list_clipboard.SelectedIndex]]);
}
// Перезагрузка элементов для трей
private void reload_tray()
{
ContextMenuStrip contextMenu = new ContextMenuStrip();
ToolStripMenuItem menuItem;
int free_slot_to_tray = Properties.Settings.Default.size_tray;
var list = VirtualClipBoard_History.OrderByDescending(x => x.Key);
foreach (var item in list)
{
menuItem = new ToolStripMenuItem();
menuItem.Tag = item.Key;
if (item.Value.Length > 60)
{
menuItem.Text = item.Value.Replace("n", "t").Replace("r", "t").Substring(0, 60);
} else {
menuItem.Text = item.Value.Replace("n", "t").Replace("r", "t");
}
menuItem.Click += new System.EventHandler(menu_item_click);
contextMenu.Items.Add(menuItem);
if (free_slot_to_tray == 1) { break; } else { free_slot_to_tray--; }
}
// Разделитель
contextMenu.Items.Add(new ToolStripSeparator());
// Свернуть/Развернуть
menuItem = new ToolStripMenuItem();
menuItem.Text = "Настройки";
menuItem.Click += new System.EventHandler(menu_item_config);
contextMenu.Items.Add(menuItem);
// Выход из программы
menuItem = new ToolStripMenuItem();
menuItem.Text = "Выход";
menuItem.Click += new System.EventHandler(exit_Click);
contextMenu.Items.Add(menuItem);
_notifyIcon.ContextMenuStrip = contextMenu;
}
// Вызов окна настроек
private void menu_item_config(object sender, EventArgs e)
{
ShowInTaskbar = true;
Show();
}
// Событие по клику на элемент контекстного меню в трее
private void menu_item_click(object sender, EventArgs e)
{
Clipboard.SetText(VirtualClipBoard_History[(int)(sender as ToolStripMenuItem).Tag]);
}
// событие при клике мышкой по значку в трее
private void _notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (ShowInTaskbar == true)
{
ShowInTaskbar = false;
Hide();
}
else
{
ShowInTaskbar = true;
Show();
}
}
// Установка путей к файлам конфигурации и истории
private void load_configs()
{
VirtualClipBoard_DAT = Application.UserAppDataPath + "\history.dat";
Console.WriteLine("Файл истории: " + VirtualClipBoard_DAT);
history_size.Value = Properties.Settings.Default.history_size;
Console.WriteLine("Размер истории загружен из настроек: " + Properties.Settings.Default.history_size);
size_tray.Value = Properties.Settings.Default.size_tray;
Console.WriteLine("Количество элементов в трее загружено из настроек: " + Properties.Settings.Default.size_tray);
RegistryKey reg = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWAREMicrosoftWindowsCurrentVersionRun", true);
if (reg.GetValue(VirtualClipBoard_Name) != null){
autoload.Checked = true;
Console.WriteLine("Приложение записано в автозагрузку. (В настройках ставим Checked = true)");
}
reg.Close();
// Загружаем историю из файла
String XMLString = "";
XMLString += @"<items>";
if (File.Exists(VirtualClipBoard_DAT))
{
StreamReader stream = new StreamReader(VirtualClipBoard_DAT);
while (stream.Peek() > -1)
{
XMLString += stream.ReadLine() + "n";
}
stream.Close();
XMLString += @"</items>";
int index_new_history = 2;
XDocument doc = XDocument.Parse(XMLString);
var items = doc.Element("items").Elements("item");
foreach (XElement item in items)
{
VirtualClipBoard_History.Add(index_new_history, item.Value);
index_new_history++; // увеличиваем индекс новому элементу
}
}
// Чистим историю буфера
if (VirtualClipBoard_History.Count() > Properties.Settings.Default.history_size)
{
int clear_items_count = VirtualClipBoard_History.Count() - Properties.Settings.Default.history_size;
var list = VirtualClipBoard_History.Keys.ToList();
list.Sort();
foreach (var key in list)
{
VirtualClipBoard_History.Remove(key);
if (clear_items_count == 1) { break; } else { clear_items_count--; }
}
}
// Обновляем файл истории
StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, false, System.Text.Encoding.UTF8);
var new_list = VirtualClipBoard_History.Keys.ToList();
new_list.Sort();
foreach (var key in new_list)
{
writer.WriteLine(@"<item>" + VirtualClipBoard_History[key].Replace(@"<", @"<").Replace(@">", @">") + @"</item>");
}
writer.Close();
// Если элементов ноль, добавляем из буфера
Console.WriteLine(VirtualClipBoard_History.Count());
if (VirtualClipBoard_History.Count() == 0)
{
VirtualClipBoard_TARGET = Clipboard.GetText();
VirtualClipBoard_History.Add(1, VirtualClipBoard_TARGET);
}
VirtualClipBoard_TARGET = VirtualClipBoard_History.Last().Value;
}
// Событие изменения статуса флажка автозагрузки
// Если флажок - прописываем в реестр на автозагрузку
private void autoload_CheckedChanged(object sender, EventArgs e)
{
RegistryKey reg = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWAREMicrosoftWindowsCurrentVersionRun", true);
if (reg.GetValue(VirtualClipBoard_Name) != null)
{
try
{
reg.DeleteValue(VirtualClipBoard_Name);
Console.WriteLine("Программа " + VirtualClipBoard_Name + " удалена из автозагрузки в реестре");
}
catch
{
Console.WriteLine("Ошибка удаления " + VirtualClipBoard_Name + " из автозагрузки в реестре");
}
}
if(autoload.Checked)
{
reg.SetValue(VirtualClipBoard_Name, Application.ExecutablePath);
Console.WriteLine("Программа " + VirtualClipBoard_Name + " записана в автозагрузку через реестр");
}
reg.Close();
}
// Завершение работы программы по закрытию через кнопку
private void exit_Click(object sender, EventArgs e)
{
Application.Exit();
}
// изменение размера истории
private void history_size_ValueChanged(object sender, EventArgs e)
{
Properties.Settings.Default.history_size = (int)history_size.Value;
Properties.Settings.Default.Save();
Console.WriteLine("Размер истории изменен: " + Properties.Settings.Default.history_size);
reload_list_clipboard(); // Обновляем ListBox
}
// изменение количества записей БО в трее
private void size_tray_ValueChanged(object sender, EventArgs e)
{
Properties.Settings.Default.size_tray = (int)size_tray.Value;
Properties.Settings.Default.Save();
Console.WriteLine("Количество элементов в трее изменено: " + Properties.Settings.Default.size_tray);
reload_tray(); // Обновляем Трей
}
// Реагируем на обновление буфераобмена
private void ClipboardChanged()
{
if (Clipboard.ContainsText() && Clipboard.GetText().Length > 0 && VirtualClipBoard_TARGET != Clipboard.GetText())
{
VirtualClipBoard_TARGET = Clipboard.GetText();
// Записываем новый элемент в словарь
VirtualClipBoard_History.Add((VirtualClipBoard_History.Last().Key + 1), VirtualClipBoard_TARGET);
reload_tray(); // Обноавляемменю в трее
reload_list_clipboard(); // Обновляем ListBox
// Отчистка словаря от лишних элементов
if (VirtualClipBoard_History.Count() > Properties.Settings.Default.history_size)
{
int clear_items_count = VirtualClipBoard_History.Count() - Properties.Settings.Default.history_size;
var list = VirtualClipBoard_History.Keys.ToList();
list.Sort();
foreach (var key in list)
{
VirtualClipBoard_History.Remove(key);
if (clear_items_count == 1) { break; } else { clear_items_count--; }
}
}
// Записываем новый элемент в файл истории
StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, true, System.Text.Encoding.UTF8);
writer.WriteLine(@"<item>" + VirtualClipBoard_TARGET.Replace(@"<", @"<").Replace(@">", @">") + @"</item>");
writer.Close();
Console.WriteLine("В историю добавлен новый элемент: " + VirtualClipBoard_TARGET);
}
}
// Затираем всю историю
private void clear_Click(object sender, EventArgs e)
{
StreamWriter writer = new StreamWriter(VirtualClipBoard_DAT, false, System.Text.Encoding.Default);
writer.Write("");
writer.Close();
VirtualClipBoard_History = new Dictionary<int, string>();
VirtualClipBoard_TARGET = Clipboard.GetText();
VirtualClipBoard_History.Add(1, VirtualClipBoard_TARGET);
reload_tray(); // Обноавляемменю в трее
reload_list_clipboard(); // Обновляем ListBox
}
// Сворачивать в трей вместо закрытия программы
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = true;
ShowInTaskbar = false;
Hide();
}
// дескриптор окна
private IntPtr nextClipboardViewer;
// Константы
public const int WM_DRAWCLIPBOARD = 0x0308;
public const int WM_CHANGECBCHAIN = 0x030D;
// Метод для реагирование на изменение вбуфере обмена и т.д.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
{
ClipboardChanged();
SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
}
case WM_CHANGECBCHAIN:
{
if (m.WParam == nextClipboardViewer)
{
nextClipboardViewer = m.LParam;
}
else
{
SendMessage(nextClipboardViewer, WM_CHANGECBCHAIN, m.WParam, m.LParam);
}
m.Result = IntPtr.Zero;
break;
}
default:
{
base.WndProc(ref m);
break;
}
}
}
}
}
Список литературы:
Properties.Settings.Default — сохранение пользовательских настроек приложения
Registry.LocalMachine — работа с реестром
SetClipboardViewer — добавляем определяемое окно к цепочке окон просмотра буфера обмена.
класс StreamWriter — запись в файл
класс ContextMenuStrip — контекстное меню
класс Dictionary — Представляет коллекцию ключей и значений.
класс Clipboard — Предоставляет методы для помещения данных в системный буфер обмена и извлечения данных из системного буфера обмена.
Ссылки на проект:
Скачать исходники всего проекта для VS в zip (133КБ)
Скачать скомпилированный EXE файл (225КБ)
Автор: yanzlatov
Здесь ошибка:
// Записываем новый элемент в словарь
VirtualClipBoard_History.Add((VirtualClipBoard_History.Last().Key + 1), VirtualClipBoard_TARGET);
VirtualClipBoard_History.Last().Key – для Dictionary это не имеет смысла – он не сортирован. Самый простой фикс – использовать SortedDictionary. На хабре увидел эту статью, но там какая-то х-ня с комментированием/личкой.