Главная » Delphi » Проблема автоматического переключения раскладки в RichEdit

1

Создадим тестовый проект с именем RichEditText (модуль формы назовем edit.pas— на диске Glava8\2), положим на пустую форму компонент RichEdit, запустим его и наберем в нем несколько разноязычных строк. Если вы теперь будете клавишами управления курсором или мышью перемещать текстовый курсор между этими строками, то раскладка у вас также будет меняться. Интересно, что баг (bug) функционирует непоследовательно: если окно теряет фокус ввода, а потом его восстанавливает, то раскладка устанавливается та, что была по умолчанию установлена системным переключателем, а стоит курсор сдвинуть с места, если он на строке с другой раскладкой, она тут же переключится. Потерей-возвращением фокуса можно вернуть раскладку на место. Заметим, что этот баг широко известен, начиная по крайней мере с версии Delphi 2.0, корпорация Borland успела с тех пор пару раз сменить название, но он так и кочует из версии в версию" (уверен, что и в Delphi .NET он также наверняка имеется — просто не проверял). Вроде бы винить Borland особенно не за что, т. к. компонент этот есть просто ретрансляция класса того же названия (RichEdit) из Windows API. Но ведь справиться с этим багом квалифицированный программист может минут за пятнадцать, и, к тому же, это не единственное, что в RichEdit сделано достаточно "криво" — так, что без прямого обращения к API сколько-нибудь приличную программу на его основе сделать просто нельзя.

Для того чтобы понять, что происходит в RichEdit при навигации по многоязычному тексту, достаточно воспользоваться программой WinSight ("официальным инструментом хакера", как ее часто характеризуют), которая входит в поставку Delphi. "Поковырявшись" в сообщениях, мы обнаружим, что разница между событиями переключения языка самопроизвольно и по нашей команде заключается в том, что в первом случае появляется только сообщение wm inputlangchange, во втором — сначала еще и наш любимый запрос (см. главу 7) wm inputlangchangerequest. Это логично: запросу при автоматическом переключении просто неоткуда взяться, а сообщение

wm_inputxangchange отправляется уже после того, как был изменен язык ввода. Изменен-то изменен, но в нашем праве пропустить это сообщение "мимо ушей" и ничего не переключать. Алгоритм исправления ситуации понятный: если запрос был — переключаем, если не было — игнорируем.

События можно в принципе отследить любым из способов: либо, как мы уже умеем, с помощью ловушки, причем тут дело значительно облегчается тем, что мы действуем в пределах окна программы и ловушка может быть локальной. Либо воспользоваться штатным способом переопределения метода OnMessage приложения. Однако на практике все это отладить довольно сложно: запрос wm_inputlangchangerequest перехватывается легко и непринужденно, а вот с последующим сообщением wm inputlangchange могут возникнуть трудности. Однако есть изящное решение, которое, в том числе, позволяет перехватывать и фильтровать только нужные сообщения, а не все на свете. Заключается оно в том, чтобы создать свой собственный RichEdit на основе имеющегося, изменив в нем только реакцию на сообщение wm_xnputlangchange7. Назовем его RichEditlnt (international).

Так как "люди мы не местные", мы будем создавать компоненты вручную (точнее, в среде Delphi), а не с помощью ModelMaker (для чего его еще надо отдельно установить с диска Delphi). Имейте в виду, что компонент можно создавать и отлаживать либо "ручками", либо уж полностью через ModelMaker— мешать данные способы не получится. Это в Delphi разрешается править исходники в каком угодно редакторе, a ModelMaker— такая странная штука, которая в результате компилящш проекта выдает исходный текст (используемый потом в Delphi). И если вы внесете свои изменения отдельно, то придется делать это уже до конца, потому что при попытке загрузки исправленного варианта проекта ModelMaker эти ваши изменения даже не просто проигнорирует, а немедленно уничтожит, ничего не спрашивая. Кроме того, в некоторых отношениях использование ModelMaker, по личному мнению автора, не упрощает, а усложняет задачу — например, на первом этапе, когда надо определить класс-родителя. Но, конечно, у этой программы есть и свои преимущества (чем сложнее проект — тем больше преимуществ), так что, как говорится, на вкус и цвет… Если хотите ознакомиться с процессом использования ModelMaker получше — вам сюда [3].

Во всех деталях я описывать процесс создания компонента не буду (см., например, [21,22], где все это описано очень подробно). Сначала выберем пункт Component | New component и заполним поля — в качестве родителя укажем TRichEdit, название присвоим, как договаривались, RichEdit mt, а в пункте Palette Page установим вкладку Win32 (хотя никто не запрещает завести и свою собственную— она потом создастся автоматически). Получим заготовку, которая после добавления надлежащего кода будет выглядеть так (обратите внимание на дополнительные модули в uses) — листинг 8.1.

 Листинг 8.1. Модуль RichEditlnt                                               "j

unit RichEditlnt;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls;

type

TLanguageMessage = procedure(Sender: TObject; Lang: HKL)

of object; (процедура по событию смены языка I TRichEditlnt = class(TRichEdit) (регистрируем сам компонент) private

FOnLangChange: TLanguageMessage;

(регистрируем событие компонента I procedure LangRequest(var Mesg: TMessage);

message WM_INPUTLANGCHANGEREQUEST; procedure LangChange(var Mesg: TMessage); message WM_INPUTLAMGCHANGE; protected

( Protected declarations ) public

{ Public declarations ) published

property OnLangChange: TLanguageMessage read FOnLangChange write FOnLangChange; (свойство компонента отслеживать смену языка)

end;

procedure Register; implementation

procedure TRichEditlnt.LangRequest(var Mesg: TMessage); (если пришел запрос) begin

if assigned(FOnLangChange) then (на дальнейшую обработку его) FOnLangChange(self, Mesg.LParam);

inherited; end;

procedure TRichEditlnt.LangChange(var Mesg: TMessage); begin

{если только сообщение о смене языка) Mesg.Result := 1; {игнорируем) end;

procedure Register; begin

RegisterComponents(‘Win32′, [TRichEditlnt]); end;

end.

Для того чтобы проверить, все ли работает, временно добавить компонент к проекту RichEditText можно динамически. Для этого надо убрать с формы "настоящий" RichEdit и внести в модуль cdit.pas вот такие изменения (также, конечно, следует внести модуль RichEditlnt в предложение uses):

var

Forml: Т Forml ; RichEditlnt:TRichEditlnt;

implementation

($R *.dfm)

procedure TForml.FormCreate(Sender: TObject); begin

RichEditInt:=TRichEdi tInt.Create(Self); RichEditlnt.Parent:=Self; end;

После запуска на пустой форме появится квадратик редактора, в который вы можете вписать текст и убедиться, что теперь раскладка никак не зависит от передвижения курсора. Но, чтобы все было "по-взрослому", добавлять компонент можно было бы выбором из палитры на этапе конструирования формы, нам надо теперь зарегистрировать компонент в Delphi по-настоящему.

По умолчанию Delphi создала новый компонент в папке ..Delphi7\Lib, там мы его и оставим (на прилагаемом диске он находится в папке Glava8\2\Lib). Для регистрации необходимо, прежде всего, создать ресурсный файл с иконкой компонента в виде Bitmap 24×24. Для этого откроем наш любимый Image Editor, создадим новый ресурсный файл, а в нем — ресурс типа BITMAP 24×24 в 16 цветов и раскрасим его (я сделал иконку, похожую на обычную для RichEdit, только попроще). BITMAP (внимание!) обязательно нужно назвать именем класса, причем прописными буквами (tricheditint), а ресурсный файл — тем же именем, что и модуль компонента, но с расширением dcr (RichEditlnt.dcr). Сохранить ресурсный файл надо в той же папке, что и исходный текст модуля компонента.

Теперь можно регистрировать, причем имейте в виду, что при любых ошибках в этом процессе все нужно будет делать заново (так, если вам не понравится иконка, или она будет отображаться как-то не так, то недостаточно ее перерисовать в ресурсном файле, придется заново компилировать компонент, возможно, также и удалять зарегистрированный компонент из палитры и заново устанавливать). Запустите Delphi, войдите в меню Component | Install Component и перейдите на закладку Into New Package (впрочем, никто не мешает и включить его в существующий пакет, по умолчанию это dcluster.dpk). При создании нового пакета нельзя ему присваивать имя, совпадающее с именем модуля (оно у нас RichEditlnt.pas), иначе получите в ответ кучу ругательств на дельфийском языке. После ввода имени (вместе с путем к файлу) надо нажать на Ok, и в появившемся окне с новым пакетом (package) нажать сначала на Compile, а затем — если все пройдет успешно — на Install. Уверяю, что вся процедура даже по первому разу больше двух часов у вас не отнимет.

Все, компонент создан, зарегистрирован и находится на вкладке Win32 — можете наслаждаться обновленным RichEdit. Уберите из проекта RichEditText ранее вставленные строки для тестирования (в проекте на диске они закомментированы), поместите на форму RichEditint и изучите закладку Events в Object Inspector— у вас появилось ранее не существовавшее событие onLangChange, которое, кстати, можно использовать при надобности.

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии

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

  1. Илья says:

    Ещё вариант решения данной проблемы – использовать сообщение RichEdit:

    SendMessage(RichEdit.Handle, EM_SETLANGOPTIONS, 0, 0);

    подробное описание возможных ключей есть в MSDN, если указать lParam=0, то все расширенные функции RichEdit по подбору шрифтов и переключению языков будут заблокированы