Разработка кроссплатформенных приложений с использованием Juce

Элементы управления. Компоненты меню

Разбить на страницы
Показывать лекцию целиком

Цель лекции: Научиться создавать и использовать главное и контекстное меню в своих программах

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

Основными типами меню являются:

  • меню верхнего уровня, представляющее собой полосу с набором команд в верхней части главного окна программы и
  • контекстное меню, представляющее собой прямоугольную область с рядом пунктов (обычно вертикально расположенных), вызываемое щелчком правой кнопкой мыши по тому или иному виджету.
  • Меню верхнего уровня является результатом совместной работы нескольких классов компонентов: MenuBarComponent, который создаёт полосу под заголовком окна и отвечает за отображение команд, которые (вместе с их обработчиками) создаёт MenuBarModel, а также PopupMenu, объекты которого образуют выпадающие меню и подменю.

    Класс PopupMenu создаёт также и контекстное меню.

    Конструктор MenuBarComponent в качестве параметра принимает указатель на объект MenuBarModel: MenuBarComponent::MenuBarComponent(MenuBarModel* model). В случае, если мы создаём меню главного окна приложения (а на практике именно так и происходит), то удобнее воспользоваться методом класса DocumentWindow void DocumentWindow::setMenuBar(MenuBarModel* menuBarModel, int menuBarHeight = 0), который также принимает в качестве параметра указатель на объект MenuBarModel, который и формирует внешний вид меню. Второй принимаемый параметр — это ширина меню; в случае, если он принимает значение 0, то ширина полосы меню будет установлена по умолчанию.

    Если функция setMenuBar принимает нулевое значение, любое существующее меню будет удалено из главного окна и тогда мы можем добавить объект MenuBarComponent с помощью метода void DocumentWindow::setMenuBarComponent(Component* newMenuBarComponent). Получить текущее меню можно с помощью функции-члена класса DocumentWindow Component* DocumentWindow::getMenuBarComponent() const.

    Рассмотрим создание меню верхнего уровня и контекстного меню на примере простого приложения (рис 15.1).

    Для начала создадим основу нашего меню — её модель. Пусть наши меню, как верхнего уровня, так и контекстное, включают три команды: "Новый", "Открыть" и "Выход". При этом реально работать (осуществлять выход из программы) будет только последняя; при щелчке по первым двум будет выводиться информационное сообщение.

    (рис 15.1) Приложение, демонстрирующее работу меню.

    В качестве модели меню будет выступать класс компонента содержимого, который мы унаследуем от класса ).

    #ifndef _TCentralComponent_h_
    #define _TCentralComponent_h_
    //------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    //------------------------------------------
    // Класс компонента содержимого.
    // Наследует класс модели меню
    class TCentralComponent  : public Component,
            public MenuBarModel
    {
    public:
      TCentralComponent();
      ~TCentralComponent();
    
      // Идентификаторы команд меню
      enum TMenuOptions
      {
        FileNew = 1,
        FileOpen = 2,
        FileQuit = 3
      };
    
      void paint(Graphics);
      void resized();
      // Функции, наследуемые от MenuBarModel
      const StringArray getMenuBarNames();
      void menuItemSelected(int, int);
      const PopupMenu getMenuForIndex(int, const String);
      
      // Вызывает контекстное меню
      void mouseDown(const MouseEvent);
    
    private:
      // Ярлык для вывода сообщений
      Label* pMessagesLabel;
    
      // Изображения - пиктограммы меню
      Image ImageNew_png;
      Image ImageOpen_png;
      Image ImageExit_png;
    
      // Предотвращает создание копии конструктора и оператора =
      TCentralComponent(const TCentralComponent);
      const TCentralComponent operator= (const TCentralComponent);
    };
    //---------------------------------------------
    #endif
    

    Класс ), которые должны быть переопределены в классе компонента содержимого.

    Метод ).

    const StringArray TCentralComponent::getMenuBarNames()
    {
        // Создаются основные пункты меню
        StringArray sMenuNames;
        sMenuNames.add(tr("Файл"));
        return sMenuNames;
    }
    

    Метод virtual void MenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) вызывается всякий раз, когда пользователь щёлкает по тому или иному пункту меню. Идентификатор команды, вызываемой пользователем, и является первым параметром функции. Второй параметр — идентификатор того меню верхнего уровня, в котором была выбрана команда menuItemID.

    Именно в этом методе и находятся обработчики команд меню, выбираемых по их числовым идентификаторам ().

    void TCentralComponent::menuItemSelected(int iMenuItemID, int iTopLevelMenuIndex)
    {
      // Обработчики команд меню
      switch(iMenuItemID)
      {
      // Если выбран пункт "Выход"...
        case FileQuit:
        {
           // Завершаем работу программы
          JUCEApplication::quit();
          break;
        }
        // Если выбран пункт "Новый"...
        case FileNew:
        {
          // показываем сообщение
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"),  
                tr("Выбран пункт \"Новый\""), tr("Да"), 0);
          break;
        }
        // Если выбран пункт "Открыть"...
        case FileOpen:
        {
          // показываем сообщение
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"), 
               tr("Выбран пункт \"Открыть\""), tr("Да"), 0);
          break;
        }
      }
    }
    

    Метод ).

    const PopupMenu TCentralComponent::getMenuForIndex(int iIndex, 
                const String sMenuName)
    {
      // Создаём выпадающее меню
      PopupMenu Menu;
    
      switch(iIndex)
      {
        // Индекс 0 соответствует пункту меню "Файл"
        // (см. getMenuBarNames)
        case 0:
        {
          // Добавляем новые пункты в меню "Файл"
          Menu.addItem(FileNew, tr("Новый"), true, false, ImageNew_png);
          Menu.addItem(FileOpen, tr("Открыть"), true, false, ImageOpen_png);
          // Добавляем разделитель
          Menu.addSeparator();
          Menu.addItem(FileQuit, tr("Выход"), true, false, ImageExit_png);
          break;
        }
      }
    
      return Menu;
    }
    

    Для добавления новых пунктов в выпадающее меню мы воспользовались методом void PopupMenu::addItem(int itemResultId, const String itemText, bool isEnabled = true, bool isTicked = false const Image iconToUse = Image::null) , класса PopupMenu. Метод принимает следующие параметры:

  • itemResultId — число, возвращаемое методом show класса PopupMenu, когда пользователь выбирает пункт меню; как понятно, в качестве этого параметра передаётся идентификатор соответствующей команды;
  • itemText — текст пункта меню для отображения;
  • isEnabled — если параметр принимает значение true, пункт меню окрашивается в серый цвет и не может быть выбран;
  • isTicked — если параметр принимает значение true, рядом с пунктом меню отображается галочка;
  • iconToUse — если параметр не равен нулю, с левой стороны пункта меню отображается пиктограмма (icon).
  • Для того, чтобы удалить все пункты из меню, необходимо вызвать метод void PopupMenu::clear().

    Практически также создаётся контекстное меню. Для его отображения нам необходимо переопределить функцию — обработчик события нажатия пользователем кнопки мыши ().

    void TCentralComponent::mouseDown(const MouseEvent Event)
    {
      // Если пользователь пытается вызвать контекстное меню...
      if(Event.mods.isPopupMenu())
      {
        // создаём его...
        PopupMenu LabelPopupMenu;
        // и добавляем в него пункты
        LabelPopupMenu.addItem(FileNew, tr("Новый"), true, false, 
                ImageNew_png);
        LabelPopupMenu.addItem(FileOpen, tr("Открыть"), true, false,
                ImageOpen_png);
        LabelPopupMenu.addSeparator();
        LabelPopupMenu.addItem(FileQuit, tr("Выход"), true, false, 
                ImageExit_png);
    
        // Показываем контекстное меню.
        const int iResult = LabelPopupMenu.show();
    
        // В зависимости от идентификатора команды
        // вызываем тот или иной обработчик.
        if(iResult == FileNew)
        {
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"), 
                tr("Выбран пункт \"Новый\""), tr("Да"), 0);
        }
        else if(iResult == FileOpen)
        {
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"),                      
                tr("Выбран пункт \"Открыть\""), tr("Да"), 0);
        }
        else if(iResult == FileQuit)
        {
          JUCEApplication::quit();
        }
      }
    }
    

    Поскольку контекстное меню вызывается нажатием правой кнопки мыши, а метод mouseDown вызывается, когда пользователь нажимает любую кнопку, мы предусмотрели соответствующее условие выполнения: мы вызываем публичное свойство класса MouseEvent const ModifierKeys MouseEvent::mods, которое представляет собой ключевой модификатор, ассоциированный с этим событием. Метод bool ModifierKeys::isPopupMenu() const throw() класса ModifierKeys позволяет узнать, было ли это событие связано с вызовом пользователем контекстного меню. Для подобной проверки рекомендуется вызывать именно этот метод, а не bool ModifierKeys::isRightButtonDown() const throw(), информирующий о нажатии правой кнопки мыши, исходя из соображений совместимости с Mac OS X, где контекстное меню вызывается как нажатием на клавишу <CTRL>, так и правой кнопкой мыши.

    После этого создаётся контекстное меню, в которое добавляются пункты уже знакомым по меню верхнего уровня способом.

    Показ контекстного меню осуществляет уже упоминавшаяся выше функция int PopupMenu::show(int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, int standardItemHeight = 0, ModalComponentManager::Callback * callback = 0). Она принимает следующие параметры:

  • int itemIdThatMustBeVisible — идентификатор пункта меню, который должен отображаться в нём при первом запуске (быть обязательно видимым). Если меню содержит слишком много пунктов, то оно будет прокручено до этого пункта;
  • int minimumWidth — минимальная ширина меню в пикселях;
  • int maximumNumColumns — число столбцов для упорядочения пунктов меню, если они не помещаются в одном вертикальном. Если параметр принимает значение 0, то будет установлено число столбцов по умолчанию (обычно около 7);
  • int standardItemHeight — если значение параметра не равно нулю, оно используется для задания стандартной высоты элементов меню;
  • ModalComponentManager::Callback * callback — если значение параметра не равно нулю, меню будет запущено асинхронно.
  • В нашем примере мы оставили все параметры принятыми по умолчанию.

    В дальнейшем в зависимости от идентификатора команды мы вызываем тот или иной её обработчик ().

    Внешний вид программы с вызванным контекстным меню представлен на рисунке 15.2 .

    (рис 15.2) Работа программы с вызванным контекстным меню

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

    (рис 15.3) Работа программы, демонстрирующей совместное использование компонентов меню и ApplicationCommandManager

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

    Объект класса ApplicationCommandManager хранит список команд, которые может выполнять программа, и вызывает их по мере необходимости. С помощью ApplicationCommandManager можно организовать совместное реагирование на общие команды меню, кнопок панели инструментов и других компонентов управления, подобно действиям (класс QAction) библиотеки Qt.

    Объявим объект класса

    #ifndef _TMainForm_h_
    #define _TMainForm_h_
    //--------------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    //--------------------------------------------------
    class TCentralComponent;
    //--------------------------------------------------
    class TMainForm : public DocumentWindow
    {
    public:
      TMainForm();
      ~TMainForm();
    
      void closeButtonPressed();
      
      // Менеджер команд приложения
      ApplicationCommandManager CommandManager;
      
    private:
      // Компонент содержимого
      TCentralComponent* pCentralComponent;
    };
    //----------------------------------------
    #endif
    

    Приёмником для команд, посылаемых ).

    #include "TMainForm.h"
    //-------------------------------------------------
    #include "TCentralComponent.h"
    //-------------------------------------------------
    TMainForm::TMainForm() : DocumentWindow(
        JUCEApplication::getInstance()->getApplicationName(),
        Colours::azure,
        DocumentWindow::allButtons)
    {
      centreWithSize(700, 400);
      setResizable(true, false);
      setVisible(true);
    
      // Динамически создаём экземпляр класса нашего компонента
      pCentralComponent = new TCentralComponent(this);
      // Устанавливаем его в качестве компонента содержимого
      setContentOwned(pCentralComponent, true);
      
      // Регистрируем все созданные команды компонента содержимого
      CommandManager.registerAllCommandsForTarget(pCentralComponent);
      // Регистрируем все текущие команды приложения
      CommandManager.registerAllCommandsForTarget(JUCEApplication::getInstance());
    
      setMenuBar(pCentralComponent);
    }
    //-------------------------------------------------
    TMainForm::~TMainForm()
    {
        setMenuBar(NULL);
    }
    //-------------------------------------------------
    void TMainForm::closeButtonPressed()
    {
        JUCEApplication::getInstance()->systemRequestedQuit();
    }
    //-------------------------------------------------
    

    Класс

  • Virtual ApplicationCommandTarget* ApplicationCommandTarget::getNextCommandTarget() - возвращает следующий после текущего приёмник команд;
  • virtual void ApplicationCommandTarget::getAllCommands(Array< CommandID > commands) — возвращает список всех команд, которые могут быть обработаны;
  • virtual void ApplicationCommandTarget::(CommandID commandID, ApplicationCommandInfo result) — предоставляет всю необходимую информацию по обрабатываемой команде; не считая идентификатора команды, которым является первый параметр метода, эта информация содержится в объекте ApplicationCommandInfo, который предварительно должен быть создан и инициализирован;
  • virtual bool ApplicationCommandTarget::perform(const InvocationInfo info) — запускает команду на выполнение, получая всю необходимую информацию о ней из объекта InvocationInfo, конструктор которого в качестве параметра принимает идентификатор команды. Если все обработчики команд меню находятся в этой функции, то ранее рассмотренный метод menuItemSelected следует оставить пустым.
  • #ifndef _TCentralComponent_h_
    #define _TCentralComponent_h_
    //------------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    #include "TMainForm.h"
    //------------------------------------------------
    // Класс компонента содержимого.
    // Наследует класс приёмника команд
    class TCentralComponent  : public Component,
                public MenuBarModel,
                public ApplicationCommandTarget
    {
    public:
      TCentralComponent(TMainForm* pParent);
      ~TCentralComponent();
    
      // Идентификаторы команд меню
      enum TMenuOptions
      {
        FileNew = 1,
        FileOpen = 2,
        FileQuit = 3,
        LanguageEnglish = 4,
        LanguageRussian = 5
      };
    
      void paint(Graphics);
      void resized();
      void mouseDown(const MouseEvent);
      const StringArray getMenuBarNames();
      void menuItemSelected(int, int);
      const PopupMenu getMenuForIndex(int, const String);
      
      // Функции, наследуемые от ApplicationCommandTarget
      // Возвращает следующий после текущего приёмник команд
      ApplicationCommandTarget* getNextCommandTarget();
      // Возвращает список всех команд, которые могут быть обработаны
      void getAllCommands(Array<int>);
      // Предоставляет всю необходимую информацию по обрабатываемой команде
      void getCommandInfo(CommandID, ApplicationCommandInfo);
      // Запускает команду на выполнение
      bool perform(const InvocationInfo);
      
      // Обработчик смены команд интерфейса
      void LanguageChange();
    
    private:
      // Флаги, какой язык выбран
      bool bEnglish;
      bool bRussian;
      // Родительский компонент
      TMainForm* pParent;
      // Ярлык для вывода сообщений
      Label* pMessagesLabel;
      
      // Строки интерфейса
      String sMessagesLabelText;
      String sFileMenuText;
      String sLanguageMenuText;
      String sFileNewMenuText;
      String sFileOpenMenuText;
      String sFileQuitText;
      String sMessageTitleText;
      String sMessageNewText;
      String sMessageOpenText;
      String sYesButtonText;
    
      Image ImageNew_png;
      Image ImageOpen_png;
      Image ImageExit_png;
    
      // Предотвращает создание копии конструктора и оператора =
      TCentralComponent(const TCentralComponent);
      const TCentralComponent operator= (const TCentralComponent);
    };
    //---------------------------------------------------
    #endif
    

    Как видно из листинга 15.8 , в нашем новом приложении в список идентификаторов команд меню мы добавили две новые: LanguageEnglish и LanguageRussian. Именно эти команды будут обрабатываться с помощью методов ApplicationCommanTarget, а остальные — ранее описанным способом.

    Кроме того, мы предусмотрели метод ).

    Следует понимать, что подобный способ перевода интерфейса, хотя и работает, но приведён исключительно как пример использования командного менеджера. Локализацию приложений, написанных на основе Juce, удобнее осуществлять с помощью класса LocalisedStrings, описание которого дано в лекции 22 .

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

    #include "TCentralComponent.h"
    #include"TImagesFactory.h"
    //-------------------------------------------------------
    #define tr(s) String::fromUTF8(s)
    //-------------------------------------------------------
    TCentralComponent::TCentralComponent(TMainForm* pParent) 
                : Component("Central Component"),
                pMessagesLabel(0)
    {
      this->pParent = pParent;
      
      pMessagesLabel = new Label(L"Messages Label", "");
      pMessagesLabel->setFont(Font(15.0000f, Font::plain));
      pMessagesLabel->setJustificationType(Justification::centred);
      pMessagesLabel->setEditable (false, false, false);
      pMessagesLabel->setColour(Label::outlineColourId, Colours::black);
      pMessagesLabel->setColour(Label::textColourId, Colours::black);
      pMessagesLabel->setColour(Label::backgroundColourId, Colours::azure);
      addAndMakeVisible(pMessagesLabel);
    
      ImageExit_png = ImageCache::getFromMemory(TImagesFactory::psExit_png, 
                TImagesFactory::iExit_pngSize);
      ImageNew_png = ImageCache::getFromMemory(TImagesFactory::psNew_png, 
                TImagesFactory::iNew_pngSize);
      ImageOpen_png = ImageCache::getFromMemory(TImagesFactory::psOpen_png, 
                TImagesFactory::iOpen_pngSize);
    
      setSize (700, 400);
      
      bEnglish = true;
      bRussian = false;
      
      LanguageChange();
    }
    //-----------------------------------------------------
    TCentralComponent::~TCentralComponent()
    {
      // Удаляем дочерние виджеты
      // и обнуляем их указатели
      deleteAllChildren();
    }
    //-----------------------------------------------------
    void TCentralComponent::LanguageChange()
    {
      if(bEnglish)
      {
        sMessagesLabelText = tr(
        quot;Choose an option of the main menu \nor right-click outside of the label 
        to invoke a context menu.");
        sFileMenuText = tr("File");
        sLanguageMenuText = tr("Language");
        sFileNewMenuText = tr("New");
        sFileOpenMenuText = tr("Open");
        sFileQuitText = tr("Quit");
        sMessageTitleText = tr("Information");
        sMessageNewText = tr("Option \"New\" was selected");
        sMessageOpenText = tr("Option \"Open\" was selected");
        sYesButtonText = tr("Yes");
      }
      else if(bRussian)
      {
        sMessagesLabelText = tr(
        "Выберите пункт главного меню \nили щёлкните правой кнопкой мыши 
        за пределами надписи для того, 
        чтобы вызвать контекстное меню.");
        sFileMenuText = tr("Файл");
        sLanguageMenuText = tr("Язык");
        sFileNewMenuText = tr("Новый");
        sFileOpenMenuText = tr("Открыть");
        sFileQuitText = tr("Выход");
        sMessageTitleText = tr("Информация");
        sMessageNewText = tr("Выбран пункт \"Новый\"");
        sMessageOpenText = tr("Выбран пункт \"Открыть\"");
        sYesButtonText = tr("Да");
      }
      
      pMessagesLabel->setText(sMessagesLabelText, false);
      getMenuBarNames();
      // Перерисовываем пункты меню с учётом нового языка
      menuItemsChanged();
    }
    //--------------------------------------------------
    

    В конструкторе компонента содержимого мы вызываем наш метод LanguageChange для задания строк интерфейса приложения. Также эта функция будет вызываться всякий раз, когда пользователь выбирает новый пункт в меню "Язык". Флагом выбора того или иного языка выступают булевые переменные bEnglish и bRussian. Для того, чтобы принудительно перерисовать пункты меню, исходя из новых языковых настроек, мы вызываем метод void MenuBarModel::menuItemsChanged().

    Рассмотрим реализацию функций, унаследованных от класса ).

    ApplicationCommandTarget* TCentralComponent::getNextCommandTarget()
    {getAllCommands
        return findFirstTargetParentComponent();
    }
    

    Метод , )

    void TCentralComponent::getAllCommands(Array<int> iCommands)
    {
      // Регистрация команд для командного менеджера
      const CommandID IDs[] =
      {
        LanguageEnglish,
        LanguageRussian
      };
      
      iCommands.addArray(IDs, numElementsInArray(IDs));
    }
    

    Метод ).

    void TCentralComponent::getCommandInfo(CommandID ID, ApplicationCommandInfo 
                Result)
    {
      switch(ID)
      {
        case LanguageEnglish:
        {
          Result.setInfo(L"English", L"Switch to English", "Language", 0);
          Result.setTicked(bEnglish);
          break;
        }
        case LanguageRussian:
        {
          Result.setInfo(tr("Русский"), L"Switch to Russian", "Language", 0);
          Result.setTicked(bRussian);
          break;
        }
      }
    }
    

    Как мы уже упоминали, за это отвечает объект класса ApplicationCommandInfo. Отображение соответствующих команде пунктов меню в программе мы задаём вызовом метода void ApplicationCommandInfo::setInfo(const String shortName, const String description, const String categoryName, int flags), который принимает следующие параметры:

  • shortName — краткое название пункта меню;
  • description — его описание;
  • categoryName — категория;
  • flags — флаги.
  • Флагами являются идентификаторы перечислимого типа enum ApplicationCommandInfo::CommandFlags:

  • isDisabled — пункт меню недоступен для выбора пользователем;
  • isTicked — рядом с пунктом стоит галочка;
  • wantsKeyUpDownCallbacks — команда вызывается дважды;
  • hiddenFromKeyEditor — делает команду недоступной для включения в список команд объекта класса KeyMappingEditorComponent (хранит карту клавиатурных комбинаций);
  • readOnlyInKeyEditor — делает команду доступной для включения в список команд объекта класса KeyMappingEditorComponent (хранит карту клавиатурных комбинаций), но не позволяет менять ассоциированную клавиатурную комбинацию;
  • dontTriggerVisualFeedback — если команда была вызвана с помощью клавиатурной комбинации, пункты меню на это не реагируют.
  • В нашем примере параметр ). Аналогично функция void ApplicationCommandInfo::setActive(bool isActive) throw() может делать пункт меню активным или неактивным, устанавливая или сбрасывая флаг isDisabled.

    И наконец, рассмотрим метод ). Их работа понятна из комментариев.

    bool TCentralComponent::perform(const InvocationInfo Info)
    {
      switch(Info.commandID)
      {
        case LanguageEnglish:
        {
          // Отмечаем пункт меню - английский
          bEnglish = true;
          bRussian = false;
          break;
        }
        case LanguageRussian:
        {
          // Отмечаем пункт меню - русский
          bEnglish = false;
          bRussian = true;
          break;
        }
      }
      
      // Меняем язык интерфейса
      LanguageChange();
    
      return true;
    }
    

    Краткие итоги

    Для создания меню верхнего уровня вашего приложения необходимо описать набор команд и связать его с соответствующими в модели меню (MenuBarModel). За отображение пунктов меню и выпадающих меню отвечает класс MenuBarComponent. Как выпадающее, так и контекстное меню создаётся объектами класса PopupMenu.

    Дополнительные материал

    Архив с исходными текстами примеровВы можете скачать здесь

    Страницы:

    Цель лекции: Научиться создавать и использовать главное и контекстное меню в своих программах

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

    Основными типами меню являются:

  • меню верхнего уровня, представляющее собой полосу с набором команд в верхней части главного окна программы и
  • контекстное меню, представляющее собой прямоугольную область с рядом пунктов (обычно вертикально расположенных), вызываемое щелчком правой кнопкой мыши по тому или иному виджету.
  • Меню верхнего уровня является результатом совместной работы нескольких классов компонентов: MenuBarComponent, который создаёт полосу под заголовком окна и отвечает за отображение команд, которые (вместе с их обработчиками) создаёт MenuBarModel, а также PopupMenu, объекты которого образуют выпадающие меню и подменю.

    Класс PopupMenu создаёт также и контекстное меню.

    Конструктор MenuBarComponent в качестве параметра принимает указатель на объект MenuBarModel: MenuBarComponent::MenuBarComponent(MenuBarModel* model). В случае, если мы создаём меню главного окна приложения (а на практике именно так и происходит), то удобнее воспользоваться методом класса DocumentWindow void DocumentWindow::setMenuBar(MenuBarModel* menuBarModel, int menuBarHeight = 0), который также принимает в качестве параметра указатель на объект MenuBarModel, который и формирует внешний вид меню. Второй принимаемый параметр — это ширина меню; в случае, если он принимает значение 0, то ширина полосы меню будет установлена по умолчанию.

    Если функция setMenuBar принимает нулевое значение, любое существующее меню будет удалено из главного окна и тогда мы можем добавить объект MenuBarComponent с помощью метода void DocumentWindow::setMenuBarComponent(Component* newMenuBarComponent). Получить текущее меню можно с помощью функции-члена класса DocumentWindow Component* DocumentWindow::getMenuBarComponent() const.

    Рассмотрим создание меню верхнего уровня и контекстного меню на примере простого приложения (рис 15.1).

    Для начала создадим основу нашего меню — её модель. Пусть наши меню, как верхнего уровня, так и контекстное, включают три команды: "Новый", "Открыть" и "Выход". При этом реально работать (осуществлять выход из программы) будет только последняя; при щелчке по первым двум будет выводиться информационное сообщение.

    (рис 15.1) Приложение, демонстрирующее работу меню.

    В качестве модели меню будет выступать класс компонента содержимого, который мы унаследуем от класса ).

    #ifndef _TCentralComponent_h_
    #define _TCentralComponent_h_
    //------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    //------------------------------------------
    // Класс компонента содержимого.
    // Наследует класс модели меню
    class TCentralComponent  : public Component,
            public MenuBarModel
    {
    public:
      TCentralComponent();
      ~TCentralComponent();
    
      // Идентификаторы команд меню
      enum TMenuOptions
      {
        FileNew = 1,
        FileOpen = 2,
        FileQuit = 3
      };
    
      void paint(Graphics);
      void resized();
      // Функции, наследуемые от MenuBarModel
      const StringArray getMenuBarNames();
      void menuItemSelected(int, int);
      const PopupMenu getMenuForIndex(int, const String);
      
      // Вызывает контекстное меню
      void mouseDown(const MouseEvent);
    
    private:
      // Ярлык для вывода сообщений
      Label* pMessagesLabel;
    
      // Изображения - пиктограммы меню
      Image ImageNew_png;
      Image ImageOpen_png;
      Image ImageExit_png;
    
      // Предотвращает создание копии конструктора и оператора =
      TCentralComponent(const TCentralComponent);
      const TCentralComponent operator= (const TCentralComponent);
    };
    //---------------------------------------------
    #endif
    

    Класс ), которые должны быть переопределены в классе компонента содержимого.

    Метод ).

    const StringArray TCentralComponent::getMenuBarNames()
    {
        // Создаются основные пункты меню
        StringArray sMenuNames;
        sMenuNames.add(tr("Файл"));
        return sMenuNames;
    }
    

    Метод virtual void MenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) вызывается всякий раз, когда пользователь щёлкает по тому или иному пункту меню. Идентификатор команды, вызываемой пользователем, и является первым параметром функции. Второй параметр — идентификатор того меню верхнего уровня, в котором была выбрана команда menuItemID.

    Именно в этом методе и находятся обработчики команд меню, выбираемых по их числовым идентификаторам ().

    void TCentralComponent::menuItemSelected(int iMenuItemID, int iTopLevelMenuIndex)
    {
      // Обработчики команд меню
      switch(iMenuItemID)
      {
      // Если выбран пункт "Выход"...
        case FileQuit:
        {
           // Завершаем работу программы
          JUCEApplication::quit();
          break;
        }
        // Если выбран пункт "Новый"...
        case FileNew:
        {
          // показываем сообщение
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"),  
                tr("Выбран пункт \"Новый\""), tr("Да"), 0);
          break;
        }
        // Если выбран пункт "Открыть"...
        case FileOpen:
        {
          // показываем сообщение
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"), 
               tr("Выбран пункт \"Открыть\""), tr("Да"), 0);
          break;
        }
      }
    }
    

    Метод ).

    const PopupMenu TCentralComponent::getMenuForIndex(int iIndex, 
                const String sMenuName)
    {
      // Создаём выпадающее меню
      PopupMenu Menu;
    
      switch(iIndex)
      {
        // Индекс 0 соответствует пункту меню "Файл"
        // (см. getMenuBarNames)
        case 0:
        {
          // Добавляем новые пункты в меню "Файл"
          Menu.addItem(FileNew, tr("Новый"), true, false, ImageNew_png);
          Menu.addItem(FileOpen, tr("Открыть"), true, false, ImageOpen_png);
          // Добавляем разделитель
          Menu.addSeparator();
          Menu.addItem(FileQuit, tr("Выход"), true, false, ImageExit_png);
          break;
        }
      }
    
      return Menu;
    }
    

    Для добавления новых пунктов в выпадающее меню мы воспользовались методом void PopupMenu::addItem(int itemResultId, const String itemText, bool isEnabled = true, bool isTicked = false const Image iconToUse = Image::null) , класса PopupMenu. Метод принимает следующие параметры:

  • itemResultId — число, возвращаемое методом show класса PopupMenu, когда пользователь выбирает пункт меню; как понятно, в качестве этого параметра передаётся идентификатор соответствующей команды;
  • itemText — текст пункта меню для отображения;
  • isEnabled — если параметр принимает значение true, пункт меню окрашивается в серый цвет и не может быть выбран;
  • isTicked — если параметр принимает значение true, рядом с пунктом меню отображается галочка;
  • iconToUse — если параметр не равен нулю, с левой стороны пункта меню отображается пиктограмма (icon).
  • Для того, чтобы удалить все пункты из меню, необходимо вызвать метод void PopupMenu::clear().

    Практически также создаётся контекстное меню. Для его отображения нам необходимо переопределить функцию — обработчик события нажатия пользователем кнопки мыши ().

    void TCentralComponent::mouseDown(const MouseEvent Event)
    {
      // Если пользователь пытается вызвать контекстное меню...
      if(Event.mods.isPopupMenu())
      {
        // создаём его...
        PopupMenu LabelPopupMenu;
        // и добавляем в него пункты
        LabelPopupMenu.addItem(FileNew, tr("Новый"), true, false, 
                ImageNew_png);
        LabelPopupMenu.addItem(FileOpen, tr("Открыть"), true, false,
                ImageOpen_png);
        LabelPopupMenu.addSeparator();
        LabelPopupMenu.addItem(FileQuit, tr("Выход"), true, false, 
                ImageExit_png);
    
        // Показываем контекстное меню.
        const int iResult = LabelPopupMenu.show();
    
        // В зависимости от идентификатора команды
        // вызываем тот или иной обработчик.
        if(iResult == FileNew)
        {
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"), 
                tr("Выбран пункт \"Новый\""), tr("Да"), 0);
        }
        else if(iResult == FileOpen)
        {
          AlertWindow::showMessageBox(AlertWindow::InfoIcon, tr("Информация"),                      
                tr("Выбран пункт \"Открыть\""), tr("Да"), 0);
        }
        else if(iResult == FileQuit)
        {
          JUCEApplication::quit();
        }
      }
    }
    

    Поскольку контекстное меню вызывается нажатием правой кнопки мыши, а метод mouseDown вызывается, когда пользователь нажимает любую кнопку, мы предусмотрели соответствующее условие выполнения: мы вызываем публичное свойство класса MouseEvent const ModifierKeys MouseEvent::mods, которое представляет собой ключевой модификатор, ассоциированный с этим событием. Метод bool ModifierKeys::isPopupMenu() const throw() класса ModifierKeys позволяет узнать, было ли это событие связано с вызовом пользователем контекстного меню. Для подобной проверки рекомендуется вызывать именно этот метод, а не bool ModifierKeys::isRightButtonDown() const throw(), информирующий о нажатии правой кнопки мыши, исходя из соображений совместимости с Mac OS X, где контекстное меню вызывается как нажатием на клавишу <CTRL>, так и правой кнопкой мыши.

    После этого создаётся контекстное меню, в которое добавляются пункты уже знакомым по меню верхнего уровня способом.

    Показ контекстного меню осуществляет уже упоминавшаяся выше функция int PopupMenu::show(int itemIdThatMustBeVisible = 0, int minimumWidth = 0, int maximumNumColumns = 0, int standardItemHeight = 0, ModalComponentManager::Callback * callback = 0). Она принимает следующие параметры:

  • int itemIdThatMustBeVisible — идентификатор пункта меню, который должен отображаться в нём при первом запуске (быть обязательно видимым). Если меню содержит слишком много пунктов, то оно будет прокручено до этого пункта;
  • int minimumWidth — минимальная ширина меню в пикселях;
  • int maximumNumColumns — число столбцов для упорядочения пунктов меню, если они не помещаются в одном вертикальном. Если параметр принимает значение 0, то будет установлено число столбцов по умолчанию (обычно около 7);
  • int standardItemHeight — если значение параметра не равно нулю, оно используется для задания стандартной высоты элементов меню;
  • ModalComponentManager::Callback * callback — если значение параметра не равно нулю, меню будет запущено асинхронно.
  • В нашем примере мы оставили все параметры принятыми по умолчанию.

    В дальнейшем в зависимости от идентификатора команды мы вызываем тот или иной её обработчик ().

    Внешний вид программы с вызванным контекстным меню представлен на рисунке 15.2 .

    (рис 15.2) Работа программы с вызванным контекстным меню

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

    (рис 15.3) Работа программы, демонстрирующей совместное использование компонентов меню и ApplicationCommandManager

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

    Объект класса ApplicationCommandManager хранит список команд, которые может выполнять программа, и вызывает их по мере необходимости. С помощью ApplicationCommandManager можно организовать совместное реагирование на общие команды меню, кнопок панели инструментов и других компонентов управления, подобно действиям (класс QAction) библиотеки Qt.

    Объявим объект класса

    #ifndef _TMainForm_h_
    #define _TMainForm_h_
    //--------------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    //--------------------------------------------------
    class TCentralComponent;
    //--------------------------------------------------
    class TMainForm : public DocumentWindow
    {
    public:
      TMainForm();
      ~TMainForm();
    
      void closeButtonPressed();
      
      // Менеджер команд приложения
      ApplicationCommandManager CommandManager;
      
    private:
      // Компонент содержимого
      TCentralComponent* pCentralComponent;
    };
    //----------------------------------------
    #endif
    

    Приёмником для команд, посылаемых ).

    #include "TMainForm.h"
    //-------------------------------------------------
    #include "TCentralComponent.h"
    //-------------------------------------------------
    TMainForm::TMainForm() : DocumentWindow(
        JUCEApplication::getInstance()->getApplicationName(),
        Colours::azure,
        DocumentWindow::allButtons)
    {
      centreWithSize(700, 400);
      setResizable(true, false);
      setVisible(true);
    
      // Динамически создаём экземпляр класса нашего компонента
      pCentralComponent = new TCentralComponent(this);
      // Устанавливаем его в качестве компонента содержимого
      setContentOwned(pCentralComponent, true);
      
      // Регистрируем все созданные команды компонента содержимого
      CommandManager.registerAllCommandsForTarget(pCentralComponent);
      // Регистрируем все текущие команды приложения
      CommandManager.registerAllCommandsForTarget(JUCEApplication::getInstance());
    
      setMenuBar(pCentralComponent);
    }
    //-------------------------------------------------
    TMainForm::~TMainForm()
    {
        setMenuBar(NULL);
    }
    //-------------------------------------------------
    void TMainForm::closeButtonPressed()
    {
        JUCEApplication::getInstance()->systemRequestedQuit();
    }
    //-------------------------------------------------
    

    Класс

  • Virtual ApplicationCommandTarget* ApplicationCommandTarget::getNextCommandTarget() - возвращает следующий после текущего приёмник команд;
  • virtual void ApplicationCommandTarget::getAllCommands(Array< CommandID > commands) — возвращает список всех команд, которые могут быть обработаны;
  • virtual void ApplicationCommandTarget::(CommandID commandID, ApplicationCommandInfo result) — предоставляет всю необходимую информацию по обрабатываемой команде; не считая идентификатора команды, которым является первый параметр метода, эта информация содержится в объекте ApplicationCommandInfo, который предварительно должен быть создан и инициализирован;
  • virtual bool ApplicationCommandTarget::perform(const InvocationInfo info) — запускает команду на выполнение, получая всю необходимую информацию о ней из объекта InvocationInfo, конструктор которого в качестве параметра принимает идентификатор команды. Если все обработчики команд меню находятся в этой функции, то ранее рассмотренный метод menuItemSelected следует оставить пустым.
  • #ifndef _TCentralComponent_h_
    #define _TCentralComponent_h_
    //------------------------------------------------
    #include "../JuceLibraryCode/JuceHeader.h"
    #include "TMainForm.h"
    //------------------------------------------------
    // Класс компонента содержимого.
    // Наследует класс приёмника команд
    class TCentralComponent  : public Component,
                public MenuBarModel,
                public ApplicationCommandTarget
    {
    public:
      TCentralComponent(TMainForm* pParent);
      ~TCentralComponent();
    
      // Идентификаторы команд меню
      enum TMenuOptions
      {
        FileNew = 1,
        FileOpen = 2,
        FileQuit = 3,
        LanguageEnglish = 4,
        LanguageRussian = 5
      };
    
      void paint(Graphics);
      void resized();
      void mouseDown(const MouseEvent);
      const StringArray getMenuBarNames();
      void menuItemSelected(int, int);
      const PopupMenu getMenuForIndex(int, const String);
      
      // Функции, наследуемые от ApplicationCommandTarget
      // Возвращает следующий после текущего приёмник команд
      ApplicationCommandTarget* getNextCommandTarget();
      // Возвращает список всех команд, которые могут быть обработаны
      void getAllCommands(Array<int>);
      // Предоставляет всю необходимую информацию по обрабатываемой команде
      void getCommandInfo(CommandID, ApplicationCommandInfo);
      // Запускает команду на выполнение
      bool perform(const InvocationInfo);
      
      // Обработчик смены команд интерфейса
      void LanguageChange();
    
    private:
      // Флаги, какой язык выбран
      bool bEnglish;
      bool bRussian;
      // Родительский компонент
      TMainForm* pParent;
      // Ярлык для вывода сообщений
      Label* pMessagesLabel;
      
      // Строки интерфейса
      String sMessagesLabelText;
      String sFileMenuText;
      String sLanguageMenuText;
      String sFileNewMenuText;
      String sFileOpenMenuText;
      String sFileQuitText;
      String sMessageTitleText;
      String sMessageNewText;
      String sMessageOpenText;
      String sYesButtonText;
    
      Image ImageNew_png;
      Image ImageOpen_png;
      Image ImageExit_png;
    
      // Предотвращает создание копии конструктора и оператора =
      TCentralComponent(const TCentralComponent);
      const TCentralComponent operator= (const TCentralComponent);
    };
    //---------------------------------------------------
    #endif
    

    Как видно из листинга 15.8 , в нашем новом приложении в список идентификаторов команд меню мы добавили две новые: LanguageEnglish и LanguageRussian. Именно эти команды будут обрабатываться с помощью методов ApplicationCommanTarget, а остальные — ранее описанным способом.

    Кроме того, мы предусмотрели метод ).

    Следует понимать, что подобный способ перевода интерфейса, хотя и работает, но приведён исключительно как пример использования командного менеджера. Локализацию приложений, написанных на основе Juce, удобнее осуществлять с помощью класса LocalisedStrings, описание которого дано в лекции 22 .

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

    #include "TCentralComponent.h"
    #include"TImagesFactory.h"
    //-------------------------------------------------------
    #define tr(s) String::fromUTF8(s)
    //-------------------------------------------------------
    TCentralComponent::TCentralComponent(TMainForm* pParent) 
                : Component("Central Component"),
                pMessagesLabel(0)
    {
      this->pParent = pParent;
      
      pMessagesLabel = new Label(L"Messages Label", "");
      pMessagesLabel->setFont(Font(15.0000f, Font::plain));
      pMessagesLabel->setJustificationType(Justification::centred);
      pMessagesLabel->setEditable (false, false, false);
      pMessagesLabel->setColour(Label::outlineColourId, Colours::black);
      pMessagesLabel->setColour(Label::textColourId, Colours::black);
      pMessagesLabel->setColour(Label::backgroundColourId, Colours::azure);
      addAndMakeVisible(pMessagesLabel);
    
      ImageExit_png = ImageCache::getFromMemory(TImagesFactory::psExit_png, 
                TImagesFactory::iExit_pngSize);
      ImageNew_png = ImageCache::getFromMemory(TImagesFactory::psNew_png, 
                TImagesFactory::iNew_pngSize);
      ImageOpen_png = ImageCache::getFromMemory(TImagesFactory::psOpen_png, 
                TImagesFactory::iOpen_pngSize);
    
      setSize (700, 400);
      
      bEnglish = true;
      bRussian = false;
      
      LanguageChange();
    }
    //-----------------------------------------------------
    TCentralComponent::~TCentralComponent()
    {
      // Удаляем дочерние виджеты
      // и обнуляем их указатели
      deleteAllChildren();
    }
    //-----------------------------------------------------
    void TCentralComponent::LanguageChange()
    {
      if(bEnglish)
      {
        sMessagesLabelText = tr(
        quot;Choose an option of the main menu \nor right-click outside of the label 
        to invoke a context menu.");
        sFileMenuText = tr("File");
        sLanguageMenuText = tr("Language");
        sFileNewMenuText = tr("New");
        sFileOpenMenuText = tr("Open");
        sFileQuitText = tr("Quit");
        sMessageTitleText = tr("Information");
        sMessageNewText = tr("Option \"New\" was selected");
        sMessageOpenText = tr("Option \"Open\" was selected");
        sYesButtonText = tr("Yes");
      }
      else if(bRussian)
      {
        sMessagesLabelText = tr(
        "Выберите пункт главного меню \nили щёлкните правой кнопкой мыши 
        за пределами надписи для того, 
        чтобы вызвать контекстное меню.");
        sFileMenuText = tr("Файл");
        sLanguageMenuText = tr("Язык");
        sFileNewMenuText = tr("Новый");
        sFileOpenMenuText = tr("Открыть");
        sFileQuitText = tr("Выход");
        sMessageTitleText = tr("Информация");
        sMessageNewText = tr("Выбран пункт \"Новый\"");
        sMessageOpenText = tr("Выбран пункт \"Открыть\"");
        sYesButtonText = tr("Да");
      }
      
      pMessagesLabel->setText(sMessagesLabelText, false);
      getMenuBarNames();
      // Перерисовываем пункты меню с учётом нового языка
      menuItemsChanged();
    }
    //--------------------------------------------------
    

    В конструкторе компонента содержимого мы вызываем наш метод LanguageChange для задания строк интерфейса приложения. Также эта функция будет вызываться всякий раз, когда пользователь выбирает новый пункт в меню "Язык". Флагом выбора того или иного языка выступают булевые переменные bEnglish и bRussian. Для того, чтобы принудительно перерисовать пункты меню, исходя из новых языковых настроек, мы вызываем метод void MenuBarModel::menuItemsChanged().

    Рассмотрим реализацию функций, унаследованных от класса ).

    ApplicationCommandTarget* TCentralComponent::getNextCommandTarget()
    {getAllCommands
        return findFirstTargetParentComponent();
    }
    

    Метод , )

    void TCentralComponent::getAllCommands(Array<int> iCommands)
    {
      // Регистрация команд для командного менеджера
      const CommandID IDs[] =
      {
        LanguageEnglish,
        LanguageRussian
      };
      
      iCommands.addArray(IDs, numElementsInArray(IDs));
    }
    

    Метод ).

    void TCentralComponent::getCommandInfo(CommandID ID, ApplicationCommandInfo 
                Result)
    {
      switch(ID)
      {
        case LanguageEnglish:
        {
          Result.setInfo(L"English", L"Switch to English", "Language", 0);
          Result.setTicked(bEnglish);
          break;
        }
        case LanguageRussian:
        {
          Result.setInfo(tr("Русский"), L"Switch to Russian", "Language", 0);
          Result.setTicked(bRussian);
          break;
        }
      }
    }
    

    Как мы уже упоминали, за это отвечает объект класса ApplicationCommandInfo. Отображение соответствующих команде пунктов меню в программе мы задаём вызовом метода void ApplicationCommandInfo::setInfo(const String shortName, const String description, const String categoryName, int flags), который принимает следующие параметры:

  • shortName — краткое название пункта меню;
  • description — его описание;
  • categoryName — категория;
  • flags — флаги.
  • Флагами являются идентификаторы перечислимого типа enum ApplicationCommandInfo::CommandFlags:

  • isDisabled — пункт меню недоступен для выбора пользователем;
  • isTicked — рядом с пунктом стоит галочка;
  • wantsKeyUpDownCallbacks — команда вызывается дважды;
  • hiddenFromKeyEditor — делает команду недоступной для включения в список команд объекта класса KeyMappingEditorComponent (хранит карту клавиатурных комбинаций);
  • readOnlyInKeyEditor — делает команду доступной для включения в список команд объекта класса KeyMappingEditorComponent (хранит карту клавиатурных комбинаций), но не позволяет менять ассоциированную клавиатурную комбинацию;
  • dontTriggerVisualFeedback — если команда была вызвана с помощью клавиатурной комбинации, пункты меню на это не реагируют.
  • В нашем примере параметр ). Аналогично функция void ApplicationCommandInfo::setActive(bool isActive) throw() может делать пункт меню активным или неактивным, устанавливая или сбрасывая флаг isDisabled.

    И наконец, рассмотрим метод ). Их работа понятна из комментариев.

    bool TCentralComponent::perform(const InvocationInfo Info)
    {
      switch(Info.commandID)
      {
        case LanguageEnglish:
        {
          // Отмечаем пункт меню - английский
          bEnglish = true;
          bRussian = false;
          break;
        }
        case LanguageRussian:
        {
          // Отмечаем пункт меню - русский
          bEnglish = false;
          bRussian = true;
          break;
        }
      }
      
      // Меняем язык интерфейса
      LanguageChange();
    
      return true;
    }
    

    Краткие итоги

    Для создания меню верхнего уровня вашего приложения необходимо описать набор команд и связать его с соответствующими в модели меню (MenuBarModel). За отображение пунктов меню и выпадающих меню отвечает класс MenuBarComponent. Как выпадающее, так и контекстное меню создаётся объектами класса PopupMenu.

    Дополнительные материал

    Архив с исходными текстами примеровВы можете скачать здесь

    Вернуться к учебному плану