Як створити mfc-додаток з підтримкою декількох розширень файлів, mfc, статті, програмування

Отже, ми будемо створювати програму, яка:

  1. Вміє відкривати і зберігати файли декількох форматів, при цьому в діалозі збереження можна буде вибрати один з наступних типів:
    1. Всі підтримувані типи (* .ex1; * .ex2; * .ex3)
    2. Перший тип (* .ex1)
    3. Другий тип (* .ex2)
    4. Третій тип (* .ex3)
    5. Всі файли (*. *)
    У діалозі збереження будуть все ті ж варіанти, крім самого першого.
  2. Реєструє на себе відкриття файлів з такими розширеннями по подвійному кліку.
  3. За бажанням, при реєстрації створює або НЕ створює запис в контекстному меню "Створити".
  4. Кожному із зазначених трьох розширень зіставляє свою іконку.

Приступаємо до виконання







Отже, запускаємо AppWizard, називаємо наш додаток SDI. На першому кроці вибираємо тип - Single document. ставимо галочку Document / View architecture support. англійська мова. Другий крок. бази даних не потрібні, ставимо None. Третій. контейнери не потрібні, None. Четвертий. Тут ставимо галочки за бажанням, я залишив все як є за замовчуванням. Натискаємо кнопку Advanced і вводимо параметри нової програми:

Далі можна відразу тиснути Finish. більш-менш важливих налаштувань далі немає. Компілюємо наш чистий проект, і запускаємо його. Можна нічого в програмі не робити, реєстрація файлів відбувається автоматично при запуску.

Зараз я хочу зробити ще один відступ, присвячене більш докладному розбору того, що ми тільки що вписували. Тут буде розказано, як файли реєструються в системі, і що при цьому пишеться в реєстр. Якщо кому-то вже все це відомо, наступний параграф можна пропустити.

Реєстрація файлів в системі

Я припускаю тут, що читач вже знайомий з тим, що таке реєстр і як з ним працювати. Якщо ні - цьому присвячено досить багато літератури, тому розповідати тут основи я не буду.

Для того, щоб Windows вміла працювати з файлом, задавати йому іконку, міняти його контекстне мемню і т.д. необхідно завести в реєстрі ідентифікатор файлу. Саме його ми вказали в полі File type ID. Це по суті назва нашого типу файлу з точки зору системи. Всі типи файлів зберігаються в реєстрі в галузі [HKEY_CLASSES_ROOT] під своїм же назвою. Давайте подивимося в гілку нашого нового типу файлу: [HKEY_CLASSES_ROOT \ SDI.Document]. У цій галузі параметр (За замовчуванням) (в англійських версіях Windows - (Default)) містить назву типу файлу яким він постає користувачеві: відкрийте будь-яку папку на диску, викличте контекстне меню, і в ньому підменю Створити. ви побачите серед інших якийсь "Unnamed file" - нічого не нагадує? Саме це значення ми вписали в поле File new name (long name). і воно ж перебуває в параметрі (За замовчуванням) гілки [HKEY_CLASSES_ROOT \ SDI.Document].

Добре, йдемо далі. Розгортаємо цю гілочку і бачимо два підрозділи: DefaultIcon і shell .Второй нам не потрібен, а от перший в тому ж параметрі (За замовчуванням) за замовчуванням містить іконку, зіставлення даного типу файлу.

Що ж, як описується тип файлу, ми розібрали. Залишилося дізнатися ще трохи: як же тип файлу зв'язується з розширенням? А дуже просто. У тому ж розділі реєстру [HKEY_CLASSES_ROOT] перераховані всі розширення - це розділи, що починаються з точки. Знайдемо там і наше розширення - .ex1 (точка важлива!). У параметрі (За замовчуванням) цього розділу ми бачимо якраз ідентифікатор типу, до якого прикріплено дане розширення, а саме - наш SDI.Document.

І залишилося вказати на останню деталь, яка нам знадобиться в майбутньому: це наявність підрозділу ShellNew в розділі [HKEY_CLASSES_ROOT \ .ex1]. Саме через наявність цього підрозділу у нас і з'являється новий тип файлу в меню Створити. Спробуйте видалити або перейменувати ShellNew. і ви побачите, що наш "Unnamed file" зникне з цього меню.

Отже тепер ми знаємо сенс трьох з семи заповнених полів. Решта поля не мають відношення до реєстру, а стосуються тільки самої програми. А саме: Main frame caption - це просто заголовок основного вікна програми; Filter name - фільтр, що з'являється в діалогах відкриття і збереження файлів; Doc type name - ім'я нашого типу з точки зору програми (якби у нас програма підтримувала кілька різних типів документів, то при створенні нового виводився б діалог з пропозицією вибрати тип створюваного документа. Doc type name - це назва, що з'являється в тому списку. В нашому додатку його не буде); File new name (short name) - ім'я нового докумемнта за замовчуванням (в багатовіконних додатках при створенні нового порожнього документа його ім'я створюється з цього параметра плюс порядковий номер).

Тепер ми повністю знаємо сенс всіх тих таємничих маніпуляцій, що здійснювали раніше. Можна приступати до процесу модифікації програми відповідно до розв'язуваної завданням.

Додавання кількох розширень

На даний момент програма підтримує тільки одне розширення - EX1. Для того, щоб додати потрібні нам EX2 і EX3. відкриємо рядкові ресурси нашої програми і подивимося на рядок з ідентифікатором IDR_MAINFRAME. Я тут не буду досконально описувати формат цього рядка - бо більша частина її - це ті рядки, про які я вже розповів, якщо щось неясно - вперед в MSDN. ) Нам зараз важлива наступна частина цього рядка: \ nSDI Files (* .ex1) \ n.ex1 \ n. Змінимо її наступним чином: \ nSDI Files (* .ex1; * .ex2; * .ex3) \ n.ex1; .ex2; .ex3 \ n. откомпіліруем і запустимо наш додаток. У вікнах відкриття і збереження ми бачимо ці самі фільтри і вони працюють! Невже так просто? На жаль, не зовсім. Подивіться в реєстр: наш тип файлу пов'язаний з вельми цікавим розширенням .ex1; .ex2; .ex3. Чи не занадто-то це схоже на правильну реєстрацію потрібних нам типів файлів.







І ось тепер починаються танці з бубном, які повинні привести нас до того, що ми хочемо отримати.

Отже, у нас проблеми з реєстрацією розширень. За реєстрацію відповідає метод CWinApp. RegisterShellFileTypes (). Однак зайшовши всередину цього методу, ми бачимо, що єдине осмислене дію, яке він виконує, це виклик

m_pDocManager -> RegisterShellFileTypes (bCompat);

А ось уже метод CDocManager. RegisterShellFileTypes (BOOL bCompat) якраз і виконує всю роботу по реєстрації форматів. Значить, саме його нам треба перевизначати.

Створюємо новий клас CDocManagerEx. отнаследованний від CDocManager. Тепер у файлі SDI.cpp додаємо рядок

m_pDocManager = new CDocManagerEx;

Тепер нам потрібно перевизначити метод CDocManagerEx. RegisterShellFileTypes (BOOL bCompat). Найпростіший спосіб зробити нормальну реалізацію - це скопіювати її з вихідних MFC.

Увага. в MFC 4.2 в цьому методі є невеликий глючок. У пріаттаченном проект він виправлений (нічого серйозного, просто реєстрація файлів відбувалася не зовсім при тих умовах, що мала б відбуватися, зважаючи на логіку коду). У MFC 7.1 ця помилка виправлена.

У цьому методі використовуються кілька констант і функцій, які в нашому файлі не визначені, тому їх треба перенести з того ж файлу, звідки ми взяли вищенаведений код (це файл docmgr.cpp). Ці визначення знаходяться в самому початку цього файлу. Для нашого коду потрібні всі константи і функція _AfxSetRegKey (.). Крім того, потрібно підключити файл afxpriv.h (я зробив це в файлі stdafx.h), інакше код не компілюватиметься.

Почнемо тепер розбиратися з нашої RegisterShellFileTypes. Цей метод для кожного зареєстрованої в додатку шаблону документів (він у нас всього один) створює в реєстрі запис з відповідним ідентифікатором, реєструє відкриття файлів на свій EXE-файл і встановлює зв'язок між розширенням файлу і типом. Якщо bCompat == TRUE. то попутно ще реєструються на той же EXE-файл команди друку, додається запис про іконці для даного типу файлів і прописується ключик ShellNew для появи нового типу файлу в підменю Створити.

Для наших цілей найзручнішим варіантом буде створити окремий метод для реєстрації одного конкретного розширення. Оголосимо його як

bool RegisterSingleFileType (CDocTemplate * pTemplate. CString FilterExt,
int IconNum. BOOL bCompat. BOOL bShellNew);

Тут я ввів такі параметри:
  • pTemplate. шаблон документа
  • FilterExt. розширення файлу (.ex1)
  • IconNum. номер іконки для цього розширення
  • bCompat. просто переносимо цей параметр з RegisterShellFileTypes
  • bShellNew. прапорець - чи треба створювати запис в меню Створити

У цей метод можна перенести мало не весь вміст методу RegisterShellFileTypes. потрібно тільки прибрати цикл, перебирають всі шаблони документа, і додати умова перевірки прапора bShellNew. Далі, вставляємо там код, що модифікує ім'я типу файлу (це потрібно, щоб зробити різні іконки), ще пара косметичних доповнень, і метод готовий.

Тепер залишилося підправити RegisterShellFileTypes. Ми залишаємо там цикл по шаблонах документів (на майбутнє, раптом знадобиться.), Вставляємо инкрементируются лічильник номера іконки і в цьому циклі викликаємо RegisterSingleFileType з потрібними параметрами. Залишилося тільки додати ще дві іконки для потрібних типів файлів, та вирішити для себе - чи потрібно додавати цей тип файлів в меню Створити. Я вирішив цю запис не додавати. Якщо кому-то вона дуже потрібна, я думаю, читач з цієї архіскладною завданням впорається самостійно ;-)

На даний момент практично всі завдання вже виконані. Залишилася дрібничка - підправити діалоги відкриття і збереження файлів так, щоб вони дозволяли вибирати потрібні нам файлові фільтри.

Спочатку ми підправимо ще один метод - CSingleDocTemplate. MatchDocType (). У нашому варіанті завдання це, в общем-то, не обов'язково. Метод MatchDocType () відповідає за визначення типу файлу, що відкривається. У нас тип один (не плутати з розширенням!), Тому особливої ​​різниці не буде. Але якщо хтось захоче модифікувати програму так, щоб вона підтримувала кілька типів документів, то цей метод буде корисний.

Для цього нам потрібно створити свій клас, отнаследованний від CSingleDocTemplate. Назвемо його CMyDocTemplate. пропишемо використання конструктора базового класу і додамо метод

virtual Confidence MatchDocType (LPCTSTR lpszPathName. CDocument * rpDocMatch);

Реалізацію беремо з вихідних MFC і підправляє так, щоб розширення файлу порівнювався ні з шаблонної рядком виду ".ex1; .ex2; .ex3". а окремо з кожним з розширень.

Для того, щоб проект Компільо, необхідно оголосити в файлі MyDocTemplate.cpp функцію

BOOL AFXAPI AfxComparePath (LPCTSTR lpszPath1. LPCTSTR lpszPath2);

Далі, потрібно додати заголовки shlwapi.h (найкраще це зробити в файлі stdafx.h) і підключити бібліотеку shlwapi.lib. Після всього цього йдемо в файлик SDI.cpp. пишемо там на початку

а в методі InitInstance () шукаємо рядки

CSingleDocTemplate * pDocTemplate; pDocTemplate = new CSingleDocTemplate (
IDR_MAINFRAME,
RUNTIME_CLASS (CSDIDoc),
RUNTIME_CLASS (CMainFrame), // main SDI frame window
RUNTIME_CLASS (CSDIView));
AddDocTemplate (pDocTemplate);

і замінюємо CSingleDocTemplate на CMyDocTemplate.

Так, відмінно. Трошки передохнём, і знову в дорогу. Уже зовсім трохи залишилося.

За роботу з діалогом вибору файлу відповідає метод CDocManager. DoPromptFileName (). Клас CDocManagerEx у нас вже є, тому прямо в ньому переобумовленої метод

virtual BOOL DoPromptFileName (CString fileName. UINT nIDSTitle,
DWORD lFlags. BOOL bOpenFileDialog. CDocTemplate * pTemplate);

if (! bOpenFileDialog) // In "Save As." dialog we must remove
strFilter. Empty (); // the first filter - "SDI Files (* .ex1; * .ex2; * .ex3)"

strFilter + = "SDI-1 Files (* .ex1)";
strFilter + = (TCHAR) '\ 0';
strFilter + = _T ( "* .ex1");
strFilter + = (TCHAR) '\ 0';

strFilter + = "SDI-2 Files (* .ex2)";
strFilter + = (TCHAR) '\ 0';
strFilter + = _T ( "* .ex2");
strFilter + = (TCHAR) '\ 0';

strFilter + = "SDI-3 Files (* .ex3)";
strFilter + = (TCHAR) '\ 0';
strFilter + = _T ( "* .ex3");
strFilter + = (TCHAR) '\ 0';

Для того, щоб цей метод працював, нам ще необхідно додати функцію _AfxAppendFilterSuffix (). Її визначення знаходиться в тому ж файлі, що і реалізація методів класу CDocManager. Копіюємо її звідти і підправляє відповідно до того, що у нас зараз в основному шаблоні кілька розширень. До речі кажучи, цю функцію потрібно правити тільки в MFC 4.2. MFC версії 7.1 вже містить код, який правильно обробляє шаблони з декількома розширеннями.

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

CString strExt;
if (pTemplate -> GetDocString (strExt. CDocTemplate. filterExt) . strExt. IsEmpty ()) ASSERT (strExt [0] == '.');
int pos = fileName. Find (strExt);
if (pos! = -1)
fileName = fileName. Left (pos) + '.' + StrDefault;
>

Цей код додається майже в самому кінці методу DoPromptFileName (), перед тим, як змінна fileName буде використана.