Статьи

HowToDoIt

  1. Вступ Ця стаття показує, як легко можна написати відносно добре і приємне додаток віртуальної клавіатури....
  2. Використання коду
  3. малюємо інтерфейс
  4. Створення матриці Марковських зв'язків
  5. Проблеми, пов'язані з отриманням і втратою фокусу програми
  6. Налаштування
  7. Заключна частина
  8. Завантаження

Вступ

Ця стаття показує, як легко можна написати відносно добре і приємне додаток віртуальної клавіатури. Тут використані техніки програмування з використанням як WinApi так і .NET.

Так, це дійсно «ще один додаток віртуальної клавіатури» і звичайно, читаючи цю статтю, ви можете подумати: «Ей, хлопець! У нас є багато додатків віртуальної клавіатури! ». Звичайно, ви маєте рацію, але ... але інші віртуальні клавіатури не мають такої функції як підказка наступного найбільш імовірною літери, після введеної, і виконані вони не в стилі клавіатури зовні дуже нагадує мені улюблену розкладку мого лептопа :) Samsung R серії. Так, я можу почати розповідати, як мені не вистачало саме такої ось утилитки, але чесно кажучи, програма писалася чисто з міркувань «розважитися»! Результат титанічної виконаної роботи Ви бачите в цій статті.

На малюнку вище відразу ж показана робота програми. Тут ми бачимо 2 зелених підсвічування - це сигналізатори, які показують найбільш ймовірні букви (E, Z) щодо щойно введеної (D). Підсвічується саме так, тому що в словнику є слово "Codeproject" (бачимо вміст файлу словника "dictEN.txt" на задньому плані). Можна помітити, що підсвічуються букви з різною яскравістю. Це зроблено для того, щоб показати, яка з букв має велику ймовірність. Працює все це з використанням статистичного аналізу і так званої Марківського моделі, але про це трохи пізніше ...

Передісторія. Так народжувалася всесвіт

Я люблю читати книги і контент сайтів лежачи на спині, керуючи комп'ютером за допомогою миші. Не раз стикався з ситуацією, коли лежиш собі і дивишся цікавий фільм і тут, раптом чуєш дратівливий звук «Про - Ооо» з програми «QIP». Читаєш повідомлення і розумієш, що тобі таки потрібно відповісти на нього фразою на кшталт «Так, я сплю!», Але щоб зробити це, потрібно підняти своє тулуб у вертикальне положення (а адже так лінь ...). Про Боги, мені навіть страшно припустити, що може подумати про мене читач, але саме лінь наштовхнула мене на написання програми віртуальної клавіатури, а пізніше і виття цієї ось статті.

Використання коду

Проект написаний з використанням вільної і дійсно чудовою середовища розробки програмного забезпечення «SharpDevelop 3.0.0.3800». Чесно кажучи, я робив все можливе, щоб писати інтуїтивно зрозумілий і приємний код з достатньою кількістю коментарів (ІМХО). Я не застосовую «рефакторинг» C-стилю, на зразок цього:

  • Було: getSortedIndexesBubblesort1D
  • Переробили: gtStdIdxBblrt1D

Сподіваюся, читач буде відчувати себе комфортно, читаючи написаний мною вихідний код. Приступимо до нього ... Головними проблемами, з якими я зіткнувся, були:

  • Розробка інтерфейсу без будь-яких небажаних зрушень між кнопками
  • Створення матриці Марківського ланцюга залежностей
  • Управління фокусом додатки (а саме втрата йди відновлення фокуса в потрібний момент)

малюємо інтерфейс

Робота над першим пунктом не склала особливих труднощів. Час зробив своє. Трохи завзятості, трохи старань ... Загалом я просто взяв лінійку і виміряв кнопки мого власного лептопа :). Ні, на це пішло трохи часу, так як даний тип клавіатури має тільки кілька видів кнопок. Вони перераховані нижче в перерахуванні:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 enum KeyType {EMPTY, REGULAR, // like A ... Z LITTLE, // like F1-F12 TAB, SHIFTL, SHIFTR, CAPSLOCK, BACKSPACE , SLASH, // like \ ENTER, SPACEBAR, ALTL, ALTR, CTRLL, CTRLR};

Наступним кроком було створення зображень самих кнопок. Як Вам пощастило, що Ви не бачите найперших варіантів творіння моєї фантазії ... Насправді це було дуже складно для мене - зробити красиві кнопки :), але я подолав усі свої приховані страхи і зробив це. Після всього цього можна завантажувати зроблені зображення в пам'ять програми.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // loading of the keys 'images Image imgTmp = null; Image imgTab = Image. FromFile (pathTab); Image imgAltL = Image. FromFile (pathAltL); Image imgAltR = Image. FromFile (pathAltR); Image imgCtrlL = Image. FromFile (pathCtrlL); Image imgCtrlR = Image. FromFile (pathCtrlR); Image imgSlash = Image. FromFile (pathSlash); Image imgEnter = Image. FromFile (pathEnter); Image imgShiftL = Image. FromFile (pathShiftL); Image imgShiftR = Image. FromFile (pathShiftR); Image imgLittle = Image. FromFile (pathLittle); Image imgRegular = Image. FromFile (pathRegular); Image imgSpacebar = Image. FromFile (pathSpacebar); Image imgCapslock = Image. FromFile (pathCapslock); Image imgBackspace = Image. FromFile (pathBackspace);

Алгоритм відтворення клавіатури виглядає наступним чином:

  • Отрисовать кожну з кнопок у циклі (не руками ж пікселі обчислювати :))
  • Написати відповідний текст на кожній з клавіш.

Код відтворення клавіатури (метод DrawKeyboard (...)) має великий розмір, тому я не буду представляти його тут, але ви завжди можете проаналізувати його в доданому вихідному коді .

До речі, метод DrawKeyboard (...) використовує багатомірний масив keyboardStructure, який зроблений з елементів KeyType. Вміст даного масиву:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 PiliKeyboard. MainForm .KeyType [] [] keyboardStructure = { new KeyType [] {KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE}, new KeyType [] {KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. BACKSPACE, KeyType. REGULAR}, new KeyType [] {KeyType. TAB, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. SLASH, KeyType. REGULAR}, new KeyType [] {KeyType. CAPSLOCK, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. ENTER, KeyType. REGULAR}, new KeyType [] {KeyType. SHIFTL, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. REGULAR, KeyType. SHIFTR, KeyType. REGULAR}, new KeyType [] {KeyType. REGULAR, KeyType. CTRLL, KeyType. REGULAR, KeyType. ALTL, KeyType. SPACEBAR, KeyType. ALTR, KeyType. REGULAR, KeyType. CTRLR, KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE}, new KeyType [] {KeyType. LITTLE, KeyType. LITTLE, KeyType. LITTLE}};

Тут я «бігав» по ​​клавіатурі, зліва направо заповнюючи порядок проходження клавіш в багаторозмірний масив.

Створення матриці Марковських зв'язків

У цій частині також не було труднощів. Може бути через те що метод я розробив багато років тому, коли писав диплом в університеті. Ідея тривіальна. Перше, що необхідно зробити, це з'ясувати скільки разів кожен із символів (буква) зустрічається після досліджуваної (букви). Цією рутиною займається метод getProbabilityMatrix (...). В результаті, ми отримаємо матрицю відносних частот (скільки раз зустрічається символ після досліджуваного символу).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 private static float [,] getProbabilityMatrix (string sDictionaryPath, string sInterestSymbols) {long symbolsNum = 0; float [,] probabilityMatrix = new float [sInterestSymbols. Length, sInterestSymbols. Length]; probabilityMatrix = new float [sInterestSymbols. Length, sInterestSymbols. Length]; // reading all the dictionary into the buffer string sDict = ""; using (TextReader tr = new StreamReader (sDictionaryPath, Encoding. GetEncoding (1251))) {sDict = tr. ReadToEnd (); } // making all the symbols from dictionary as lower cased sDict. ToUpper (); for (int s = 0; s <sInterestSymbols. Length; s ++) {for (int i = 0; i <sDict. Length - 1; i ++) {if ((sDict [i]. ToString (). ToUpper () == sInterestSymbols [s]. ToString () .ToUpper ())) {for (int j = 0; j <sInterestSymbols. Length; j ++) {if (sDict [i + 1]. ToString () .ToUpper () == sInterestSymbols [j]. ToString () .ToUpper ()) {probabilityMatrix [s, j] + = 1; }} SymbolsNum ++; }}} Return probabilityMatrix; }

Наступним завданням є сортування цих даних, щоб отримати щось на зразок цього: «Після букви 'а' у нас можуть з'являтися літери 'б', 'р', 'м' і так далі». Вся ця робота виконується в методі getSequenceMatrix (...).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // method to get sequential matrix public static string [,] getSequenceMatrix (string sDictionaryPath, string sInterestSymbols) {if (! File. Exists (sDictionaryPath)) return null; float [,] probabilityMaxtrix = new float [sInterestSymbols. Length, sInterestSymbols. Length]; string [,] sequenceMatrix = new string [sInterestSymbols. Length, sInterestSymbols. Length]; float [] dataTmp = new float [sInterestSymbols. Length]; int [] indxsTmp = new int [sInterestSymbols. Length]; probabilityMaxtrix = getProbabilityMatrix (sDictionaryPath, sInterestSymbols); for (int r = 0; r <sInterestSymbols. Length; r ++) {for (int i = 0; i <sInterestSymbols. Length; i ++) {dataTmp [i] = probabilityMaxtrix [r, i]; } GetSortedIndexesBubblesort1D (ref dataTmp, ref indxsTmp); for (int c = 0; c <sInterestSymbols. Length; c ++) {sequenceMatrix [r, c] = sInterestSymbols [indxsTmp [sInterestSymbols. Length - c - 1]]. ToString () .ToUpper (); }} Return sequenceMatrix; }

Використовуючи цей метод, ми бачимо, що перший параметр введення - шлях до словника. Давайте розглянемо, що ж являють собою словники. Яким повинен бути ідеальний словник? Це повинен бути досить великий набір часто використовуваних слів. Виконавши цю умову, ми будемо отримувати більш високу точність визначення наступної букви після введеної. У будь-який час, ми можемо додати слово в «базу даних», просто відкривши «* .txt» файл. з директорії «./Словарі/». Ці файли є одними і можуть включати будь-які види скорочень, якщо необхідно. Для того, щоб визначити кількість підсвічуються символів була визначена змінна «numOfSuppKeysToShow». Значення за замовчуванням дорівнює числу «2», але не складе особливих труднощів встановити будь-яке інше значення.

Проблеми, пов'язані з отриманням і втратою фокусу програми

Сенс проблеми в тому, що під час натискання курсора миші по віртуальній клавіатурі наш додаток перехоплює фокус і в результаті, повідомлення про натискання клавіші відправляється йому ж самому. Для вирішення цієї проблеми я вирішив схитрувати. Поки курсор миші рухається над розфокусувати програмою віртуальної клавіатури, ми отримуємо дескриптор активного вікна. Потім, коли здійснюється вибір бажаної клавіші, ми вже знаємо, куди відправляти її код. Завдяки цьому «милиці» програма працює чудово.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // method to get handle of the target window while the PiliKeyboard inactive private void MainForm_MouseMove (object sender, MouseEventArgs e) {// when we move cursor under the PiliKeyboard // we are getting pointer to the target window IntPtr IntPtrTmp = CWinApi. GetForegroundWindow (); if ((IntPtrTmp! = hWndSource) && (IntPtrTmp. ToInt32 ()! = 0)) {hWndTarget = IntPtrTmp; } // updating input language var lCurrentLanguage = GetKeyboardLayoutLang (); switch (lCurrentLanguage) {case LANG. UK: sCurrentLanguage = UK; break; case LANG. RU: sCurrentLanguage = RU; break; case LANG. EN: sCurrentLanguage = EN; break; }}

Налаштування

Ідея настройки графічного інтерфейсу програми полягає в тому, що користувач має можливість самостійно змінити стиль шляхом створення нового або ж зміною існуючого набору зображень для кнопок. Є деякі обмеження! Розміри для кнопок повинна бути такими ж, як і у оригіналу. Нижче представлений приклад трохи божевільного і не зовсім вдалого інтерфейсу:

Нижче представлений приклад трохи божевільного і не зовсім вдалого інтерфейсу:

Заключна частина

У майбутніх версіях було б здорово реалізувати можливість створення списку можливих слів в залежності від раніше введених букв. В цьому випадку можна отримати збільшення швидкості друку, яка може бути порівнянна зі швидкістю введення за допомогою реальної клавіатури. Хочу зазначити, що програма була написана в обмежений час і пройшла дуже мало тестів. Таким чином може містити певну кількість «багів». Ціную Вашу можливу допомогу, коментарі та поради.
І ще, зміна мови клавіатури здійснюється вибором мови в панелі «біля годинок» :).

Завантаження

Завантажити демонстраційний проект

Завантажити вихідний код проекту

Яким повинен бути ідеальний словник?

Новости