Введение
Пожалуй, буфер обмена — одно из найболее часто используемых функций в Windows, да и в других операционных системах. Сегодня мы взглянем на то, как при помощи.net Framework и нескольких
API-функций Windows создать простое приложение, которое будет следить за буфером обмена и сообщать нам о изменении его содержимого.
Класс Clipboard
В
.net-приложении буфер обмена Windows доступен в любой момент при помощи класса
System. Windows. Forms. Clipboard. Для получения данных, которые находятся в буфере, предназначены четыре метода:
public static string Clipboard.GetText();
public static string Clipboard.GetText(TextDataFormat format);
public static Object Clipboard.GetData(string format);
public static IDataObject Clipboard.GetDataObject();
Найболее простой из них — первый метод, который получает текстовые данные из буфера обмена. Если в буфере не текстовые данные (а, например, изображение), то результатом будет пустая строка.
Метод
GetText (TextDataFormat format) отличается от собственной перегрузки без параметров тем, что позволяет получить текст из буфера обмена в определенном формате (всего их пять — Text, UnicodeText, Rtf,
Html и CommaSeparatedValue). Опять же, если в буфере обмена не текстовые данные, либо данные не запрошенного формата, результатом вызова метода будет пустая строка.
Для проверки того, содержит ли буфер текст нужного формата, существуют следующие методы:
public static bool ContainsText();
public static bool ContainsText(TextDataFormat format);
Синтаксис их вызова идентичен методам GetText, но результатом будет
true, если буфер обмена содержит текст заданного формата и
false в обратном случае.
Последние два
Get-метода предназначены для получения объекта определенного формата из буфера (о других форматах мы поговорим в следущей части статьи) и имеют соответствующие
Contains-методы.
Уведомления об изменении содержимого буфера обмена
Что нужно сделать, чтобы узнать, когда содержимое буфера изменилось? Простейшее решение — воспользоваться таймером и периодически опрашивать буфер на предмет изменения. Это не лучшее решение — содержимое буфера может измениться несколько раз между итерациями таймера, а слишком частый опрос может быть губительным для производительности.
В этой задаче нам на помощь приходят три
WinAPI-функции:
//Register a window handle as a clipboard viewer
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetClipboardViewer(IntPtr hWnd);
//Remove a window handle from the clipboard viewers chain
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(
IntPtr hWndRemove, // handle to window to remove
IntPtr hWndNewNext // handle to next window
);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
Функция
SetClipboardViewer регистрирует окно для получения сообщения WM_DRAWCLIPBOARD (о нём ниже),
ChangeClipboardChain предназначена для удаления идентификатора окна из цепочки получения WM_DRAWCLIPBOARD, а
SendMessage — функция WinAPI для отправки сообщений Windows другим окнам.
Сообщение
WM_DRAWCLIPBOARD рассылается системой при изменении содержимого буфера обмена. С помощью функции
SetClipboardViewer мы подпишем наше приложение в цепочку получателей этого сообщения. Небольшой сложностью этого подхода будет то, что наше приложение должно будет передать сообщение дальше по цепочке — адрес окна, которому нужно будет передать сообщение, вернет функция
SetClipboardViewer.
Кроме обработки
WM_DRAWCLIPBOARD, нашему приложению придётся обрабатывать также и сообщение
WM_CHANGECBCHAIN. Система рассылает это сообщение, когда исключает одно из окон из цепочки рассылки.
Время для кода
Итак, откроем Visual Studio, создадим новый проект типа Windows Forms Application, и приступим к работе. Создадим простой интерфейс для приложения:
Кнопки Start и Stop Monitoring будут соответственно регистрировать окно нашего приложения в цепочке уведомлений и удалять его оттуда, а в текстовое поле будет выводиться изменения в буфере и прочая диагностическая информация. Для этого будет предназначена вспомогательная функция
Output:
private void Output(string message)
{
tbOutput.Text += message + Environment.NewLine; // добавить наше сообщение
tbOutput.SelectionStart = tbOutput.Text.Length; // установить курсор в конец текста
tbOutput.ScrollToCaret(); // прокрутка до курсора для показа сообщения
}
Добавим переменную, в которой будем хранить дескриптор окна, которому будем передавать сообщения
WM_DRAWCLIPBOARD и
WM_CHANGECBCHAIN:
private IntPtr nextClipboardViewer;
Добавим обработчики событий для кнопок:
private void btnStart_Click(object sender, EventArgs e)
{
nextClipboardViewer = SetClipboardViewer(this.Handle);
Output("Registered clipboard viewer.");
}
private void btnStop_Click(object sender, EventArgs e)
{
ChangeClipboardChain(this.Handle, nextClipboardViewer);
Output("Unregistered clipboard viewer.");
}
Переопределим функцию WndProc, чтобы обработать пришедшие нам сообщения:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
// содержимое буфера обмена изменилось
{
// реагируем на изменение буфера
ClipboardChanged();
// пересылаем сообщение следующему окну в цепочке
SendMessage(nextClipboardViewer, WM_DRAWCLIPBOARD, IntPtr.Zero, IntPtr.Zero);
break;
}
case WM_CHANGECBCHAIN:
// цепочка приложений, подписанных на WM_DRAWCLIPBOARD изменилась
{
if (m.WParam == nextClipboardViewer)
{
// окно, которому мы передавали WM_DRAWCLIPBOARD, удалено из
// цепочки, и нам нужно обновить переменную
nextClipboardViewer = m.LParam;
}
else
{
// просто передаем новости дальше
SendMessage(nextClipboardViewer, WM_CHANGECBCHAIN, m.WParam, m.LParam);
}
m.Result = IntPtr.Zero; // уведомляем систему об обработке сообщения
break;
}
default:
// поведение для остальных сообщений - по умолчанию
{
base.WndProc(ref m);
break;
}
}
}
И последнее, что осталось сделать — реализовать метод
ClipboardChanged:
private void ClipboardChanged()
{
Output("The clipboard content has been changed.");
string s = "New clipboard content: ";
if (Clipboard.ContainsText()) // будем выводить только текстовое содержимое
{
s += Clipboard.GetText();
}
else
{
s += "[non-text]";
}
Output(s);
}
Готово! Монитор буфера обмена готов к работе:
Обратите внимание, что при регистрации монитора Windows тут же присылает нам сообщение о изменении буфера —
что-то вроде «добро пожаловать» :)
В следующей части мы рассмотрим работу с другими форматами данных буфера обмена, и пропагрейдим наш монитор до менеджера буфера обмена — с историей и прочими полезными функциями.
Исходник проекта (Visual Studio 2008)
SetClipboardViewer Function (Windows)
Clipboard Class (System. Windows. Forms)