[SOLVED] Generating a UI on the fly, Pt.2
-
For background info, see "this thread":http://developer.qt.nokia.com/forums/viewthread/6970/.
To summarize the situation, I want to generate a GUI on the fly using a DB table that looks like this:
@
PK FK to Group FK to Test
1 1 1
2 1 2
3 1 3
@I have decided to implement each test individually (each test is its own class). I am now wondering on the best way to selectively instantiate them. I have had two ideas.
Firstly, one could implement a function that is solely a huge switch statement and would take only an int as an argument. One would them iterate through the query results and call that function n number of times, when n is the number of selected tests in the DB. However, I have a lot of tests, and wondered if it made sense to have a switch statement with something like 300 cases?
Secondly, one could create a single QTabBar with EVERY test as a tab and only show the tabs as needed. Seems like a quick and dirty solution to me, though.
Thanks for your thoughts!
WARNING: Noob ramblings follow:
In my opinion, the first solution is neater, even though it requires a big switch statement. Instantiating 300 classes at app startup and keeping them in memory all the time seems like a bad idea, so forget about the second idea.
-
I am really not sure what you want to achieve. Your two proposed solutions sound like they solve totally different problems (and both in a bad way, IMHO). Is your problem how you present 300+ tests to a user to select in a GUI, or do you want to try to solve another problem?
-
@Andre, I'll try to summarize the situation as best as I can. Note that I also described my use case in the thread I linked to ("this one":http://developer.qt.nokia.com/forums/viewthread/6970/).
I have approximately 300 tests that can be performed. However, not all of them are performed simultaneously at any one time. There are groups that specify which test must be performed (a minimal example of this grouping is the table above).
Now, here's what I want to do. When it's time for data entry, I want to popup a dialog which content is generated automatically, depending on the contents of the table above. I am mostly thinking of a QTabBar that possess a tab for each test.
For a more concrete description, suppose I want to enter data for Group 1 of all my tests. In my app, I select Data Entry and a dialog pops up asking what Group I want to enter data for, then generates a data entry dialog based on my choice.
I hope I've summarized the situation for you.
Thanks for your feedback!
As for your humble opinion, I know those are not very robust solutions, hence my posting on this forum.
-
OK, so I guess you are looking for a solution for a design for your UI for this, then. Your description of what the actual problem is you want to solve, is still not really clear to me, even after reading the first topic again.
How much data do you expect to have to enter per test? Is that a single digit? Do you require a complete, complex form? In the last topic, you were suggested to look into QUiLoader to generate forms on the fly, but I understand that that does not pan out for you. I conclude from that, that your data entry needs per test can get quite complicated, right?
What might be a solution for your UI, is to create a list of your tests on the left (using a QListView or something similar). Each of your plugins with a test would register itself, and would register what tests it supports. You also make sure that your base class for those tests has a method to create a QWidget* that supplies a UI for each test it supports. In your list, you then collect all the tests as registered by the plugins. Now, you can use the right side of the dialog as an area where you dynamically create the widgets for the data entry when they are needed, by calling the ui creation method of the correct plugin and setting the container widget in your form as the parent for that new UI widget.
With a design like that, you can extend the number of tests without running into big issues.
-
[quote author="Andre" date="1309179631"]Do you require a complete, complex form? In the last topic, you were suggested to look into QUiLoader to generate forms on the fly, but I understand that that does not pan out for you. I conclude from that, that your data entry needs per test can get quite complicated, right? [/quote]
Yes, it can get quite complicated. Sometimes I have to enter data 12 to 15 numeric data points per test. Sometimes it is only a line of text. It vary a lot.
[quote author="Andre" date="1309179631"]
What might be a solution for your UI, is to create a list of your tests on the left (using a QListView or something similar). Each of your plugins with a test would register itself, and would register what tests it supports. [/quote]I'm not sure I understand this. You mean that each plugin would be a group of tests, effectively hardcoding my groups into the application? If so, this is not that useful, as the groupings overlap each other and can change over time (this information is stored in a PostgreSQL DB).
[quote author="Andre" date="1309179631"] You also make sure that your base class for those tests has a method to create a QWidget* that supplies a UI for each test it supports. In your list, you then collect all the tests as registered by the plugins. Now, you can use the right side of the dialog as an area where you dynamically create the widgets for the data entry when they are needed, by calling the ui creation method of the correct plugin and setting the container widget in your form as the parent for that new UI widget. [/quote]
Every test has its own class, and they inherit from abstract classes which in turn inherit from QWidget. So yes, they can create a QWidget.
Now, this solution is nice, but I still need to populate my QListView with data from the DB (table shown in first post). So now my switch statement will call a plugin instead of instantiating objects from my test classes, no? Am I getting this right?
Thanks for your time!
-
[quote author="Joey Dumont" date="1309182066"]
[quote author="Andre" date="1309179631"]Do you require a complete, complex form? In the last topic, you were suggested to look into QUiLoader to generate forms on the fly, but I understand that that does not pan out for you. I conclude from that, that your data entry needs per test can get quite complicated, right? [/quote]Yes, it can get quite complicated. Sometimes I have to enter data 12 to 15 numeric data points per test. Sometimes it is only a line of text. It vary a lot.
[/quote]
OK. I hope your actual tests know how to store and retrieve those values?[quote author="Joey Dumont" date="1309182066"]
[quote author="Andre" date="1309179631"]
What might be a solution for your UI, is to create a list of your tests on the left (using a QListView or something similar). Each of your plugins with a test would register itself, and would register what tests it supports. [/quote]I'm not sure I understand this. You mean that each plugin would be a group of tests, effectively hardcoding my groups into the application? If so, this is not that useful, as the groupings overlap each other and can change over time (this information is stored in a PostgreSQL DB).
[/quote]No, it would not mean that. The two could be orthogonal. You could make each plugin supply only one test. However, that may not be the best solution in all cases, as conceivably several of your tests may share a significant portion of code. That would suggest that it might be convenient to have each plugin be capable of containing more than one test. How you group those tests then, is up to you.
[quote author="Joey Dumont" date="1309182066"]
[quote author="Andre" date="1309179631"] You also make sure that your base class for those tests has a method to create a QWidget* that supplies a UI for each test it supports. In your list, you then collect all the tests as registered by the plugins. Now, you can use the right side of the dialog as an area where you dynamically create the widgets for the data entry when they are needed, by calling the ui creation method of the correct plugin and setting the container widget in your form as the parent for that new UI widget. [/quote]Every test has its own class, and they inherit from abstract classes which in turn inherit from QWidget. So yes, they can create a QWidget.
[/quote]
Why does a test inherit QWidget? I don't know what kind of tests you're talking about, but that does not seem to be most logical way to go. QObject I understand, but QWidget? That a test (-plugin) must be able to return a QWidget, does not mean that it should be a QWidget.[quote author="Joey Dumont" date="1309182066"]
Now, this solution is nice, but I still need to populate my QListView with data from the DB (table shown in first post). So now my switch statement will call a plugin instead of instantiating objects from my test classes, no? Am I getting this right?
[/quote]
The point is to avoid the huge switch you have in mind. Forget it. You will not be able to maintain that code.How this could work, is by using something like this:
- Each plugin implements a factory object that can instantiate your test objects and provide information on what tests are supported by that plugin.
- You scan for plugins, and attempt to load them.
- For each plugin that loads successfully, you call its function to list the test that plugin supports. You keep track of those, and keep track of what factory object supplies what test.
- Create a QAIM model that can be used to visualize the available tests.
- Create a QSFPM that you can use to filter that list of tests based on the selected group.
There are other approaches possible, of course. If your database of tests contains the name of the plugin supplying a test, you don't have to scan for them, for instance. You just have to try to load the plugins you need for the group of tests you want to run.
-
Here's what a test looks like. This is the only test I've designed so far.
Thanks, Andre. However, I think I'll put the topic on hold for a while, because I don't quite understand everything you're saying to me. I'll have to look into it and see how I may proceed. Plus, I don't know how to design a plugin and neither how they work (how to call them, and whatever).
I'll design my first test and read about everything you told me about.
Thanks again,
-
Even if you don't actually put all the test into plugins, I would still approach the design as if you did. That will force you to keep this properly modular, otherwise you'll end up in a maintenance nightmare. Instead of scanning for plugins, you could just have a central initialization that instantiates each of the factories manually. With some tricks, you can even get rid of that, but that is a bit more tricky and probably not worth it.
-
[quote author="Andre" date="1309187153"] Why does a test inherit QWidget? I don’t know what kind of tests you’re talking about, but that does not seem to be most logical way to go. QObject I understand, but QWidget? That a test (-plugin) must be able to return a QWidget, does not mean that it should be a QWidget. [/quote]
I'm not sure, really. In Qt Creator, I simply added a new Qt Designer form class, designed my test there, made the class inherit from an abstract class that contains some information about a particular category of test. This class inherits from QWidget, so that I can simply add a test to the MainWindow by writing
@ cqpkvi2 = new CQpkVI2(this, 1);
ui->gridLayout_2->addWidget(cqpkvi2);@The second argument simply toggles some functionality. Seemed like a good solution when I came up with it, but then again, I started programming a month ago.
I think I'm beginning to understand why one would use plugins, but as far as I know (documentation on my tests is scattered in different places), my tests won't share a lot of code.
Tell me if I'm wrong, but what you're proposing by using plugins, or approaching the problem as if I used plugins, is to break up my hypothetical huge switch statement into several smaller switch statements? I would know how to do that with the hierarchical class structure I'm using right now.
Oh, BTW, what are you talking about when you say factory?
Thanks!
-
I think you have to realize, that the UI a test presents to set its parameters is something different from the test itself. It should be possible to instantiate the test itself without showing its UI, for instance if the parameters are already known. A test would therefore be represented by a class of some common base class, and either the test factory or the test class itself could supply a QWidget-based UI for the settings belonging to the class by means of a method that returns a QWidget*. That is: you need a has-a, not an is-a relationship.
Why you would want to have a modular design, is that you are bound to want to change the set of tests at one time or another. That will lead to problems if you did not properly modularize from the start. I don't know what class structure you now use; you did not tell us about any of that. So it is hard to comment on that. It is hard to imagine you using a big class hierarchy in a useful way, while still claiming that your tests don't share a lot of code...
A "factory":http://en.wikipedia.org/wiki/Factory_method_pattern is a term from a design pattern. Basically, it is an object with a known interface that can create instances of other objects. That sounds vague, I know, but it is really useful. In the case of plugins, you can have your plugin only export a factory, and let that factory tell the host application about the concrete tests supported, and supply a method to instantiate those test objects.
-
[quote author="Andre" date="1309189454"]It should be possible to instantiate the test itself without showing its UI, for instance if the parameters are already known. [/quote]
Could I do that by keeping the same code as I have now (since I use Qt Creator and Designer, I have a ui_{fileTitleHere}.h, class.h, class.cpp and class.ui files)? Can I just move the setupUi(this) command to a function that is called only when a certain condition is met?
Is there another way of doing that?
[quote author="Andre" date="1309189454"] I don’t know what class structure you now use; you did not tell us about any of that. So it is hard to comment on that. It is hard to imagine you using a big class hierarchy in a useful way, while still claiming that your tests don’t share a lot of code…[/quote]
The class structure's main purpose is to provide a certain organization to the tests. (The tests are already divided in several categories, the class hierarchy only makes this apparent in the source code.) A few of the tests are similar indeed.
As for the factory concept, I printed out some pages of Design Patterns: Elements of Reusable Object-Oriented Software, do you think it's a good place to start?
Thanks again for all your tips,
-
I would not use the structure you have now. I would separate the UI parts and the functional parts of your tests into separate classes (which can, if you want, be defined in a single file). That also makes it possible to share a GUI between more than one test, for instance by using some parameters. Is it possible? Yes. Is it wise? No, I don't think so.
Don't use class hierarchies just as a tool for organization. That doesn't make much sense. Only do that if there is really a technical reason for such a design. The Design patterns book is a good start, but it is a bit abstract I find.
-
Let me recapitulate (I will also try to incorporate practical steps in my summary, so you can tell if I'm wrong).
I create classes for each of my test which contain:
Members that hold the results of a test;
Functions that fetch and insert data in DB
Possibly other functions
Depending on the test's results, I create GUIs that can be shared amongst multiple tests.
I then create a factory that determines which GUI goes with what test.
Factory contains functions that decides which test+GUI to instantiate depending on query from DB.
Something like that? If so, that raises some questions.
[Edit: How do you nest lists?]
-
Eh... no.
Perhaps the issue here is that I don't fully understand what your tests are all about, and where the GUI for them comes into the picture. I had assumed the GUI is for setting parameters for a test, and a test itself can be run without a GUI as long as the parameters are known. If those assumptions are false, please be more specific on your needs. You can not assume that we understand your needs, if you don't explicitly tell us about them.
Yes
Yes, I guess. I don't know what your tests return.
I guess so, if that is where your configuration data is stored.
Sure.
This one, I don't get. I'd say that every test that would need configuration, should be able to create a GUI to allow configuration. That would imply that it has nothing to do with results. A GUI class might be shared between tests, if two tests need the same type of configuration that you can adjust with some simple parameters (a single number, for instance).
No, you need the factories to actually create the test instances. A factory supplies:
Information on what tests are supported
A way to create object instances for those test
Optionally, other information on those tests, such as human-readable names or other meta-data
Depending on your design, a way to create a GUI for a test (could also be a method of the test class itself)
Who decides which tests to run, is completely separate from the above.
-
Sorry Andre. Here's the "background info":http://developer.qt.nokia.com/forums/viewthread/7221/ you need.
For instance, the screenshot I posted shows a form for a test that is performed on a machine. The four white LineEdits expect a numerical value, which will get inserted in the DB when user clicks Submit (not yet implemented). The four grayed-out LineEdits contain values that are stored in the DB and correspond to expected and tolerances values for this test.
Moreover, I want to be able to display data from the DB in the four white LineEdits if necessary, but that I already know how to do.
I hope the situation is clearer and, please, let me know if you need anything else.
-
Hello again!
I've read about several patterns in Design Patterns: Elements of Reusable Object-Oriented Software in the past few days. However, I don't see how it helps me. Again, here's a "recap":http://developer.qt.nokia.com/forums/viewthread/7221/ of what I want to do.
Both the Abstact Factory and Factory Method require subclassing an abstract Creator class that instantiates its subclasses using conditional statements. !https://lh4.googleusercontent.com/-sHPvGeYFGgs/ThH0noZ0JCI/AAAAAAAAB4Y/qjmP-wzMI_k/s800/ClassDiagram.jpg(Sample diagram for "Factory Method")!
Imagine this with 300+ tests. In a given set, I might have about 50 tests. To instantiate a test, I'm dependant upon a table similar to this one:
[quote author="Joey Dumont" date="1309147389"]
@
PK FK to Group FK to Test
1 1 1
2 1 2
3 1 3
@
[/quote]Hence the CreateTest(int) could have a switch statement that instantiates a subclass depending on the value in the table above. Now, we already said this was ridiculous, because a switch with 300+ cases seemed like a weak solution.
Now, the other solution could be this (imagine it with 15-20 classes after each category class).
!https://lh6.googleusercontent.com/-NRA-CcM9Gro/ThH57btXtVI/AAAAAAAAB4g/fH9q3p2dQwQ/s800/ClassDiagram2.png(Bigger class hierarchy. )!Because I have access to the integers characterize the group and category of each test, I could divide my switch statement with this. Filters are applied to isolate a group, then a category, then the instantiation takes place. At this level, there are about 15 to 30 tests per category, making a switch statement viable (I think).
Any thoughts? Lemme know if you want to explain my ideas in more detail. It would be my pleasure.
-
I would not bind my db structure to my application structure that tightly. You may come to regret that. I think I have told you before how I would go about this. I really don't know how much more I can tell you short of writing the code for you. To reiterate, what I would do, given what I understand of your application:
I would create my tests in plugins, where each plugin may contain 1..N tests. Each plugin has as its entry-point a factory object that tells the application loading the plugin what tests are supported, and optionally delivers other meta-information on the supported tests. The factory can create instances of the tests it supports, and return them. Then, either you give each test a function like this:
@
QWidget* createTestConfigurationGui();
@or you give each factory a method like this:
@
QWidget* createTestConfigurationGui(const QString& testName);//this is needed anyway:
AbstractTest* createTestInstance(const QString& testName);
@The implemenation of createTestInstance would of course contain a switch-like construction to instantiate an instance of the correct test, but because you keep the test in logical groups in different plugins, those constructions say of a reasonable size (not 300...)
-
Thanks Andre. I think I know how I'll go about and design this.