Design approach for interface with many input widgets
I have an interface that has ~16 input field boxes. They are all declared as public pointers within a class, initialized, etc. However, as my code has grown more and more with private functions doing database committals, error checking, temporary storage routines, etc it has become very painful if a field has to get removed or a new one added I must delve into all these private functions and explicitly remove/add the field; and always with regard to the field ordering.
There has to be a simpler way!
This is my idea and am hoping anyone can shoot it down or build upon it:
My thought is to store pointers to all the input fields in a array of pointers and then all these private helper functions walk the array; however some of these private functions are static some are non-static; thus some more pointer magic is required, or should I have two of these array-of-pointer functions: one for static functions to use and one for non-static functions to use?
To further complicate things, the method invoked upon the widgets varies depending on what the private function is doing... Some may call "->value(foo)" some may call "->location(1),->location(2)," incrementing in order of the widgets. Is there a way to pass the method invoked and the parameters to be passed to this new helper function containing the array of input field pointers?
Food for thought: Maybe I'm trying to get too fancy by saving myself the burden of scrolling all around my code whenever I need to make a change? Maybe this will add too much overhead with all the extra pointer indirection? Is it better to suffer?
Thanks, any help is appreciated. If some code examples are really required I can churn out some.
Code Example (this won't compile and is typed freehand as an example)
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());
}
You could possible add a container of InputBox
as a member to Foo
and iterate it to make life simpler.
#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
My take on what you said is that your code is getting to complex to follow. I see the problem as it's one big lump that is too hard to follow. So the response should be to break it up into smaller more manageable chunks.
You might consider adopting something like the M.V.C. Pattern. The basic idea is you break the code into three sections. The "Model" part handles all database activity. The "View" part handles all GUI interaction. The "Controller" part handles implementing the logic. This will help make the huge-lump-o-code™ a little easier to maintain.
What about the possibility of wrapping your widgets in a class that is able to "self-initialize" itself when you add them to the parent-window class? In other words you can have a method in the parent_window
class that would be something like add_widget(widget_type* widget)
, where class widget_type
would be an abstract base-class object with a standard interface. The main interface component would either be a constructor, or a method that would take a pointer to the parent_window
class, i.e., widget_type::widget_type(parent_window* window)
. Now that the widget has a pointer to the parent, it can call any methods necessary in the parent_window
class for initialization. Also the parent_window
class would then take ownership of the passed-in pointer to the widget_type
so that the widget could be destroyed properly when the parent is destroyed.
So I'm thinking at a very abstract level, your code might look something like:
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 { /*...*/ };
You would then make calls something like:
parent_window window;
window.add_widget(new derived_widget_type(optional_arg_val));
window.add_widget(new another_widget_type(optional_arg_val));
Now you won't have to worry about changing your parent_window
class every time you want to add a new widget type. The main thing would be to make sure that the widgets have access to all the methods and data-structures in the parent necessary to initialize and destroy themselves properly.
Edit: Made some changes to the code above to better reflect some of the methods in the new code additions by the OP
- You're NOT getting too fancy. Repetitive code for GUI interaction is a real problem.
Iterating through a vector of guiElements is a good solution. You want a base guiElement class that supports clear(), load() and print() as virtual functions. You can handle differences such as clearing to zero or clearing to blank in your sub classes.
Even so, you will still end up with each field having influence on multiple places in your code. You'll also likely find yourself writing very similar code for doing much the same thing when you add additional dialogs.
For an EDA application with over 1000 parameter fields I used a combined approach - both iterators and code/document generation. The generator wrote the code that had the iterators and it handled special cases. To allow custom data validations field identifiers needed to appear in header files, and I could not have done that using only the iterator approach. I also had to have document generation to generate memory map and help documentation from the field info - so adding code generation wasn't a big additional cost. I held the source information about each field in one place, a YAML file. A change in the specs and I could make the changes I needed just once by changes in the YAML file.
I wrote my custom code/document generator in C++. Done again I'd adapt the cog code generator, which is elegantly simple and just right for this kind of semi-repetitive code. Think of cog as macros on steroids, with the advantage that you get real code to look at that you can step through and debug.
In Sum
- Use an iterator, iterating through guiElements. There's a good chance it will be enough for your needs.
- If you need more, look into code generation with cog so that you can have all the field information in one place.
精彩评论