|
Словари
Файлов: 72
Библиотека Документов: 309
Генератор хэшей Алгоритмов: 104
Новости интернета RSS-лент: 11
Подписка на новости Подписчиков: 795
Дополнительные сервисы
|
Создание программ с многоязыковым интерфейсомВведениеСогласитесь, что пользователю работать с вашей программой гораздо удобнее на своем родном языке, поэтому поддержка программой многоязыкового интерфейса крайне желательна. Более того - в последнее время многоязыковой интерфейс иногда становится одним из обязательных требований, выдвигаемых пользователями и заказчиками к разработчикам ПО. На рынке предлагается множество инструментов для добавления в программу возможности изменять интерфейс на любой язык. Но эти средства или являются платными, или приводят в существенному увеличению размеров программы, или являются достаточно сложными для применения, или используют для хранения текста не текстовые файлы (доступные для редактирования), а скомпилированные модули, что затрудняет внесение в них изменений. В данной статье мы рассмотрим принципы создания многоязыковой программы на языке 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-файлом параметры будут следующими:
Данный код для поддержки многоязыкового интерфейса (согласно листингу, который выдает 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-файле, будет подменена на строку, взятую из массива 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" диалогового окна в настройках программы:
Рисунок 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-файлов непостредственно в меню нашей программы:
Рисунок 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. Формирование диалогового окна на языке интерфейса,
выбранного пользователем Аналогично и при создании меню не нужно устанавливать заранее никакого текста (свойство "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. |