вторник, 20 декабря 2011 г.

Delphi, Кодировка и Буфер обмена (Clipboard)

Столкнулся с проблемой неправильной кодировки при переносе данных из приложений на Delphi.
Если переключить раскладку на РУС, то работа с буфером идёт корректно, иначе кракозябры.
По сети есть несколько решений:
  1. Использовать набор компонентов Unicode от TMS Software ("+" Быстро для старта / "-" Платные)
  2. Использовать модуль RusClipboard.pas
  3. Использовать процедуру правильно для правильного формирования сообщений буферу. SQL.RU ( "-" Ручное формирование буфера обмена)
 Я хочу предложить свой вариант, обобщающий всё выше + заметку из книги "Delphi глазами хакера" Ссылка ("Всё о работе с реестром")

Суть состоит в том, чтобы реализовать исправления в буфере динамически  для всех текстовых сообщений направляемых в буфер.


Сразу же добавим в раздел uses модуль Clipbrd, в котором объявлены функции.
   В Delphi нет готового обработчика события, который мог бы отвечать на изменения буфера обмена, поэтому придется описать самостоятельно. Для этого в разделе private главной формы пишем следующий код:
  uses
  Windows,Clipbrd;
private
    { Private declarations }
 FNextViewer:HWnd;
 procedure WMChangeCBChain(var Msg: TWMChangeCBChain); message WM_CHANGECBCHAIN;
 procedure WMDrawClipboard(var Msg: TWMDrawClipboard); message WM_DRAWCLIPBOARD;
  public
    { Public declarations }
  Здесь у нас объявлена переменная FNextViewer типа HWnd, в которой будем хранить указатель на следующее в системе окно, зарегистрированное в качестве наблюдателя.
Процедура WMChangeCBChain является обработчиком системного события WM_CHANGECBCHAIN. Об этом говорит добавленный в конце объявления процедуры оператор message с названием системного события. Это событие генерируется каждый раз, когда изменяется очередь, а именно происходит удаление или добавление какого-то наблюдателя. Чуть позже мы рассмотрим наши действия, которые должны быть в этом обработчике. Процедура WMDrawClipboard будет обработчиком системного события WM_DRAWCLIPBOARD. Оно генерируется каждый раз, когда в буфере обмена изменились данные, и его нужно перерисовать.

Активизировать наблюдатель можно в любом событии, лучше сразу при создании формы.
procedure Tform1.FormCreate(Sender: Tobject);
begin
 FnextViewer:=SetClipboardViewer(Handle);
end;
 Функция SetClipboardViewer получает в качестве параметра идентификатор окна, которое регистрируется как наблюдатель за буфером обмена. Именно этому окну будут направляться сообщения WM_CHANGECBCHAIN и WM_DRAWCLIPBOARD, и вы должны реализовать корректные обработчики событий. В качестве результата функция возвращает идентификатор окна, следующего за вашим в цепочке наблюдателей. После обработки события вы должгы сообзить этому окну о том, что в буфере произошли изменения, т.е. передать событие по цепочке.
 Теперь посмортим на реализацию функции WMChangeCBChain, котороая вызывается при изменении очереди наблюдателей за буфером обмена:
procedure Tform1.WMChain(var Msg: TWMChangeCBChain);
begin
 if Msg.Remove = FNextViewer then
  FnextViewer:=Msg.Next
 else
  SendMessage(FNextViewer, WM_CHANGECBCHAIN, Msg.Remove, Msg.Next);
end;

   В качестве параметра функции мы получаем переменную Msg типа структуры TWMChangeCBChain. В этой структуре нас будут интересовать два свойства: Remove и Next. Первое свойство указывает а окно, которое должно быть удалено из очереди, а второе указывает на окно, следующее за ним. Наша задача проверить, если удаляемое окно является удаляемым, которому мы посылаем сообщения, то должна быть произведена замена следующим наблюдателем.
procedure TForm1.WMDrawClipboard(var Msg: TWMDrawClipboard);
var WideBuffer: WideString;
    BuffSize: Cardinal;
    Data: THandle;
    DataPtr: Pointer;
begin
    WideBuffer := clipboard.AsText;
    BuffSize := length(WideBuffer) * SizeOf(WideChar);
    Data := GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE+GMEM_ZEROINIT, BuffSize + 2);
    try
      DataPtr := GlobalLock(Data);
      try
        Move(PWideChar(WideBuffer)^, Pointer(Cardinal(DataPtr))^, BuffSize);
      finally
        GlobalUnlock(Data);
      end;
      Clipboard.SetAsHandle(CF_UNICODETEXT, Data);
    except
      GlobalFree(Data);
      raise;
    end;
end;
Эта процедура срабатывает на изменение буфера обмена. В переменную WideBuffer помещается текущее содержание буфера (только если его содержимое - текст), далее рассчитывается размер и содержимое переменной перемещается в буфер в формате UNICODE.
Таким образом любые текстовые данные перемещаемые из приложения (любого приложения в момент работы программы) через буфер, будут преобразованы в UNICODE и правильно поняты приложением-приемником.

1 комментарий: