Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Visual C++ - расширенное программирование Диалог открытия/сохранения файлов в стиле Windows 2000/XP


Информационный Канал Subscribe.Ru

Visual C++ - расширенное программирование

Выпуск № 12
Cайт : SoftMaker.com.ru
Архив рассылки : SoftMaker.com.ru
Количество подписчиков : 4550
В этом выпуске
От автора

Здравствуйте, уважаемые подписчики !

Сегодня у меня есть пара достаточно значительных новостей относительно рассылки.

Рассылка "C/C++ Вопрос-Ответ" вместо рубрики "Вопрос-Ответ"

Как вы можете помнить, до последнего времени в рассылке существовала рубрика "Вопрос-Ответ", в которой подписчиками задавались вопросы и давались ответы в области программирования на C и C++. Теперь эта рубрика концептуально не укладывается в рамки данной рассылки. В связи с этим открыта новая рассылка, а рубрика переносится туда:

"C/C++ Вопрос-Ответ"
Эта рассылка, конечно же, посвящена программированию на C и C++.
Рассылка работает в режиме Вопрос-Ответ.
Периодичность выхода - еженедельно.

Так что, всех приглашаю подписаться:

  C/C++ Вопрос-Ответ    
Подписаться по почте
В рассылке можно задать свой вопрос по программированию на C, C++, дать свой ответ на заданный другим подписчиком вопрос.

Здесь интересно - присоединяйтесь !


Изменение объема и периодичности выпусков

Новость вторая.
С данного момента рассылка будет выходить чаще, но объем выпусков, естественно, уменьшится. Думаю, это сделает рассылку более легкой для прочтения и восприятия.

---

Как всегда, Вы можете отправить свои пожелания по поводу рассылки и сайта по этому адресу.

Если вы хотите опубликовать свою статью, исходники, либо создать и вести какой либо раздел в этой рассылке - также пишите мне.

С уважением, Вахтуров Виктор.

MFC - простое и сложное

Диалог открытия/сохранения файлов в стиле Windows 2000
Вступление

Общаясь в нескольких форумах тематики "программирование", я несколько раз сталкивался с одним и тем же вопросом. Звучал он примерно так: "как в программе (написанной с использованием MFC) при работе под Windows 2000/XP использовать новый диалог открытия/сохранения файлов (с панелью слева, наподобие панели Outlook) ?". Везде использовалась среда разработки Visual C++ 6.0 и MFC версии 4.2.

Собственно, задача сводилась к тому, чтобы получить диалог следующего вида:



Рис. 1

под Windows 2000/XP, используя класс MFC CFileDialog (отображался диалог старого стиля - как в Windows 95).

Сразу оговорюсь. Данная проблема исправлена в версии библиотеки MFC, входящей в комплект поставки Visual Studio .NET. И, вообще то, тема эта не новая (данный вопрос рассматривал еще в апреле 2000-го года Paul DiLascia; его статью можно найти в MSDN), но, поскольку вопрос задают все снова и снова, позволю себе раскрыть данную тему здесь.

API-реализация (или - как это устроено)

Как известно, для отображения диалога открытия и диалога сохранения файлов в Windows существуют API функции:


BOOL GetOpenFileName(LPOPENFILENAME lpofn);
и
BOOL GetSaveFileName(LPOPENFILENAME lpofn);

соответственно. Они доступны во всех версиях Windows, начиная с Windows 95 (для семейства Win9x) и с Windows NT 3.1 (для семейств Windows NT/2000/XP).

Реализация класса CFileDialog библиотеки MFC (конечно же) также использует эти функции для создания диалоговых окон открытия/сохранения файлов.
Как показывает простой эксперимент, эти API-функции работают "правильно" во всех версиях Windows. То есть, следующий простой код:


OPENFILENAME sOfn;
memset(&sOfn, 0, sizeof(sOfn));
sOfn.lStructSize = sizeof(sOfn);
::GetOpenFileName(&sOfn);

отобразит диалог открытия файла стиля Windows 95 при работе под Windows 95/98/Me, и диалог стиля Windows 2000 (с левой панелью) при работе под Windows 2000/XP.

Однако, при использовании CFileDialog библиотеки MFC версии 4.2 и при работе под Windows 95 и при работе под Windows 2000/XP будут отображаться диалоги старого стиля.

В этом и состоит проблема.

Давайте же попробуем определить, от каких факторов зависит отображение диалогов того или иного стиля.

Рассмотрим определение структуры OPENFILENAME (я намеренно привожу тут определение, взятое из заголовочного файла commdlg.h Visual Studio .NET, поскольку там есть поля, определенные для версий Windows 2000 и выше).

//

typedef struct tagOFNA {
   DWORD        lStructSize;
   HWND         hwndOwner;
   HINSTANCE    hInstance;
   LPCSTR       lpstrFilter;
   LPSTR        lpstrCustomFilter;
   DWORD        nMaxCustFilter;
   DWORD        nFilterIndex;
   LPSTR        lpstrFile;
   DWORD        nMaxFile;
   LPSTR        lpstrFileTitle;
   DWORD        nMaxFileTitle;
   LPCSTR       lpstrInitialDir;
   LPCSTR       lpstrTitle;
   DWORD        Flags;
   WORD         nFileOffset;
   WORD         nFileExtension;
   LPCSTR       lpstrDefExt;
   LPARAM       lCustData;
   LPOFNHOOKPROC lpfnHook;
   LPCSTR       lpTemplateName;
#ifdef _MAC
   LPEDITMENU   lpEditInfo;
   LPCSTR       lpstrPrompt;
#endif
#if (_WIN32_WINNT >= 0x0500)
   void *  pvReserved;
   DWORD        dwReserved;
   DWORD        FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAMEA, *LPOPENFILENAMEA;

как видите, версия структуры для Windows 2000/XP имеет 3 новых поля:


   void *       pvReserved;
   DWORD        dwReserved;
   DWORD        FlagsEx;

что означает, что ее размер на 12 байт больше чем размер структуры старой версии (размер структуры указывается в ее поле lStructSize; для старой версии структуры он равен 76 байт, для новой - 88). Это важно, поскольку реализация функций GetOpenFileName и GetSaveFileName определяет версию структуры исходя именно из значения поля lStructSize.

Очевидно, версия структура влияет на стиль создаваемых диалогов.

Известно, что для изменения вида диалога сохранения/открытия файлов, создаваемого функциями GetOpenFileName и GetSaveFileName используется следующий способ: в поле Flags структуры OPENFILENAME устанавливается комбинация флагов OFN_ENABLETEMPLATE и OFN_ENABLETEMPLATEHANDLE, в поле hInstance - дескриптор модуля, содержащего ресурс шаблона диалога, а в поле lpTemplateName - имя ресурса шаблона диалога, который будет использоваться вместо стандартного.
Этот способ применяется, например, для добавления своих элементов управления в стандартный диалог открытия/сохранения.

Также известно, что для обработки сообщений-уведомлений окна диалога обычно используется функция-обработчик, указатель на которую должен содержаться в поле lpfnHook структуры OPENFILENAME (при этом должен быть установлен флаг OFN_ENABLEHOOK в поле Flags).

Эксперименты показывают, что:

  • При передаче в функции GetOpenFileName и GetSaveFileName указателя на структуру OPENFILENAME с значением поля lStructSize равным 76 (старая версия структуры) и значением поля Flags равным 0 под Windows 2000 отображается диалог нового стиля.
  • При передаче указателя на структуру OPENFILENAME с значением поля lStructSize равным 76 и значением поля Flags, содержащим флаг OFN_ENABLETEMPLATE под Windows 2000 отображается диалог старого стиля.
  • При передаче указателя на структуру OPENFILENAME с значением поля lStructSize равным 76 и значением поля Flags, содержащим флаг OFN_ENABLEHOOK под Windows 2000 отображается диалог старого стиля.
  • Но.
    При передаче указателя на структуру OPENFILENAME с значением поля Flags, содержащим флаги OFN_ENABLEHOOK и/или OFN_ENABLETEMPLATE и с значением поля lStructSize равным 88 под Windows 2000 отображается диалог нового (!) стиля.

Таким образом, при работе программы под управлением ОС Windows 2000 (и более поздних версий), для отображения диалога открытия или сохранения файла нового стиля (с левой панелью) вне зависимости от флагов, установленных в поле Flags структуры OPENFILENAME, функциям GetOpenFileName и GetSaveFileName следует передавать указатель на версию этой структуры длиной 88 байт.

В этом то и состоит проблема, описанная выше. Библиотека MFC использует функцию-хук для обработки событий диалога (эта глобальная функция _AfxCommDlgProc, из которой, кстати, вызываются виртуальные функции класса CFileDialog), следовательно устанавливается флаг OFN_ENABLEHOOK в поле Flags структуры OPENFILENAME. Однако, она (библиотека) скомпилирована с учетом использования только одной версии структуры OPENFILENAME - длиной 76 байт.

Отсюда становится понятным, что при использовании класса CFileDialog будут отображаться только диалоги старого стиля (как в Windows 95).

Реализация класса CFileDialog2K

Давайте теперь попробуем доработать класс CFileDialog библиотеки MFC таким образом, чтобы при работе программы под управлением ОС Windows 2000/XP диалоги открытия/сохранения файлов выглядели должным образом.

Прежде всего, следует отметить следующие обстоятельства:

  • Диалоговые окна сохранения/открытия файла создаются вызовами GetOpenFileName и GetSaveFileName из метода DoModal класса CFileDialog:

    //
    
    int CFileDialog::DoModal()
    {
            ...
    
            int nResult;
            if (m_bOpenFileDialog)
                    nResult = ::GetOpenFileName(&m_ofn);
            else
                    nResult = ::GetSaveFileName(&m_ofn);
    
            ...
    }
    

  • Переменная-член структура m_ofn используется везде в коде методов класса CFileDialog непосредственно.
  • Библиотека MFC давным-давно скомпилирована (как в виде статических, так и в виде динамических библиотек) и повлиять на структуру класса мы уже точно никак не сможем.
  • В заголовочных файлах Visual C++ 6.0 есть объявление только старой версии структуры OPENFILENAME - длиной 76 байт.

Из этого следует, что:

  • Придется создавать свой класс-наследник CFileDialog, включив в него еще одну переменную-член - структуру OPENFILENAME (либо указатель на нее), но уже новой версии (причем, придется самостоятельно объявить соответствующий тип).
  • Придется полностью реализовать метод CFileDialog::DoModal (надо передавать в функции GetOpenFileName и GetSaveFileName указатель на структуру новой версии, в то время, как методы базового класса оперируют данными переменной m_ofn, следовательно, надо скопировать данные m_ofn в новую структуру, вызвать GetOpenFileName или GetSaveFileName, а затем скопировать полученные данные обратно в m_ofn).
  • Придется синхронизировать содержимое этих двух структур (старой и новой) все время, пока существует окно диалога сохранения/открытия файла.

Итак, начнем.

Я создал проект обычным мастером создания проектов Visual C++. Проект с главным окном - диалогом.

Теперь создадим класс CFileDialog2K, выбрав в качестве базового класса CFileDialog. Class Wizard сразу создал конструктор класса и карту сообщений. Вручную придется добавить комментарии //{{AFX_VIRTUAL(CFileDialog2K) - для того, чтобы Class Wizard мог вставлять в код описания виртуальных методов класса.

Затем (тем же самым Wizard-ом) добавим виртуальный метод DoModal и виртуальную функцию OnNotify.

Насчет необходимости реализации DoModal уже было сказано выше. Насчет OnNotify могу сказать только то, что она вызывается во время работы пользователя с диалоговым окном (она получает уведомления с кодами типа CDN_INITDONE, CDN_SELCHANGE, и.т.д. при совершении пользователем различных действий в диалоге). Базовая реализация CFileDialog::OnNotify вызывает виртуальные функции (например, OnInitDone(), OnFileNameChange()). Из этих функций могут быть вызваны другие методы класса CFileDialog. А эти методы (к примеру, CFileDialog::HideControl, CFileDialog::GetPathName) используют данные переменной m_ofn. Поэтому в реализованной OnNotify необходимо копировать данные в структуру m_ofn перед вызовом реализации OnNotify базового класса.

Далее необходимо отметить всего пару важных моментов:

  • Большинство кода DoModal просто скопировано из исходников MFC, и в него внесены необходимые исправления.
  • Объявлена структура OPENFILENAME_2K с дополнительными полями (определение дополнительных полей взято, конечно же, из заголовочного файла commdlg.h Visual Studio .NET).
  • В DoModal определяется версия Windows, под которой работает программа и, если это Windows 2000 или более поздние версии, то выделяется память под структуру OPENFILENAME_2K.

А теперь просто приведу код.

Описание структуры OPENFILENAME_2K и класса CFileDialog2K:

//


struct OPENFILENAME_2K
{
        OPENFILENAME        ofn;

        void                        *pvReserved;
        DWORD                        dwReserved;
        DWORD                        FlagsEx;
};

class CFileDialog2K : public CFileDialog
{
        DECLARE_DYNAMIC(CFileDialog2K)

public:
        
        CFileDialog2K(BOOL bOpenFileDialog, // TRUE for FileOpen, FALSE for FileSaveAs
                LPCTSTR lpszDefExt = NULL,
                LPCTSTR lpszFileName = NULL,
                DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                LPCTSTR lpszFilter = NULL,
                CWnd* pParentWnd = NULL);

public:

        //{{AFX_VIRTUAL(CFileDialog2K)
        public:
        virtual int DoModal();
        protected:
        virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
        //}}AFX_VIRTUAL

protected:

        OPENFILENAME_2K *m_pofn2K;

        //{{AFX_MSG(CFileDialog2K)
        //}}AFX_MSG

        DECLARE_MESSAGE_MAP()
};

Реализация методов класса CFileDialog2K:

//


IMPLEMENT_DYNAMIC(CFileDialog2K, CFileDialog)

CFileDialog2K::CFileDialog2K(BOOL bOpenFileDialog,
                LPCTSTR lpszDefExt, LPCTSTR lpszFileName,
                DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd) :
                CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName,
                            dwFlags, lpszFilter, pParentWnd)
{
        m_pofn2K = NULL;
}


BEGIN_MESSAGE_MAP(CFileDialog2K, CFileDialog)
        //{{AFX_MSG_MAP(CFileDialog2K)
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()

int CFileDialog2K::DoModal() 
{
        ASSERT_VALID(this);
        ASSERT(m_ofn.Flags & OFN_ENABLEHOOK);
        ASSERT(m_ofn.lpfnHook != NULL); // can still be a user hook

        // zero out the file buffer for consistent parsing later
        ASSERT(AfxIsValidAddress(m_ofn.lpstrFile, m_ofn.nMaxFile));
        DWORD nOffset = lstrlen(m_ofn.lpstrFile)+1;
        ASSERT(nOffset <= m_ofn.nMaxFile);
        memset(m_ofn.lpstrFile+nOffset, 0, (m_ofn.nMaxFile-nOffset)*sizeof(TCHAR));

        // WINBUG: This is a special case for the file open/save dialog,
        //  which sometimes pumps while it is coming up but before it has
        //  disabled the main window.
        HWND hWndFocus = ::GetFocus();
        BOOL bEnableParent = FALSE;
        m_ofn.hwndOwner = PreModal();
        AfxUnhookWindowCreate();
        if (m_ofn.hwndOwner != NULL && ::IsWindowEnabled(m_ofn.hwndOwner))
        {
                bEnableParent = TRUE;
                ::EnableWindow(m_ofn.hwndOwner, FALSE);
        }

        _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
        ASSERT(pThreadState->m_pAlternateWndInit == NULL);

        if (m_ofn.Flags & OFN_EXPLORER)
                pThreadState->m_pAlternateWndInit = this;
        else
                AfxHookWindowCreate(this);

        // с этого места внесены добавления и исправления

        int nResult = -1;

        DWORD dwWinMajorVersion = GetVersion();

        if(dwWinMajorVersion > 2)
        {
                if(dwWinMajorVersion > 4) // Windows 2000 и выше
                {
                        m_pofn2K = new OPENFILENAME_2K;

                        memset(m_pofn2K, 0, sizeof(OPENFILENAME_2K));
                        memcpy(m_pofn2K, &m_ofn, sizeof(OPENFILENAME));

                        m_pofn2K->ofn.lStructSize = sizeof(OPENFILENAME_2K);
                }

                if (m_bOpenFileDialog)
                        nResult = ::GetOpenFileName(m_pofn2K ?
                                    (LPOPENFILENAME) m_pofn2K : &m_ofn);
                else
                        nResult = ::GetSaveFileName(m_pofn2K ?
                                    (LPOPENFILENAME) m_pofn2K : &m_ofn);

                if(m_pofn2K)
                {
                        memcpy(&m_ofn, m_pofn2K, sizeof(m_ofn));
                        m_ofn.lStructSize = sizeof(m_ofn);

                        delete m_pofn2K;

                        m_pofn2K = NULL;
                }
        }

        // в этом месте добавления и исправления заканчиваются

        if (nResult)
                ASSERT(pThreadState->m_pAlternateWndInit == NULL);
        
        pThreadState->m_pAlternateWndInit = NULL;

        // WINBUG: Second part of special case for file open/save dialog
        if (bEnableParent)
                ::EnableWindow(m_ofn.hwndOwner, TRUE);
        if (::IsWindow(hWndFocus))
                ::SetFocus(hWndFocus);

        PostModal();
        return nResult ? nResult : IDCANCEL;
}

BOOL CFileDialog2K::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
{
        if(m_pofn2K != NULL)
        {
                memcpy(&m_ofn, &m_pofn2K->ofn, sizeof(m_ofn));
                m_ofn.lStructSize = sizeof(m_ofn);
        }

        return CFileDialog::OnNotify(wParam, lParam, pResult);
}

Вот, пожалуй, и все.

Автор статьи : Вахтуров Виктор.

Скачать исходный код класса CFileDialog2K, а также демонстрационное приложение можно с соответствующей страницы сайта: CFileDialog2K - класс диалога сохранения/открытия файла с поддержкой стиля Windows 2000/XP.

Дискуссионные листы компьютерной тематики


Программирование. Форум !!!

Задайте здесь любой вопрос по программированию - и Вы получите ответ. Участвуйте в оживленных дискуссиях, обсуждайте интересные темы. Давайте ответы сами. Ведь это форум !!! Здесь просто интересно ! Присоединяйтесь !

Вебстроительство. Форум !!!

В дискуссионном листе ведется обсуждение различных аспектов создания сайтов - написание скриптов, использование, настройка портальных движков, и многое другое. Присоединяйтесь !

Поисковые системы. Форум !!!

Этот дискуссионный лист посвящен обсуждению поиковых систем, методов индексации сайтов поисковиками, способам оптимизации сайта под поисковые системы.

Хостинг. Обзоры и обсуждения платного и бесплатного хостинга.

Вы ищете хостинг (платный, бесплатный) ? Хотите спросить совета в выборе ? Можете обсудить это здесь. Поделитесь советом, если знаете. Или узнайте больше. Все о хостинге.


Всего доброго. До встречи в следующей рассылке.
Любой из материалов, опубликованных в этой рассылке, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.qandacpp
Отписаться

В избранное