В наших проектах есть такое требование – логин и пароль пользователя к БД должны быть введены в английской раскладке клавиатуры. Ну так исторически сложилось. А чтобы голову пользователя не напрягать таким ограничением, перед вводом пароля или логина раскладка клавиатуры принудительно переключается на английскую (а потом возвращается та, которая была).
И есть ещё такая полезняшка: при завершении работы, приложение сохраняет пользовательскую раскладку, а при следующем запуске – восстанавливает её.
Раньше это делалось с помощью пары функций GetKeyboardLayoutName и LoadKeyboardLayout. Первая возвращает строку, а вторая – активирует по строке. Однако в Windows 7 (в отличии от Windows XP) вторая функция сильно и заметно для пользователя тормозит (от 2 до 7 секунд, кто-то пишет до 10).
Не буду долго расписывать поиски решения, там всё (ну почти) тривиально. Просто чтобы не писать код самому, сделал поиск по библиотеке JCL и нашёл модуль JclLocales.
Получилось примерно следующее:
uses JclLocales; .. type TAppDataModule = class(TDataModule) .. strict private class var FKeyboradLocalesList: TJclKeyboardLayoutList; class var FSavedKL: HKL; class constructor Create; class destructor Destroy; public class procedure SaveKLAndSelectEnglish; class procedure RestoreKL; .. end; class constructor TAppDataModule.Create; begin FKeyboradLocalesList := TJclKeyboardLayoutList.Create; end; class destructor TAppDataModule.Destroy; begin FreeAndNil(FKeyboradLocalesList); end; class procedure TAppDataModule.SaveKLAndSelectEnglish; var LKbLt: TJclKeyboardLayout; begin Assert(FSavedKL = 0); // для соблюдения парности вызовов, иначе надо делать стек FSavedKL := FKeyboradLocalesList.ActiveLayout.Layout; LKbLt := FKeyboradLocalesList.LayoutFromLocaleID[$409]; if Assigned(LKbLt) then LKbLt.Activate([klActivate, klSubstituteOK]); end; class procedure TAppDataModule.RestoreKL; begin Assert(FSavedKL <> 0); FKeyboradLocalesList.ItemFromHKL[FSavedKL].Activate([klActivate, klSubstituteOK]); FSavedKL := 0; end;
И использование где-то в коде:
TAppDataModule.SaveKLAndSelectEnglish; try with TfrmChangePassword.Create(AOwner) do try Mode := NewLgn; DB := ADB; edLogin.Text := ALogin; ActiveControl := edLogin; Result := ShowModal = mrOk; if Result then begin ALogin := edLogin.Text; APassword := edNewPassword.Text; end; finally Free; end; finally TAppDataModule.RestoreKL; end;
Ну примерно так. Спасибо за внимание :)
5 коммент.:
Подскажите, с какой целью в описании класса используется ключевое слово CLASS?
И в чем отличие от такого? (если вышеприведенный код написан на Delphi)
type
TAppDataModule = class(TDataModule)
..
strict private
FKeyboradLocalesList: TJclKeyboardLayoutList;
FSavedKL: HKL;
сonstructor Create;
destructor Destroy;
public
procedure SaveKLAndSelectEnglish;
procedure RestoreKL;
..
end;
Анонимный, в моём примере нет необходимости создавать экземпляр класса.
Т.е. допустимо и безопасно обращаться к методам от имени класса вот так: TAppDataModule.SaveKLAndSelectEnglish.
В Вашем же примере, для доступа к методам SaveKLAndSelectEnglish и RestoreKL необходимо создать экземпляр класса TAppDataModule. Например:
var
..ADM: TAppDataModule;
..ADM := TAppDataModule.Create(..);
..ADM.SaveKLAndSelectEnglish;
Классовые переменные и методы можно расценивать как способ группировки обычных (глобальных) переменных и процедур, а класс-конструктор/деструктор в старых версиях Delphi заменяется на секции initialization и finalization.
Конкретно в примере, приведённом в заметке - если экземпляр TAppDataModule создаётся всегда, то мудрить не обязательно. Просто в моём случае - класс объявлен в одном модуле (и этот модуль доступен всем проектам), а экземпляр создаётся на уровне другого модуля (для каждого проекта - это свой другой модуль) - вот делать зависимость на этот другой модуль я не захотел.
Спасибо! Как раз недавно прикручивал к логин диалогу индикатор нажатого Caps lock-a. А про раскладку и не думал. (впрочем у наших пользователей редко есть русский язык).
А что будет если у юзера нет английской раскладки? Ничего или Exception?
p.s. Кстати, прямо глядя на код возникает риторический вопрос - почему бы не вынести это в один законченный класс TdelphiNotesKbLayoutStateSaver (иначе говоря, и зачем там TDataModule)
Aleksey Timohin
> если у юзера нет английской раскладки?
Ничего не будет. FKeyboradLocalesList.LayoutFromLocaleID[$409] вернёт nil и ниже есть проверка if Assigned
> почему бы не вынести это в один законченный класс?
Флаг Вам в руки :) У меня в этом необходимости пока нет, для своих проектов я вижу это именно на уровне основного датамодуля.
> Флаг Вам в руки :) У меня в этом необходимости пока нет, для своих проектов я вижу это именно на уровне основного датамодуля.
Спасибо Николай, тут весь код в принципе уже есть в виде отдельного класса. Я из чистого любопытства спросил - всё же Принцип Единственной Ответственности и всё такое.
Отправить комментарий