![]() |
| ||
Classes - Annotated - Tree - Functions - Home - Structure |
The following example shows how to use cell editors other than the default left aligning QLineEdit. It implements a spreadsheet used as a product order form for a not extremely sophisticated wine shop.
For each wine on the list the user might choose how many bottles he or she wants to order. Instead of using the default cell editor we present the user with a spin box where he or she might choose between 0 and 250 bottles.
Additionally we provide a cell where the shop owner might substract a discount from the bill.
Whenever the user changes one of the editable cells all sums are updated at once.
We implement two custom classes: the order spreadsheet, ProductList, and a special table item class that uses a QSpinBox as the cell editor. The latter one is named SpinBoxItem.
You might wish to get an overview over the entire code first.
Usually QTables are filled with QTableItems or objects of the more specialized classes QComboTableItem and QCheckTableItem. A QTableItem looks quite right for our purposes. It has only one minor disadvantage: As we want to restrict the user input when it comes to the number of bottles we must exchange the default QLineEdit.
#include <qtable.h> #include <qstring.h>
class SpinBoxItem: public QTableItem {
This is easily done by subclassing QTableItem.
public: SpinBoxItem( QTable *, const int, const QString &);
The only SpinBoxItem function that can be called from outside a SpinBoxItem object is the constructor . It requires three arguments: the QTable that should parent the new item, the initial numerical value of the item (i.e. the number of bottles) and a suffix denoting the "unit" of the value, e.g. " btls".
private: QWidget * createEditor() const;
To use another cell editor than the default QLineEdit we have to reimplement QTableItem::createEditor().
void setContentFromEditor( QWidget * );
When the user is done with editing, the value stored in the editor has to be transfered to the SpinBoxItem itself. As we use a custom cell editor we have to care about this ourselves by reimplementing QTableItem::setContentFromEditor().
int getValue() const;
getValue() is a private helper function that returns the numerical value currently stored in the item.
QTable * table; QString suffix; };
And last but not least we define two helper variables to store the parent QTable and a string that holds the unit to be shown in the cell.
Now let's have a look at the implementation of our custom table item.
#include "spinboxitem.h"
First we include the API of the new class ...
#include <qspinbox.h> #include <qregexp.h>
... and two helper classes that we need during the implementation.
SpinBoxItem::SpinBoxItem( QTable * myTable, const int value, const QString & text ) : QTableItem( table, WhenCurrent, "" ) {
We derive the SpinBoxItem class from QTableItem. Each object has the QTableItem::EditType WhenCurrent, will say that the cell editor appears only when the user activates the cell.
table = myTable; suffix = text;
In the constructor we don't really have much to do: First of all the two class variables must be initialized. To remind you table holds the parent QTable of the item object, and suffix takes care of the last SpinBoxItem argument that denotes the "unit" to be shown in the cell.
setText( QString::number( value ) + suffix ); }
Both, the initial numerical value and the suffix are shown as the cell text, for example "0 btls" as long as the user does not want to order.
To achieve this we have to convert the integer value to a string using QString::number() and merge it with the text in suffix. Using QTableItem::setText() we promote the resulting string to serve as the item content.
QWidget * SpinBoxItem::createEditor() const { QSpinBox * quantities = new QSpinBox( table->viewport(), "quantities" );
Now we create the cell editor of a SpinBoxItem. First we make a spin box object, quantities. It is a child of the current view port of the parent QTable. The view port is the area that the item is supposed to fill in the table.
quantities->setSuffix( suffix );
suffix is used as the unit displayed in the spin box.
quantities->setMaxValue( 250 );
We don't allow the user to order more than 250 bottles of one wine and therefore restrict the editor spin box to ignore values greater 250. As QSpinBoxes by default have their minimal value set to 0 we don't have to care about this.
quantities->setValue( getValue() );
To initialize the editor spin box with the value stored in this SpinBoxItem we use getValue() to obtain exactly this integer value.
return quantities; }
Now that we created the cell editor we return it to the SpinBoxItem.
int SpinBoxItem::getValue() const {
Our helper function getValue() does not change anything in a SpinBoxItem and is therefore defined a const function.
QString value = text();
First we obtain the content of the current spin box item, i.e. the item text.
value.replace( QRegExp( suffix ), "" );
As this text consists of the numerical (integer) value and a suffix ("0 btls"), we simply chop off the latter one...
bool ok; int number; number = value.toInt( &ok, 10 );
... and convert the remains to a decimal integer value.
if ( ok ) return number;
If the conversion resulted in a proper integer we return it.
return 0; }
Otherwise we simply return 0.
void SpinBoxItem::setContentFromEditor( QWidget * spinbox ) {
We have so far taken care of that a spin box pops up when the user wants to change the value of a SpinBoxItem. What we still need to do is to make sure that the content of the editor is properly transfered to and stored in the item.
setText( ( (QSpinBox *) spinbox )->text() ); }
To achieve this we gather the text from the spinbox and make it the content of the item using QTableItem::setText(). The cast from QWidget * to QSpinBox * is necessary because text() is a QSpinBox and not a QWidget function.
Now that we have our custom table item ready we can think about the API of the (main) application window, an order list in a spreadsheet.
#include <qtable.h> #include <qstring.h>
class ProductList: public QTable {
We call the new class ProductList and derive it from QTable.
Q_OBJECT
Contrary to the SpinBoxItem class we want to implement a slot here and therefore add the macro Q_OBJECT.
public: ProductList();
Again, the only public function is the constructor, this time with no arguments.
private slots: void processValueChanged( int, int );
When the user changes a value in the spreadsheet we use the slot processValueChanged() to react to it. This reaction consists of two parts:
private: double calcPrice( int );
The price of the actual number of bottles is calculated, and ...
double sumUp( int );
... the total number of bottles and the total price are computed.
int discountRow;
The variable discountRow stores the row where the sales person can subtract a discount.
int totalRow;
This variable is used to store the number of the row where the total values are shown.
QString suffix; };
suffix holds the unit for cells with numerical content.
#include "productlist.h" #include "spinboxitem.h" #include <qstring.h>
Apart from the includes...
struct { const char * product; double price; } winelist[] = {
... we define a structure to store the product name, product, and the price of our range of wines ...
{ "Wynns Coonawarra Shiraz 1998", 15.00 }, { "Meissner Kapitelberg Riesling Kabinett trocken 1999", 8.94 }, { "Perdera Monica di Sardegna 1997", 7.69 } };
... and fill this "database" named winelist[] with some example values (prices in EURO).
const int numwines = sizeof( winelist ) / sizeof( winelist[0] );
To make an extension of our product range as simple as possible we better calculate the number of products to be shown in the list at run-time instead of defining a fixed numerical value.
ProductList::ProductList() : QTable( numwines + 2, 4, 0, "productlist" ) {
The ProductList class itself we derive from QTable. The spreadsheet size depends on how many products we want to list. Plus one additional row for the discount and one for the total sums we have numwines + 2 rows and four columns.
As we use this class as the main widget the parent of a ProductList object named productlist is zero.
discountRow = numRows() - 2; totalRow = numRows() - 1; suffix = " btls";
First we set the class variables. The discount should appear in the second last row of the table, the total sums on the last row. QTable::numRows() returns the number of rows of this table.
As a suffix we use btls because finally we deal with bottles.
horizontalHeader()->setLabel( 0, "Quantity" ); horizontalHeader()->setLabel( 1, "Product" ); horizontalHeader()->setLabel( 2, "Price/bottle (EUR)" ); horizontalHeader()->setLabel( 3, "Sum (EUR)" );
Then we change the section titles of the top most table header to something that explains the column contents. The first column holds the number of bottles of each type, the second one the name of the wine, the third one the price per bottle in EURO, and last but not least we have the fourth column for the price of n bottles.
for ( int i = 0; i < numwines; i++ ){
Next we fill the spreadsheet with the products.
SpinBoxItem * quantity = new SpinBoxItem( this, 0, suffix );
We create a SpinBoxItem that will be initialized with the text "0 btls".
setItem( i, 0, quantity );
This item we make the content of the first column in the current (i-th) row.
setText( i, 1, winelist[i].product );
The product description for column no. 1 (i.e. the second column) we take from the winelist array.
setText( i, 2, QString::number( winelist[i].price ) );
To fill the third column with the appropriate price per bottle we have to convert the double struct member price to a string using QString::number().
setText( i, 3, "0"); }
As the price of zero bottles is 0 this is the value we initialize the last column with.
setText( discountRow, 1, "Discount" );
Next we fill the second column of the second-last row (the discountRow following the product listings) with the description Discount.
QTableItem * discount = new QTableItem( this, QTableItem::Always, "-0.00" );
In the last cell of this row the sales person may substract a discount (for simplicity reasons this is a fixed not a percental amount, and we won't even check whether it actually is a negative value).
Initially the discount is zero (-0.00 EUR), and we use a standard QTableItem to store this value. The cell should be Always editable, i.e. the user will always see the item's QLineEdit editor.
As the line editor aligns everything including numerical values to the left this cell is notably different from the rest of the column where numerical values are right-aligned.
setItem( discountRow, 3, discount );
Finally we make the discount item the content of the fourth cell in the discountRow.
processValueChanged( 0, 0 );
To fill the last row with the total amount of bottles and the final price we simply call the processValueChanged() function. The argument cell 0,0 is choosen arbitrarily but must be one from the first column.
setColumnReadOnly( 1, TRUE ); setColumnReadOnly( 2, TRUE ); setColumnReadOnly( 3, TRUE );
After we have filled the initial spreadsheet form so far we want to prevent the user from manipulating things like product names, prices and sums. Therefore we simply make every but the first column read-only.
Note that the only way to cheat here and make the discount cell editable for the user was to give this cell the edit type Always. With any other edit type we would not be able to get up an editor because the entire column is read-only.
connect( this, SIGNAL( valueChanged( int, int ) ), this, SLOT( processValueChanged( int, int ) ) );
Whenever the user has changed a value in the first column or the discount cell the signal QTable::valueChanged() is emitted. As arguments it carries the coordinates of the manipulated cell. When this happens we want to update the sums and totals, and therefore connect valueChanged() to the processValueChanged() slot.
adjustColumn( 1 ); adjustColumn( 2 ); }
As we want the user to see all product titles and the longish section header of column no. 2 in full we apply some cosmetics and adjust the second and the third column to fit their contents.
void ProductList::processValueChanged( int row, int ) {
Now we define what happens when a table cell changes its value. As soon as the user stops editing we of course want to update the sums and totals.
QString total;
First we define a helper variable.
if ( row != discountRow ){
When the user changed the number of bottles...
total = QString::number( calcPrice( row ) );
... we generate the total price of the bottles in this row with a little help of the calcPrice() function. As we want to make this the cell content we have to convert the returned double value into a string using QString::number().
setText( row, 3, total );
We update the cell content in the last cell of the edited row ...
total = QString::number( sumUp( 0 ) );
... and sum up the number of total bottles in column no. 0.
setText( totalRow, 0, total + suffix );
The cell content of the last cell in the first row is updated to the new value followed by the suffix btls.
} else { clearCell( discountRow, 0 ); }
In the discount row the user can change the discount value but also the empty writable cell in the first column. We could make this cell unwritable but for simplicity reasons we simply wipe it out when the user changed something in this row.
total = QString::number( sumUp( 3 ) );
Whether the user changed a quantity or the discount does not matter when it comes to the total sum of the last row -- we have to calculate it in each case.
setText( totalRow, 3, total ); }
Finally we present the user with the total price in the lower right corner of the spreadsheet.
double ProductList::calcPrice( int row ) {
This function multiplies the price per bottle with the number of bottles in the particular row.
double price = text( row, 0 ).toDouble();
To do this it gathers the cell content of the first cell in this row (which should read for example 0 btls etc.) and converts it to a double. This is possible because QString::toDouble() ignores the trailing string btls.
return price * text( row, 2 ).toDouble(); }
Multiplied with the price from the third column this is the result we return.
double ProductList::sumUp( int col ) {
This function on the other hand is responsible for summing up the values of an entire column.
double sum = 0;
We start calculating with a zero.
for ( int i = 0; i <= discountRow; i++ )
Then we work ourselves through each row from the first one to the one with the discount ...
sum += text( i, col ).toDouble();
..., convert the content of the relevant cell to double and add it to the subtotal stored in sum.
return sum; }
Finally we return the total sum.
Same procedure as everytime. Therefore we simply leave you with the code that makes a ProductList object the main widget of the application, shows it and enters the application's event loop.
#include "productlist.h" #include <qapplication.h> int main( int argc, char ** argv ) { QApplication app( argc, argv ); ProductList * productlist = new ProductList(); app.setMainWidget( productlist ); productlist->show(); return app.exec(); }
Copyright © 2000 Trolltech | Trademarks | Qt version main-beta1
|