Какой лучший способ обмена действиями между отдельными классами GUI (меню, панели инструментов и т.д.)

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

Каков наилучший способ поделиться ими? Я использую Qt 5.3 с С++, но это в основном не зависит от какой-либо конкретной инфраструктуры GUI или языка.

Некоторые возможности:

  • Назначьте одно центральное место, скажем, в главном окне, чтобы создать все из них с их текстом, значком и обратным вызовом. Тогда:

    • Передайте действия конструктору при создании подкомпонентов графического интерфейса. Это может сделать списки параметров конструктора довольно длинными.

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

    • Поставьте получателей из главного окна и подкомпоненты получат нужные им действия. Подкомпоненты обычно имеют указатель на главное окно. Это делает так, что главное окно не знает, кто заботится о каком-либо действии, но также предоставляет множество публичных членов (если я не использую идиому Адвокат-Клиент или аналогичную).

    • Добавьте их в отдельный глобальный-хэш-репозиторий, где основное окно добавляет их, и пользователи по мере необходимости находят их по имени или ключу или что-то в этом роде. Это похоже на другие варианты, но немного облегчает проблемы и предоставляет только один параметризованный геттер, а не пучок конкретных геттеров. Даунсайд: добавляет объект global-ish, к которому все обращаются.

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

Какой лучший подход? Есть ли что-то лучшее, что я не перечислял здесь?

Ответ 1

Давайте сделаем шаг назад и посмотрим на созданное программное обеспечение. Обычно рекомендуется настраивать пользовательский интерфейс, чтобы пользователи могли создавать/изменять меню приложений и панели инструментов. Это требует создания панелей инструментов/меню с помощью файла конфигурации (.xml или .cfg), который просто привязывает элементы меню/панели инструментов к действиям.

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

Поэтому я бы рекомендовал 1.4. Вы можете либо создавать действия по запросу, используя ActionFactory, который получает имя действия/код в качестве аргумента, либо вы можете сделать обязательным для Actions зарегистрировать себя с помощью ActionRegistry (global!), Из которого они могут быть посмотрел вверх.

PS: Другой вариант использования для названных действий - это то, что если ваше программное обеспечение имеет SDK, вы можете легко предоставить API автоматизации (например, ApiExecuteAction(Actions.COPY)).

Ответ 2

Являются ли они необязательными или требуется ваш класс?

если ваш класс требует, чтобы они передавали их конструктору, вы говорите, что список аргументов станет длинным? то почему бы не сначала упаковать действия и объекты и не использовать метод factory для создания указанного объекта.

Если они не передают их с сеттерами и геттерами.

Что-то, что вы могли прочитать и рассмотреть, - это шаблон инъекции зависимостей, о котором вы можете прочитать здесь:

Что такое инъекция зависимостей?

Ответ 3

Проблема повторного использования QActions в некоторых диалоговых окнах заключается в повторном подключении сигналов.

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

template < class T >
class EditionSet
{
    T* parent;

  public:

    EditionSet( T* parent )
      : parent( parent )
    {
      cutAction = new QAction( "Cut", parent );
      copyAction = new QAction( "Copy", parent );
      pasteAction = new QAction( "Paste", parent );

      QObject::connect( cutAction, SIGNAL( triggered( ) ),
                        parent, SLOT( CutActionTriggered( ) ) );

      QObject::connect( copyAction, SIGNAL( triggered( ) ),
                        parent, SLOT( CopyActionTriggered( ) ) );

      QObject::connect( pasteAction, SIGNAL( triggered( ) ),
                        parent, SLOT( PasteActionTriggered( ) ) );
    }

    ~EditionSet( )
    {
      QObject::disconnect( cutAction, SIGNAL( triggered( ) ),
                           parent, SLOT( CutActionTriggered( ) ) );

      QObject::disconnect( copyAction, SIGNAL( triggered( ) ),
                           parent, SLOT( CopyActionTriggered( ) ) );

      QObject::disconnect( pasteAction, SIGNAL( triggered( ) ),
                           parent, SLOT( PasteActionTriggered( ) ) );

      delete cutAction;
      delete copyAction;
      delete pasteAction;
    }

    QAction* cutAction;
    QAction* copyAction;
    QAction* pasteAction;
};

class dialog : public QDialog
{
    Q_OBJECT

  public:

    dialog::dialog( QWidget* parent )
      : QDialog( parent ),
        ui( new Ui::dialog ),
        editionSet( EditionSet< dialog >( this ) )
    {
      // ...

      ui->mainToolBar->addAction( editionSet.cutAction );
      ui->mainToolBar->addAction( editionSet.copyAction );
      ui->mainToolBar->addAction( editionSet.pasteAction );
    }

  private:

    EditionSet< dialog > editionSet;
};

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

template < class T >
class EditionSet
{
    T* parent;
    QAction* cutAction;
    QAction* copyAction;
    QAction* pasteAction;

  public:

    EditionSet( T* parent )
      : parent( parent )
    {
      cutAction = new QAction( "Cut", parent );
      copyAction = new QAction( "Copy", parent );
      pasteAction = new QAction( "Paste", parent );

      QObject::connect( cutAction, SIGNAL( triggered( ) ),
                        parent, SLOT( CutActionTriggered( ) ) );

      QObject::connect( copyAction, SIGNAL( triggered( ) ),
                        parent, SLOT( CopyActionTriggered( ) ) );

      QObject::connect( pasteAction, SIGNAL( triggered( ) ),
                        parent, SLOT( PasteActionTriggered( ) ) );

    }

    ~EditionSet( )
    {
      QObject::disconnect( cutAction, SIGNAL( triggered( ) ),
                           parent, SLOT( CutActionTriggered( ) ) );

      QObject::disconnect( copyAction, SIGNAL( triggered( ) ),
                           parent, SLOT( CopyActionTriggered( ) ) );

      QObject::disconnect( pasteAction, SIGNAL( triggered( ) ),
                           parent, SLOT( PasteActionTriggered( ) ) );

      delete cutAction;
      delete copyAction;
      delete pasteAction;
    }

    void AddActionsTo( QWidget* container )
    {
      container->addAction( cutAction );
      container->addAction( copyAction );
      container->addAction( pasteAction );
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

  public:
    MainWindow(QWidget *parent = 0)
      : QMainWindow( parent ),
        ui( new Ui::MainWindow )
        editionSet( EditionSet< MainWindow >( this ) )
    {
      ui->setupUi(this);

      editionSet.AddActionsTo( ui->mainToolBar );
      editionSet.AddActionsTo( ui->menuBar );
    }

  private:

    EditionSet< MainWindow > editionSet;
};

Ответ 4

Я настоятельно рекомендую 1.1 или 1.2 как наименее тяжелое решение.

1.3 значительно расширяет публичный интерфейс класса окна очень утомительно.

1.4 отключает проблему глобальной уникальности с новым "пространством имен" - тем местом, где живет хранилище команд.

2.0 предоставляет много личной информации, мне кажется худшим для меня.

Кстати, если вы еще не прочитали шаблон Command, я рекомендую его.