Дизайн подход для интерфейса со многими входными виджетами - PullRequest
2 голосов
/ 12 мая 2011

У меня есть интерфейс с ~ 16 полями ввода.Все они объявлены как открытые указатели внутри класса, инициализированы и т. Д. Однако, так как мой код все больше и больше растет благодаря частным функциям, выполняющим коммитты базы данных, проверку ошибок, процедуры временного хранения и т. Д., Становится очень больно, если поле должноудалить или добавить новый Я должен вникнуть во все эти частные функции и явно удалить / добавить поле;и всегда в отношении порядка полей.

Там имеет , чтобы быть более простым способом!

Это моя идея, и я надеюсь, что любой может ее сбитьили опираться на него:

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

Чтобы еще больше усложнить ситуацию,Метод, вызываемый для виджетов, варьируется в зависимости от того, что делает частная функция ... Некоторые могут вызывать "-> value (foo)" некоторые могут вызывать "-> location (1), -> location (2)," увеличивая по порядкуиз виджетов.Есть ли способ передать вызванный метод и параметры для этой новой вспомогательной функции, содержащей массив указателей на поля ввода?

Пища для размышлений: Может быть, я пытаюсьслишком увлекаться, избавляя себя от необходимости прокручивать весь код, когда мне нужно что-то изменить?Может быть, это добавит слишком много накладных расходов со всей дополнительной косвенностью указателя?Лучше ли страдать?

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

Пример кода (он не компилируется и печатается от руки в качестве примера)

class Foo
{
public:
   InputBox * in1;
   InputBox * in2;
   InputBox * in3;
   ExternalDataSource * exds; // Pretend this object you can retrieve values out of
private:
   static void clearFieldsFunc1(void * v); // callback bound to a button
   static void loadFieldFunc2(void * v);  // callback bound to a button
   void printFieldsFunc3(); // not a callback, just called from various functions
}

Foo::Foo()
{
   in1= new InputBox (0,0,10,10);  // Box x,y,w,h
   in2= new InputBox (15,0,10,10);
   in3= new InputBox (30,0,10,10);
   exds = new ExernalDataSource("US Intelligence Agency");
}

// Clears the fields
void Foo::clearFieldsFunc1(void * v)
{
   Foo * fptr = ((Foo*)v);
   fptr->in1->clear();
   fptr->in2->clear();
   fptr->in3->clear();
}

// Loads the fields
void Foo::loadFieldFunc2(void * v)
{
   Foo * fptr = ((Foo*)v);
   fptr->in1->value(fptr->exds->getValue(1));
   fptr->in2->value(fptr->exds->getValue(2));
   fptr->in3->value(fptr->exds->getValue(3));
}

// Prints the fields
void Foo::printFieldsFunc3()
{
   printf("%s\n",this->in1->value());
   printf("%s\n",this->in2->value());
   printf("%s\n",this->in3->value());
}

Ответы [ 4 ]

1 голос
/ 12 мая 2011

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

#include <vector>

    class Foo
    {
    private:
       static void clearFieldsFunc1(void * v); // callback bound to a button
       static void loadFieldFunc2(void * v);  // callback bound to a button
       void printFieldsFunc3(); // not a callback, just called from various functions

       std::vector<InputBox> m_inputBoxes;
       typedef std::vector<InputBox>::iterator InputItr;
    };

    Foo::Foo()
    {
       m_inputBoxes.push_back(InputBox(0, 0, 10, 10));
       m_inputBoxes.push_back(InputBox(15, 0, 10, 10));
       m_inputBoxes.push_back(InputBox(30, 0, 10, 10));
    }

    // Clears the fields
    void Foo::clearFieldsFunc1(void * v)
    {
       for(InputItr itr(m_inputBoxes.begin()); itr != m_inputBoxes.end(); ++itr)
           itr->clear(); // calls clear for each InputBox
    }

    // etc
1 голос
/ 12 мая 2011

Мое мнение о том, что вы сказали, состоит в том, что ваш код становится все более сложным. Я вижу проблему в том, что это один большой кусок, которому слишком трудно следовать. Таким образом, ответ должен быть разбит на более мелкие и более управляемые куски.

Вы можете подумать о принятии чего-то вроде M.V.C. Узор . Основная идея заключается в том, что вы разбиваете код на три части. Часть «Модель» обрабатывает все действия базы данных. Часть «Просмотр» обрабатывает все взаимодействия GUI. Часть «Контроллер» управляет реализацией логики. Это поможет немного упростить поддержку огромного кода-кода ™.

0 голосов
/ 12 мая 2011
  • Вы НЕ становитесь слишком модными.Повторяющийся код для взаимодействия с GUI представляет собой реальную проблему.

Итерация по вектору guiElements является хорошим решением.Вам нужен базовый класс guiElement, который поддерживает clear (), load () и print () как виртуальные функции.Вы можете обрабатывать различия, такие как очистка до нуля или очистка до пустых в ваших подклассах.

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

Для приложения EDA с более чем 1000 полями параметров я использовал комбинированный подход - итераторы, и генерация кода / документа.Генератор написал код, содержащий итераторы, и он обрабатывал особые случаи.Чтобы разрешить идентификаторы полей пользовательских валидаций данных должны появляться в заголовочных файлах, и я не смог бы сделать это, используя только итераторский подход.Мне также нужно было сгенерировать документ, чтобы сгенерировать карту памяти и справочную документацию на основе информации о поле, поэтому добавление генерации кода не было большой дополнительной затратой.Я держал исходную информацию о каждом поле в одном месте, файл YAML.Изменение в спецификациях, и я мог внести необходимые изменения всего один раз, изменив файл YAML.

Я написал свой собственный генератор кода / документа на C ++.Сделав еще раз, я бы адаптировал генератор Cog-кода , который элегантно прост и подходит именно для этого вида полуповторного кода.Думайте о cog как о макросах на стероидах, с тем преимуществом, что вы получаете реальный код, на который вы можете смотреть и отлаживать его.

В сумме

  • Использовать итератор, выполняющий итерации по элементам управления.Есть большая вероятность, что этого будет достаточно для ваших нужд.
  • Если вам нужно больше, изучите процесс генерации кода с помощью cog, чтобы вы могли хранить всю информацию о полях в одном месте.
0 голосов
/ 12 мая 2011

А как насчет возможности обернуть ваши виджеты в класс, который способен «самоинициализироваться» сам при добавлении их в класс родительского окна?Другими словами, у вас может быть метод в классе parent_window, который будет выглядеть как add_widget(widget_type* widget), где class widget_type будет абстрактным объектом базового класса со стандартным интерфейсом.Основной компонент интерфейса будет либо конструктором, либо методом, который будет принимать указатель на класс parent_window, т. Е. widget_type::widget_type(parent_window* window).Теперь, когда виджет имеет указатель на родителя, он может вызывать любые методы, необходимые в классе parent_window для инициализации.Кроме того, класс parent_window получит владение переданным указателем на widget_type, чтобы виджет мог быть уничтожен должным образом при уничтожении родителя.

Так что я думаю об очень абстрактномуровень, ваш код может выглядеть примерно так:

class parent_window
{
    private:
        widget_type** widget_ptr_array;
        //... more data elements, private methods, etc.

    public:
        parent_window();            
        //... more public data, methods, etc.

        void add_widget(widget_type* widget) 
        { 
            widget->initialize(this);
            widget_ptr_array[CURRENT_EMPTY_SLOT] = widget;
        }

        void clear_fields()
        {
            for (int i=0; i < MAX_WIDGETS; i++)
            {
                widget_ptr_array[i]->clear_field();
            }
        }

        //for simplicity I'm assuming the size of 
        //the array "value" points to is appropriate in length
        void load_fields(some_type* value)
        {
            for (int i=0; i < MAX_WIDGETS; i++)
            {
                widget_ptr_array[i]->load_field(value[i]);
            }
        }

        ~parent_window()
        {
            for (int i=0; i < MAX_WIDGETS; i++)
            {
                widget_ptr_array[i]->destroy(this);
                delete widget_ptr_array[i];
            }

            delete [] widget_ptr_array;
        }
};

//somewhere else ...
class widget_type
{
    private:
        //...private data, methods, etc.
    public:
        widget_type();
        virtual void initialize(parent_window* window) = 0;
        virtual void destroy(parent_window* window) = 0;

        virtual void clear_field() = 0;
        virtual void load_field(some_type value) = 0;
};

class derived_widget_type: public widget_type { /*...*/ };
class another_widget_type: public widget_type { /*...*/ };

Затем вы будете делать вызовы примерно так:

parent_window window;
window.add_widget(new derived_widget_type(optional_arg_val));
window.add_widget(new another_widget_type(optional_arg_val));

Теперь вам не придется беспокоиться об изменении класса parent_windowкаждый раз, когда вы хотите добавить новый тип виджета.Главное, чтобы убедиться, что виджеты имеют доступ ко всем методам и структурам данных в родительском элементе, необходимым для правильной инициализации и уничтожения.

Редактировать: Внесены некоторые изменения в приведенный выше код для лучшего отражениянекоторые методы в новых дополнениях кода с помощью OP

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...