Опубликован: 17.08.2010 | Доступ: свободный | Студентов: 999 / 59 | Оценка: 4.11 / 3.89 | Длительность: 29:38:00
Самостоятельная работа 9:

Однодокументный интерфейс MFC

Изменение меню

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

  1. Добавить категорию для выбора цвета
  2. Добавить категорию для выбора толщины линии
  3. Добавить обработчики для каждого нового пункта меню
  4. Добавить обработчики, которые бы проверяли, какая позиция меню представляет текущий цвет и текущую толщину линии
Изменение ресурса меню
  • Через вкладку Resource View вызовите ресурс меню на редактирование
  • Отредактируйте ресурс меню в соответствии с рисунками, которые получены для режима Edit IDs оболочки


Мастер создал макроопределения. Чтобы их увидеть, в панели Solution Explorer необходимо файл приложения Resource.h. Файл должен содержать, примерно, следующий код

Файл приложения Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by DrawSDI.rc
//
#define IDD_ABOUTBOX                    100
#define IDP_OLE_INIT_FAILED             100
#define IDR_MAINFRAME                   128
#define IDR_DrawSDITYPE                 129
#define ID_COLOR_BLACK                  32771
#define ID_COLOR_BLUE                   32772
#define ID_COLOR_GREEN                  32773
#define ID_COLOR_CYAN                   32774
#define ID_COLOR_RED                    32775
#define ID_COLOR_MAGENTA                32776
#define ID_COLOR_YELLOW                 32777
#define ID_COLOR_WHITE                  32778
#define ID_WIDTH_WIDTH                  32779
#define ID_WIDTH_1                      32780
#define ID_WIDTH_2                      32781
#define ID_WIDTH_3                      32782
#define ID_WIDTH_4                      32783
#define ID_WIDTH_5                      32784
#define ID_WIDTH_6                      32785
#define ID_WIDTH_7                      32786
#define ID_WIDTH_8                      32787
...............................................
Добавление обработчиков меню

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

Первым способом является вызов в редакторе меню через команду Add Event Handler контекстного меню соответствующего пункта


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

Вторым способом является выделение в панели Class View нужного класса, который будет принимать и обрабатывать сообщения от пунктов меню. Затем в панели Properties необходимо установить режим Events и для соответствующих идентификаторов пунктов меню создать обработчики, раскрыв узлы дерева событий


Первый тип сообщения ( COMMAND ) Windows генерирует каждый раз, когда пользователь выбирает соответствующий пункт меню. При этом сообщение передается со значением ID идентификатора пункта в качестве параметра сообщения. Сообщение типа UPDATE_COMMAND_UI не является чистым сообщением Windows, это запрос MFC на обновление внешнего вида отдельных пунктов меню перед их показом пользователю. Мы создадим обработчики для обеих сообщений каждого пункта, добавленного нами в меню.

  • Откройте вкладку Class View и выберите класс CDrawSDIDoc
  • В панели Properties установите режим Events и найдите идентификатор ID_COLOR_BLACK
  • Раскройте узел событий для пункта меню как объекта и для каждого из сообщений COMMAND и UPDATE_COMMAND_UI создайте обработчики, которые заполните так

    Обработчики меню для выбора цвета Black
    void CDrawSDIDoc::OnColorBlack()
    {
      m_iColor = 0;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      // Отобразить пункт меню (если TRUE) с флажком
      pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
    }
  • Аналогичным образом создайте обработчики для выбора всех других цветов

    Обработчики меню для выбора остальных цветов
    void CDrawSDIDoc::OnColorBlue()
    {
      m_iColor = 1;
    }
      
    void CDrawSDIDoc::OnUpdateColorBlue(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iColor == 1 ? TRUE : FALSE);
    }
      
    .........................................................
      
    void CDrawSDIDoc::OnColorWhite()
    {
      m_iColor = 7;
    }
      
    void CDrawSDIDoc::OnUpdateColorWhite(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iColor == 7 ? TRUE : FALSE);
    }
  • Подобным образом создайте обработчики для выбора через меню толщины линий рисования

    Обработчики меню для выбора толщины линий
    void CDrawSDIDoc::OnWidth1()
    {
      m_iWidth = 1;
    }
      
    void CDrawSDIDoc::OnUpdateWidth1(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iWidth == 1 ? TRUE : FALSE);
    }
      
    .....................................................
      
    void CDrawSDIDoc::OnWidth8()
    {
      m_iWidth = 8;
    }
      
    void CDrawSDIDoc::OnUpdateWidth8(CCmdUI *pCmdUI)
    {
      pCmdUI->Enable(); 
      pCmdUI->SetCheck(m_iWidth == 8 ? TRUE : FALSE);
    }

Построение комбинированных обработчиков

Мы видим, что количество обработчиков пунктов меню очень большое. Имеется возможность скомбинировать обработчики меню каждой категории, существенно уменьшив количество обработчиков и сделав код более изящным. Для этого применяют групповые сообщения Windows с параметрами.

Карта сообщений для индивидуальных обработчиков

Если посмотреть карту сообщений в файле DrawSDIDoc.cpp, то можно увидеть, что обработчики и идентификаторы пунктов меню связаны так

Карта сообщений в файле CDrawSDIDoc.cpp
BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
  ON_COMMAND(ID_COLOR_BLACK, OnColorBlack)
  ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, OnUpdateColorBlack)
  ON_COMMAND(ID_COLOR_BLUE, OnColorBlue)
  ON_UPDATE_COMMAND_UI(ID_COLOR_BLUE, OnUpdateColorBlue)
  ON_COMMAND(ID_COLOR_GREEN, OnColorGreen)
  ON_UPDATE_COMMAND_UI(ID_COLOR_GREEN, OnUpdateColorGreen)
  ON_COMMAND(ID_COLOR_CYAN, OnColorCyan)
  ON_UPDATE_COMMAND_UI(ID_COLOR_CYAN, OnUpdateColorCyan)
  ON_COMMAND(ID_COLOR_RED, OnColorRed)
  ON_UPDATE_COMMAND_UI(ID_COLOR_RED, OnUpdateColorRed)
  ON_COMMAND(ID_COLOR_MAGENTA, OnColorMagenta)
  ON_UPDATE_COMMAND_UI(ID_COLOR_MAGENTA, OnUpdateColorMagenta)
  ON_COMMAND(ID_COLOR_YELLOW, OnColorYellow)
  ON_UPDATE_COMMAND_UI(ID_COLOR_YELLOW, OnUpdateColorYellow)
  ON_COMMAND(ID_COLOR_WHITE, OnColorWhite)
  ON_UPDATE_COMMAND_UI(ID_COLOR_WHITE, OnUpdateColorWhite)
  ON_COMMAND(ID_WIDTH_1, OnWidth1)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_1, OnUpdateWidth1)
  ON_COMMAND(ID_WIDTH_2, OnWidth2)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_2, OnUpdateWidth2)
  ON_COMMAND(ID_WIDTH_3, OnWidth3)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_3, OnUpdateWidth3)
  ON_COMMAND(ID_WIDTH_4, OnWidth4)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_4, OnUpdateWidth4)
  ON_COMMAND(ID_WIDTH_5, OnWidth5)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_5, OnUpdateWidth5)
  ON_COMMAND(ID_WIDTH_6, OnWidth6)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_6, OnUpdateWidth6)
  ON_COMMAND(ID_WIDTH_7, OnWidth7)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_7, OnUpdateWidth7)
  ON_COMMAND(ID_WIDTH_8, OnWidth8)
  ON_UPDATE_COMMAND_UI(ID_WIDTH_8, OnUpdateWidth8)
END_MESSAGE_MAP()

Обратите внимание, что каждое макроопределение карты сообщений, сгенерированное мастером Add Event Handler, имеет в заголовке только два параметра. Но существуют макроопределения, которые мастером не генерируются и которые нужно вводить вручную, но зато они имеют три параметра и способны регистрировать на один обработчик диапазон идентификаторов. Применительно к нашей задаче они могут выглядеть так

Возможная таблица сообщений с диапазоном в файле CDrawSDIDoc.cpp
BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
  ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
  ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
  
  ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnWidthCommand)
  ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_8, OnUpdateWidthUI)
END_MESSAGE_MAP()
Обработчикам передаются значения ID пунктов меню

Написанные нами ранее в приложении обработчики, на примере выбора одного цвета, имеют вид

Обработчики сообщений файла CDrawSDIDoc.cpp
void CDrawSDIDoc::OnColorBlack()
{
  m_iColor = 0;
}
  
void CDrawSDIDoc::OnUpdateColorBlack(CCmdUI *pCmdUI)
{
  // Отобразить пункт меню (по умолчанию TRUE) как доступный
  pCmdUI->Enable(); 
  // Отобразить пункт меню (если TRUE) с флажком
  pCmdUI->SetCheck(m_iColor == 0 ? TRUE : FALSE);
}
....................................................

Мастер при создании первого обработчика типа OnColorBlack() не предусмотрел в нем передачу аргументов, однако этот обработчик может принимать аргумент в виде значения идентификатора нажатого пункта меню. Во второй обработчик типа OnUpdateColorBlack(CCmdUI *pCmdUI) в качестве аргумента передается указатель на объект, в свойстве pCmdUI->m_nID которого содержится значение идентификатора соответствующего пункта меню. Учитывая это, мы можем создать комбинированные обработчики для каждой категории меню.

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

Дополнительные пункты меню категории Width
Название пункта Толщина пера
Medium (Средняя) 16
Thick (Толстое) 24
Very Thick (Очень толстое) 32
  • Через панель Resource View вызовите редактор меню
  • Добавьте в конец категории Width три новых пункта так, чтобы вначале названия пунктов были введены цифрами 16, 24, 32. Это нужно для того, чтобы редактор автоматически сформировал имена идентификаторов похожими на предыдущие пункты
  • Измените названия уже сгенерированных пунктов на слова, приведенные в таблице выше

Должно получиться что-то подобное в режиме Edit IDs редактора меню


Новые комбинированные обработчики

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

  1. Прототипы расположены объявлении класса CDrawSDIDoc в файле DrawSDIDoc.h
  2. Определения размещены в конце файла DrawSDIDoc.cpp
  3. Связь идентификаторов пунктов меню и имен соответствующих обработчиков прописана в карте сообщений класса CDrawSDIDoc в начале файла DrawSDIDoc.cpp

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

  • Откройте через панель Solution Explorer файл Resource.h

    Он должен иметь примерно такой вид

    Идетификаторы пунктов меню в файле Resource.h
    ...............................................  
    #define ID_COLOR_BLACK                  32771
    #define ID_COLOR_BLUE                   32772
    #define ID_COLOR_GREEN                  32773
    #define ID_COLOR_CYAN                   32774
    #define ID_COLOR_RED                    32775
    #define ID_COLOR_MAGENTA                32776
    #define ID_COLOR_YELLOW                 32777
    #define ID_COLOR_WHITE                  32778
    #define ID_WIDTH_WIDTH                  32779
    #define ID_WIDTH_1                      32780
    #define ID_WIDTH_2                      32781
    #define ID_WIDTH_3                      32782
    #define ID_WIDTH_4                      32783
    #define ID_WIDTH_5                      32784
    #define ID_WIDTH_6                      32785
    #define ID_WIDTH_7                      32786
    #define ID_WIDTH_8                      32787
    #define ID_WIDTH_16                     32788
    #define ID_WIDTH_24                     32789
    #define ID_WIDTH_32                     32790
    ...............................................
  • В файле DrawSDIDoc.h уберите прототипы всех созданных ранее обработчиков для категорий меню Color и Width и введите прототипы комбинированных обработчиков отдельно для каждой категории в конец класса

    Код должен выглядеть так

    Прототипы комбинированных обработчиков в файле DrawSDIDoc.h
    class CDrawSDIDoc : public CDocument
    {
    ................................................
    public:
      // Объявление таблицы цветов
      static const COLORREF m_Colors[8];
      
      // Комбинированные обработчики Color и Width
      afx_msg void OnColorCommand(UINT nID);
      afx_msg void OnUpdateColorUI(CCmdUI *pCmdUI);
      afx_msg void OnWidthCommand(UINT nID);
      afx_msg void OnUpdateWidthUI(CCmdUI *pCmdUI);
    };
  • В файле DrawSDIDoc.cpp карта сообщений после редактирования должна выглядеть так

    Карта сообщений в файле DrawSDIDoc.cpp
    BEGIN_MESSAGE_MAP(CDrawSDIDoc, CDocument)
      ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnUpdateColorUI)
      ON_COMMAND_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnWidthCommand)
      ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_1, ID_WIDTH_32, OnUpdateWidthUI)
    END_MESSAGE_MAP()
  • Уберите в файле DrawSDIDoc.cpp код всех прежних обработчиков для категорий Color и Width и введите новый код обобщенных обработчиков так

    Новый код обработчиков сообщений в файле DrawSDIDoc.cpp
    
    // Срабатывает при нажатии на любой пункт меню Color
    void CDrawSDIDoc::OnColorCommand(UINT nID)
    {
      m_iColor = nID - ID_COLOR_BLACK;
    }
      
    // Срабатывает для каждого пункта перед раскрытием меню Color
    void CDrawSDIDoc::OnUpdateColorUI(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      bool check = pCmdUI->m_nID == m_iColor + ID_COLOR_BLACK;
      // Какой цвет установлен, такой пункт
      // меню и отобразить с флажком
      pCmdUI->SetCheck(check ? TRUE : FALSE);
    }
      
    // Срабатывает при нажатии на любой пункт меню Width
    void CDrawSDIDoc::OnWidthCommand(UINT nID)
    {
      switch(nID){
        case ID_WIDTH_16: m_iWidth = 16; break;
        case ID_WIDTH_24: m_iWidth = 24; break;
        case ID_WIDTH_32: m_iWidth = 32; break;
        default: m_iWidth = nID - ID_WIDTH_1 + 1;
      }
    }
      
    // Срабатывает для каждого пункта перед раскрытием меню Width
    void CDrawSDIDoc::OnUpdateWidthUI(CCmdUI *pCmdUI)
    {
      // Отобразить пункт меню (по умолчанию TRUE) как доступный
      pCmdUI->Enable(); 
      
      bool check;
      int pos = 0;
      if(m_iWidth <= 8)
        pos = m_iWidth - 1;  //0-7
      else if(m_iWidth == 16)  
        pos = m_iWidth - 8;  //8
      else if(m_iWidth == 24)
        pos = m_iWidth - 15;//9
      else if(m_iWidth == 32)
        pos = m_iWidth - 22;//10
      // Какая толщина установлена, такой пункт
      // меню и отобразить с флажком
      check = pCmdUI->m_nID == pos + ID_WIDTH_1;
      pCmdUI->SetCheck(check ? TRUE : FALSE);
    }
  • Постройте приложение и проверьте его работоспособность
  • Оформите только About, но ни в коем случае не оформляйте строку заголовка окна и не русифицируйте ресурс String Table. Может смениться кодировка и придется тексты интерфейсов переколачивать заново вручную
Александр Даниленко
Александр Даниленко
Стоит Windows 8 Pro, Visual Studio 2010 Express Edition .