Восстановление паролей к хэшам

SAMInside
Скачать v2.6.3.0
Купить лицензию
Архив обновлен: 11.10.2008

PasswordsPro
Скачать v2.4.4.1
Купить лицензию
Архив обновлен: 17.11.2008


23.10.2008
Вышла EGB v1.4
Обсудить (комментариев: 44)

11.10.2008
Вышла PasswordsPro v2.4.4.1
Обсудить (комментариев: 7)

28.09.2008
Вышла EGB v1.3
Обсудить (комментариев: 16)

Словари
Файлов: 72

Библиотека
Документов: 309

Генератор хэшей
Алгоритмов: 104

Новости интернета
RSS-лент: 11

Подписка на новости
Подписчиков: 795

Дополнительные сервисы

Всего запросов: 15367245
Всего посещений: 2593555
Посетителей сегодня: 1622
Форум
Сообщений: 12634
Последнее: 02.12.2008, 14:55
  База хэшей
Найдено паролей: 806079
Всего хэшей в базе: 27173284
 

Создание программ с многоязыковым интерфейсом

©InsidePro Software

Введение

Согласитесь, что пользователю работать с вашей программой гораздо удобнее на своем родном языке, поэтому поддержка программой многоязыкового интерфейса крайне желательна. Более того - в последнее время многоязыковой интерфейс иногда становится одним из обязательных требований, выдвигаемых пользователями и заказчиками к разработчикам ПО.

На рынке предлагается множество инструментов для добавления в программу возможности изменять интерфейс на любой язык. Но эти средства или являются платными, или приводят в существенному увеличению размеров программы, или являются достаточно сложными для применения, или используют для хранения текста не текстовые файлы (доступные для редактирования), а скомпилированные модули, что затрудняет внесение в них изменений.

В данной статье мы рассмотрим принципы создания многоязыковой программы на языке C/C++ с использованием библиотеки MFC, которые вы сможете использовать в своих программах, создаваемых в пакете Microsoft Visual C++ 6.0 и выше. Если же вы используете другой язык программирования, то автор надеется, что из данной статьи вы сможете взять для себя принципы создания программы с многоязыковым интерфейсом и реализовать их на том языке, на котором пишете программы.

Описанная ниже технология многоязыковых программ использует текстовые файлы для хранения строк текста на различных языках. Эти файлы обычно имеют расширение *.LNG и выглядят таким образом:

;Note 1

[Translations]
...
10=OK
11=Cancel
12=About
...
;Note 2
156=Some text 1
157=Some text 2
158=Some text 3
...
;Note 3
513=Message 1
514=Message 2
515=Message 3
...

Листинг 1. Фрагмент LNG-файла.

Фактически, с точки зрения Windows данный LNG-файл является обычным INI-файлом, поэтому и чтение строк из него осуществляется обычной WinAPI-функцией GetPrivateProfileString(). Что же касается редактирования такого LNG-файла (или перевод его на какой-либо язык), то это очень легко сделать в любом текстовом редакторе, даже в Блокноте. При этом мы вполне успешно можем использовать в LNG-файлах и специальные строки "\n", "\t", т.к. после считывания строк (как будет показано ниже) данные сочетания символов будут автоматически заменены на стандартные ASCII-коды.

Итак, посмотрим на описание функции GetPrivateProfileString() в "WinAPI Programmer's Reference":

DWORD GetPrivateProfileString (
        LPCTSTR lpAppName,       // points to section name
        LPCTSTR lpKeyName,       // points to key name
        LPCTSTR lpDefault,       // points to default string
        LPTSTR lpReturnedString, // points to destination buffer
        DWORD nSize,             // size of destination buffer
        LPCTSTR lpFileName       // points to initialization filename
);

Листинг 2. Описание функции GetPrivateProfileString().

В случае работы с LNG-файлом параметры будут следующими:

  1. lpAppName - указатель на строку с именем секции. Автор использует такое название секции - "Translations", но вы, конечно же, можете называть ее по-своему;

  2. lpKeyName - указатель на название считываемого параметра. У нас строки будут просто пронумерованы - "1", "2", "3" и т.д., но вам никто не мешает ввести символьные названия параметров: "Label1", "MenuItem23" и т.д., только этот вариант приведет к усложнению кода и увеличению его размера;

  3. lpDefault - указатель на строку по умолчанию. Это параметр очень важен и про его использование вы узнаете ниже;

  4. lpReturnedString - указатель на буфер для считывания строки;

  5. nSize - размер буфера в байтах;

  6. lpFileName - имя LNG-файла.

Данный код для поддержки многоязыкового интерфейса (согласно листингу, который выдает Visual C++ 6.0) без текстовых строк для интерфейса по умолчанию занимает... менее 1 Кб. Но если все-таки размер кода очень критичен, то желающие без труда смогут его несколько уменьшить.

И нам осталось обговорить - как ваш программа будет сохранять выбранный язык интерфейса? Автор принципиально не использует хранение настроек своих программы в реестре - вместо этого используется хранение всех параметров в INI-файле, чтение и запись в который происходит функциями WinAPI GetPrivateProfileString() и WritePrivateProfileString(). Приведенные ниже примеры кода также используют эту технологию, но если вы в своей программе используете другой способ сохранения настроек программы, то также cможете переделать код "под себя".

Замечание: из-за совместимости с предыдущими версиями Windows размер INI-файлов (а, следовательно, и LNG-файлов) не должен превышать 64 Кб. Поэтому, если у вас много строк, то имеет смысл написать свою собственную функцию, аналогичную GetPrivateProfileString(), но считывающую строки из файлов большего размера.

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

Инициализация

Итак, приступим к созданию программы с многоязыковым интерфейсом.

        ...
        // Максимальное кол-во строк в LNG-файле
        #define MAX_STRINGS 100

        // Основной массив для хранения строк
        // текста на различных языках
        CString sText[MAX_STRINGS];

        // Путь к текущему файлу настроек программы (INI-файл)
        char szFileINI[MAX_PATH];

        // Путь к каталогу с программой (т.е. к текущему каталогу)
        char szCurrentDir[MAX_PATH];
        ...

Листинг 3. Код инициализации для поддержки многоязыкового интерфейса.

        ...
        int i = GetCurrentDirectory(255, (char *)szCurrentDir);
        if (szCurrentDir[i - 1] == '\\')
                szCurrentDir[i - 1] = 0;
        sprintf(szFileINI, "%s\\MyProgram.INI\0", szCurrentDir);
        ...

Листинг 4. Инициализация путей к файлу с настройками и к текущей директории
(данный код вставляется в самом начале программы еще до создания главного окна).

Также создаем и массив с текстовыми строками, которые относятся к интерфейсу программы - т.е. пункты меню, сообщения программы и надписи в диалоговых окнах. Данные строки должны быть на том языке, который принят в программе по умолчанию:

char *sTextOriginal[MAX_STRINGS] = {
        "", //0
        "&File", //1
        "E&xit", //2
        "&View", //3
        "&Options\\tF9", //4
        "&?", //5
        "&About...", //6
        ...
}

Листинг 5. Файл со строками, принятыми в программе по умолчанию.

Заполнение массива строк

Далее мы считываем все строки из нашего LNG-файла в массив sText[], т.к. гораздо быстрее обращаться к тексту, уже хранящемуся в памяти нашей программы, чем каждый раз вызывать функцию GetPrivateProfileString() для считывания нужной строки текста. Да и код считывания всех строк текста получается минимальным, т.к. происходит в цикле.

Данный метод еще хорош и тем, что автоматически обрабатываются несколько ошибочных ситуаций, которые могут возникнуть у пользователя:
- после изменения языка интерфейса и выходе из программы нужный LNG-файл был удален;
- в LNG-файле отсутствуют перевод какой-либо строки (или нескольких строк);
- и т.д.

В любом из этих случаев строка, отсутствующая в LNG-файле, будет подменена на строку, взятую из массива sTextOriginal, т.е. на тот язык, который принят в программе "по умолчанию":

        ...
        char szLang[MAX_PATH], szTemp[512], Bufer[1024], szParam[32];

        // Считываем из файла настроек выбранный пользователем
        // языковой файл, "Italian", к примеру.
        // При этом в качестве LNG-файла "по умолчанию"
        // устанавливаем нужный нам файл ("English" в нашем примере):
        GetPrivateProfileString("Common", "Language", "English", szLang, MAX_PATH, szFileINI);

        // Формируем полный путь к текущему LNG-файлу
        sprintf(szTemp, "%s\\%s.lng\0", szCurrentDir, szLang);

        for (int i = 0; i < MAX_STRINGS; i++) // Читаем ВСЕ фразы в массив sText[]
        {
                // Формируем имена параметров, а фактически - номера строк в LNG-файле
                sprintf(szParam, "%d\0", i);

                // Читаем очередную строку из LNG-файла
                GetPrivateProfileString("Translations", szParam, sTextOriginal[i], Bufer, 512, szTemp);

                // Т.к. функция GetPrivateProfileString() не может читать
                // строковые данные сразу в объект CString, то приходится
                // делать дополнительное присвоение:
                sText[i] = Bufer;

                // Меняем подстроки "\t" и "\n" из LNG-файла
                // в стандартный формат языка C:
                sText[i].Replace("\\t", "\t");
                sText[i].Replace("\\n", "\n");
        }
        ...

Листинг 6. Чтение строк из LNG-файла в массив sText[].

Что ж, теперь массив sText[] содержит весь интерфейс нашей программы на том языке, который выбрал пользователь. Теперь создадим код, который позволит управлять списком LNG-файлов.

Работа со списком LNG-файлов через диалоговое окно

Рассмотрим ситуацию, когда нам необходимо сформировать список LNG-файлов в объекте "Combo Box" диалогового окна в настройках программы:

Список LNG-файлов

Рисунок 1. Список LNG-файлов в объекте "Combo Box".

Для этого, используя редактор ресурсов, в нужном диалоге создаем объект "Combo Box" со стилем "Drop List" и с включенной сортировкой (по желанию). Назначаем ему имя - IDC_COMBO1 (для примера). А затем в код инициализации диалога (обычно используется обработчик события WM_INITDIALOG) вставляем следующий фрагмент кода:

        ...
        void *hSearch;
        WIN32_FIND_DATA wfd;
        char szFile[MAX_PATH], szLang[256];

        // Если пользователь менял текущую директорию,
        // вызывая функции открытия или сохранения файлов,
        // то ее необходимо вернуть принудительно
        SetCurrentDirectory(szCurrentDir);

        // Поиск всех LNG-файлов в текущей директории
        hSearch = FindFirstFile("*.lng", &wfd);

        // Считываем текущий язык интерфейса из настроек программы
        GetPrivateProfileString("Common", "Language", "English", szLang, 256, szFileINI);

        if (hSearch != INVALID_HANDLE_VALUE)
        {
                do
                {
                        strcpy(szFile, wfd.cFileName);

                        // "Отсекаем" расширение файла - ".LNG"
                        szFile[strlen(szFile) - 4] = 0;

                        // Добавляем LNG-файл к списку
                        ((CComboBox *)GetDlgItem(IDC_COMBO1))->AddString(szFile);
                }
                while (FindNextFile(hSearch, &wfd));
        }

        FindClose(hSearch);

        // Добавляем язык по умолчанию
        if (((CComboBox *)GetDlgItem(IDC_COMBO1))->FindString(0, "English") == CB_ERR)
                ((CComboBox *)GetDlgItem(IDC_COMBO1))->AddString("English");

        // Выбираем текущий язык интерфейса
        nLanguage = ((CComboBox *)GetDlgItem(IDC_COMBO1))->SelectString(0, szLang);

        // Если он отсутствует в списке, то устанавливаем язык по умолчанию
        if (nLanguage == CB_ERR)
        {
                ((CComboBox *)GetDlgItem(IDC_COMBO1))->SelectString(0, "English");
                nLanguage = 0;
        }
        ...

Листинг 7. Код для инициализации списка LNG-файлов.

Примечание: переменная "nLanguage" имеет тип "int" и является членом класса диалогового окна или же просто статической переменной.

При выходе из диалога и нажатии кнопки "OK" сохраняем выбранный пользователем язык интерфейса и сообщаем о необходимости перезапуска программы, если нужно:

        ...
        // Теущий язык интерфейса был изменен?
        if (((CComboBox *)GetDlgItem(IDC_COMBO1))->GetCurSel() != nLanguage)
        {
                // Да, был изменен
                CString s;
                GetDlgItemText(IDC_COMBO1, s);

                // Сохраняем выбранный язык интерфейса
                WritePrivateProfileString("Common", "Language", s, szFileINI);

                // Выдаем сообщение о необходимости перезапуска программы
                AfxMessageBox(sText[10]);
        }
        ...

Листинг 8. Код для функции OnOK().

Работа со списком LNG-файлов через пункты меню

Теперь рассмотрим ситуацию, когда мы хотим сформировать список LNG-файлов непостредственно в меню нашей программы:

Список LNG-файлов

Рисунок 2. Список LNG-файлов в меню нашей программы.

Здесь весь код желательно поместить сразу в функцию создания главного окна - CMainFrame::OnCreate(). При этом мы будем добавлять LNG-файлы в виде пунктов меню с нужными нам идентификаторами. Для этого в файл "resource.h" добавим такую запись:

...
#define ID_LANGUAGE                 500
...

Листинг 9. Фрагмент файла "resource.h".

Добавляемые элементы меню будут иметь следующие идентификаторы - 500, 501, 502, 503 и т.д. Это позволит нам обрабатывать события, которые возникают, когда пользователь выбирает один из этих пунктов меню. Само собой, следующий используемый идентификатор должен иметь номер 550, к примеру, или 600, чтобы в дальнейшем не возикло ошибок из-за совпадений идентификаторов.

        ...
        int i;
        char szLang[MAX_PATH], szTemp[512], Bufer[1024], szParam[32];

        // Получаем ссылку на тот элемент главного меню
        // программы, где хотим отобразить список LNG-файлов
        CMenu* pMenu = GetMenu();
        CMenu* cmSub = pMenu->GetSubMenu(0);

        // И создаем новое Popup-меню, где будет список наших LNG-файлов
        m_pMenuNew = new CMenu;
        m_pMenuNew->CreatePopupMenu();

        void *hSearch;
        WIN32_FIND_DATA wfd;
        char szFile[MAX_PATH];

        hSearch = FindFirstFile("*.lng", &wfd); // Поиск всех LNG-файлов
        GetPrivateProfileString("Common", "Language", "English", szLang, 256, szFileINI);

        // Первый добавляемый пункт меню - это LNG-файл по умолчанию
        nCurrentLanguage = ID_LANGUAGE;
        m_pMenuNew->AppendMenu(MF_STRING, ID_LANGUAGE, "English");
        nLanguages = 1; // Счетчик количества LNG-файлов

        if (hSearch != INVALID_HANDLE_VALUE)
        {
                do
                {
                        strcpy(szFile, wfd.cFileName);
                        szFile[strlen(szFile) - 4] = 0;
                        if (!strcmp(szFile, "English"))
                                continue;

                        // Если добавляемый LNG-файл совпадает с тем,
                        // который выбрал пользователь, то запоминаем его
                        if (!strcmp(szFile, szLang))
                                nCurrentLanguage = ID_LANGUAGE + nLanguages;

                        m_pMenuNew->AppendMenu(MF_STRING, ID_LANGUAGE + nLanguages, szFile);
                        nLanguages++;
                }
                while (FindNextFile(hSearch, &wfd));
        }

        FindClose(hSearch);

        // Теперь добавляем наше Popup-меню в главное меню программы
        cmSub->InsertMenu(0, MF_STRING | MF_POPUP | MF_BYPOSITION, (UINT)m_pMenuNew->m_hMenu, "");

        // И ставим флажок напротив выбранного пользователем LNG-файла
        pMenu->CheckMenuItem(nCurrentLanguage, MF_CHECKED);

        ...
        // Загрузка строк в массив sText[]
        ...

        ...
        // Обновление всех пунктов меню на нужный язык
        ...

        // Не забываем изменить и название пункта меню с нашим списком LNG-файлов
        cmSub = pMenu->GetSubMenu(0);
        cmSub->ModifyMenu(0, MF_BYPOSITION | MF_STRING, 0, (LPCTSTR)sText[7]);
        ...

Листинг 10. Добавление списка LNG-файлов к меню программы.

Теперь добавляем обработчик событий для изменения языка интерфейса в функцию CMainFrame::OnCmdMsg():

        ...
        if (nCode == 0)
        {
                if (nID != (UINT)nCurrentLanguage)
                {
                        // Пользователь выбрал другой LNG-файл
                        // и мы определяем его ID

                        for (UINT i = ID_LANGUAGE; i < (ID_LANGUAGE + (UINT)nLanguages); i++)
                        {
                                if (i == nID)
                                {
                                        CMenu* pMenu = GetMenu();
                                        pMenu->CheckMenuItem(nCurrentLanguage, MF_UNCHECKED);

                                        // Ставим флажок напротив нового пункта меню
                                        pMenu->CheckMenuItem(i, MF_CHECKED);
                                        nCurrentLanguage = i;
                                        CString szTemp;
                                        pMenu->GetMenuString(i, szTemp, MF_BYCOMMAND);

                                        // Сохраняем язык нового интерфейса
                                        WritePrivateProfileString("Common", "Language",                                              szTemp, szFileINI);
                                        AfxMessageBox(sText[10]);
                                        break;
                                }
                        }
                }
        }
        ...

Листинг 11. Сохранение выбранного пользователем языка интерфейса.

Примечание: переменные "nCurrentLanguage" и "nLanguages" имеют тип "int" и являются либо статическим переменными, либо членами класса CMainFrame.

Использование многоязыкового интерфейса

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

        ...
        AfxMessageBox(sText[12]);
        ...

Листинг 12. Выдаем сообщение на языке интерфейса, выбранного пользователем.

Для использования выбранного языка интерфейса в диалоговом окне необходимо перед его отображением установить нужный текст для каждого из элементов управления (объекты "Static Text", "Group Box", "Check Box" и др.). Естественно, при создании данного диалога в редакторе ресурсов никакого текста заранее устанавливать не нужно, чтобы не увеличивать размер программы.

        ...
        // Список идентификаторов объектов, используемых в диалоговом окне
        // с соответствующими номерами строк из массива sText[]
        static unsigned short nTemp[] = {IDOK, 10, IDCANCEL, 11, IDC_STATIC1, 69}; // И т.д.
        for (int i = 0; i < (sizeof(nTemp) / sizeof(unsigned short)); i += 2)
                SetDlgItemText(nTemp[i], sText[nTemp[i + 1]]);
        ...

Листинг 13. Формирование диалогового окна на языке интерфейса, выбранного пользователем
(вставляется в код инициализации диалогового окна, т.е. в обработчик события WM_INITDIALOG).

Аналогично и при создании меню не нужно устанавливать заранее никакого текста (свойство "Caption"), или же чисто для удобства работы можно во все пункты меню установить одинаковый текст, например "A":

Создание меню

Рисунок 3. Создание меню для программы с многоязыковым интерфейсом.

А затем при создании главного окна программы (например, в функции CMainFrame::OnCreate()) вставляем следующий код для установки нужного текста во все пункты меню:

        ...
        CMenu* pMenu = GetMenu();
        ...
        // Список идентификаторов меню с соответствующими номерами строк
        static unsigned short nMenu[] = {ID_APP_EXIT, 27, ID_APP_ABOUT, 51, ID_HELP, 205}; // И т.д.
        for (int i = 0; i < (sizeof(nMenu) / sizeof(unsigned short)); i += 2)
                pMenu->ModifyMenu(nMenu[i], MF_BYCOMMAND | MF_STRING, nMenu[i],
                                 (LPCTSTR)sText[nMenu[i + 1]]);
        ...
        // Номера строк для пунктов меню верхнего уровня ("Файл", "Вид" и пр.)
        static unsigned short nItems[] = {20, 12, 204, 13, 18, 50}; // И т.д.
        for (int i = 0; i < (sizeof(nItems) / sizeof(unsigned short)); i++)
                pMenu->ModifyMenu(i, MF_BYPOSITION | MF_STRING, 0, (LPCTSTR)sText[nItems[i]]);
        ...

Листинг 14. Изменение меню на язык интерфейса, выбранный пользователем.

Файлы с примерами

Здесь вы можете скачать архив с двумя проектами, созданных в Visual C++ 6.0 для демонстрации вышеприведенных принципов создания многоязыковых программ.

Заключение

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


Copyright ©2003-2008, InsidePro Software. All rights reserved.
Tuesday, 02nd of December 2008, 15:27:04.