MVV

|
Posted: Sat Apr 24, 2010 00:56 Post subject: |
|
|
В общем, вроде заработало оно у меня...
Но пожалуй функциям работы с буфером обмена в Windows я бы дал первое место по тупости (хотя, не знаю, что тупее организовано - работа с буфером или редирекция 32-битной папки System32 в 64-битной винде, которой вообще могло не существовать, сделай они папку System64 для 64-битных библиотек)...
Во-первых, пока одна программа открыла буфер, остальные вообще не могут ниче сделать.
Во-вторых, система дает тебе дескриптор - и хрен знает, что с ним делать. Например, для текстового формата дескриптор - тупо указатель на текст, ни тебе размера, ничего, голый текст.
В-третьих, эта идиотская система организации списка рассылки - который полностью формируется приложениями, но не системой! То есть, при добавлении окна в список оно должно запомнить, перед кем становится в очередь, и должно передавать уведомление по эстафете, при удалении того или иного окна это окно говорит, что оно такое-то, а за ним идет такое-то, и каждое окно передает по эстафете следующему, что такое-то окно удаляется, а вместо него теперь будет такое-то. И окно, видя, что удаляется то, что стояло за ним, должно теперь записать себе дескриптор нового "последыша"... Уже возникает вопрос, что будет, если хотя бы одно окно эстафеты тупо убьется, не удалив себя из списка рассылки... Очередь загнется?? Почему не система хранит очередь, а каждая программа должна включать избыточный код?..
Наконец, вспомним о том, что функция SendMessage не возвращает управление до тех пор, пока сообщение не обработано - получается, что окно, обработавшее уведомление и вызвавшее SendMessage для передачи уведомления далее по эстафете будет самым явным образом висеть до тех пор пока оконные процедуры всех последующих "глядителей буфера" не закончат обработку уведомления. Как вариант, возможно, можно было бы вызывать PostMessage, которая не блокирует управление (хотя в MSDN явно сказано вызывать SendMessage) - но если содержимое буфера вдруг изменится, вторая волна уведомлений может начаться до того как закончится первая...
Итак, лирическое отступление закончилось. Вот код для получения текста из буфера (и выдачи его в сообщении):
Code: | if (OpenClipboard(0)) {
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HANDLE hclip=GetClipboardData(CF_UNICODETEXT);
if (hclip) MessageBoxW(0, (wchar_t*)hclip, L"Clipboard text (Unicode)", MB_ICONINFORMATION);
}
else if (IsClipboardFormatAvailable(CF_OEMTEXT)) {
HANDLE hclip=GetClipboardData(CF_OEMTEXT);
if (hclip) MessageBoxA(0, (char*)hclip, "Clipboard text (ANSI)", MB_ICONINFORMATION);
}
else MessageBox(0, "Clipboard contains non-text data.", "Clipboard viewer", MB_ICONINFORMATION);
CloseClipboard();
} |
Как видно, довольно несложно. Замечу, что пока буфер открыт, ни одна программа не сможет ни прочитать из него, ни записать в него. Поэтому обработку нужно проводить как можно быстрее - например, скопировать текст в свой буфер и закрыть буфер. Выдаваемые сообщения - лишь пример, причем, неправильный, но позволяющий оценить неправильность подхода (пока не закроешь мессейджбокс, буфер обмена в других программах не работает).
А вот какие варианты uMsg нужно добавить в оконную процедуру для обработки:
Code: | case WM_DRAWCLIPBOARD:
if (OpenClipboard(hWnd)) {
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HANDLE hclip=GetClipboardData(CF_UNICODETEXT);
if (hclip) MessageBoxW(hWnd, (wchar_t*)hclip, L"Clipboard text (Unicode)", MB_ICONINFORMATION);
}
else if (IsClipboardFormatAvailable(CF_OEMTEXT)) {
HANDLE hclip=GetClipboardData(CF_OEMTEXT);
if (hclip) MessageBoxA(hWnd, (char*)hclip, "Clipboard text (ANSI)", MB_ICONINFORMATION);
}
else MessageBox(hWnd, "Clipboard contains non-text data.", "Clipboard viewer", MB_ICONINFORMATION);
CloseClipboard();
}
if (hNextClipboardViewer) {
SendMessage(hNextClipboardViewer, WM_DRAWCLIPBOARD, wParam, lParam);
MessageBox(hWnd, "Finished processing WM_DRAWCLIPBOARD message.", "Clipboard viewer", MB_ICONINFORMATION);
}
break;
case WM_CHANGECBCHAIN:
if ((HWND)wParam==hNextClipboardViewer) hNextClipboardViewer=(HWND)lParam;
else if (hNextClipboardViewer) SendMessage(hNextClipboardViewer, WM_CHANGECBCHAIN, wParam, lParam);
return 0;
case WM_CREATE:
hNextClipboardViewer=SetClipboardViewer(hWnd);
... // дальнейшие команды, выполняемые при создании окна
case WM_DESTROY:
ChangeClipboardChain(hWnd, hNextClipboardViewer);
... // дальнейшие команды, выполняемые при уничтожении окна
|
Окно, содержащее подобную хрень (именно хрень - как иначе назвать полстраницы кода, который только и делает что следит за изменениями буфера обмена да занимается прочими его проблемами), будет показывать мессейджбокс с новым текстом при каждом изменении содержимого буфера. hNextClipboardViewer - глобальная переменная, хранящая дескриптор следующего окна в цепочке списка рассылки уведомлений буфера обмена. Добавлять окно в список рассылки можно в любое время, необязательно при создании окна. Удалять его из списка можно тоже в любом месте, но до того как окно будет уничтожено - ведь нужно успеть разослать сообщение об удалении окна всем подписчикам. Насколько я понимаю, если удаляемое окно - следующее, то далее сообщение об удалении окна можно не передавать, так как мы должны у себя установить новое следующее окно, что и сделано в примере.
Как видно из примера, дескриптор, возвращаемый функцией GetClipboardData для форматов CF_UNICODETEXT и CF_OEMTEXT - тупо указатель на строку в соответствующей кодировке. Видимо, размер строки будет определяться позицией нулевого символа, раз способа получить длину строки API не предоставляет.
Вместо вывода замечу, что категорически не рекомендую использовать рабочее окно своей программы как "глядителя буфера" - лучше создать дополнительное невидимое окно, и пусть оно работает себе в фоне, чтобы не вешало основное. Ну а при необходимости можно посылать основному окну уведомление о том, что содержимое буфера изменилось - например, в аккурат перед передачей уведомления остальным подписчикам - тогда основное окно спокойно обработает буфер и продолжит работу, не зависая на неопределенное время. Хотя, наверное, лучше вообще всю грязную работу с буфером свалить на вспомогательное окно, а основному просто посылать через PostMessage специальное пользовательское сообщение (каждый может зарегистрировать себе новое сообщение для своих нужд) с указателем на выделенный блок памяти со скопированным из буфера обмена текстом - подчеркиваю, не оригинальным указателем, возвращенным функцией GetClipboardData, а указателем на самостоятельно выделенный блок памяти. При этом основное окно, получив такое сообщение, обрабатывает текст и освобождает присланный блок памяти (или вызывает фоновый обработчик, который затем освободит память).
Напоследок - примеры проявления фактов, замеченных в ходе выполнения лабораторки по исследованию API для работы с буфером обмена.
Выдача сообщения о завершении работы с буфером позволяет наглядно убедиться, что программа будет висеть, пока остальные подписчики на уведомления об изменении буфера "читают почту". Запустим два экземпляра программы (буду звать их первым и вторым). Далее копируем в буфер любой текст. Второй экземпляр покажет сообщение с новым текстом окна, далее такое же сообщение покажет первый экземпляр... но пока это окно сообщения открыто, второй экземпляр программы будет висеть, и только после закрытия всех окон сообщений первого позволит продолжить работу с его окном.
Еще забавно выполнять строчку с вызовом SetClipboardViewer дважды - при этом окно будет слать сообщение об изменении буфера само себе и зациклится при оном.
И можно проверить, как работает убийство процесса, окно которого следит за буфером - при этом нарушается цепочка уведомлений, и все подписчики, подписанные на рассылку ранее, "почты" уже не получат. Запустите, скажем, программу clipbrd (показывающую содержимое буфера), а затем - программу с этим примером. Убедитесь, что обе программы реагируют на изменение содержимого буфера. Теперь убейте процесс программы с примером. Всё! От программы clipbrd теперь толку как с козла молока - она уже не видит изменений буфера, так как умершая программа должна была сообщать ей о его изменениях.
PS. Что-то разошёлся я, прям статья получилась... Повод хороший попался)) _________________ TCFS2 + TCFS2Tools: Полноэкранный режим и многое другое (обсуждение)
WINCMD.RU: AskParam, CopyTree, NTLinks, Sudo, VirtualPanel… |
|