Create a qtablewidgetitem subclass
The cell class inherits from qtablewidgetitem. This class is designed for spreadsheet and can work well, but it has no special dependency on this class, so lilun can be used for all qtablewidget. The following is its header file:
#ifndef CELL_H#define CELL_H#include <QTableWidgetItem>class Cell : public QTableWidgetItem{public: Cell(); QTableWidgetItem *clone() const; void setData(int role, const QVariant &value); QVariant data(int role) const; void setFormula(const QString &formula); QString formula() const; void setDirty();private: QVariant value() const; QVariant evalExpression(const QString &str, int &pos) const; QVariant evalTerm(const QString &str, int &pos) const; QVariant evalFactor(const QString &str, int &pos) const; mutable QVariant cachedValue; mutable bool cacheIsDirty;};#endif
The cell class extends qtablewidgetitem by adding two private variables:
Cachedvalue caches the value of a cell and treats it as the qvariant type.
Cacheisdirty: true if the cached value is not the latest.
Because some cells are values of the double type, while others are values of the qstring type, we use qvariant.
The cachedvalue and cacheisdirty variables are declared using the C ++ mutable keyword. This allows us to modify these variables in constant functions. Another option is to re-calculate every text () call, but this should be unnecessary.
Note that there is no object macro in the definition of this class. Cell is a common C ++ class with no signal or slot. In fact, because qtablewidgetitem is not inherited from qobject, there cannot be signals and slots in the cell. Qt's item classes do not inherit from qoibject to minimize their extra costs. If signals and slots are required, they can be implemented in the object classes that contain these items, or, more specifically, they can use multiple inheritance with qobject.
The starting part of cell. cpp is as follows:
# Include <qtgui>
# Include "cell. H"
Cell: Cell ()
{
Setdirty ();
}
In constructors, we only need to set the cache as dirty data. You do not need to pass a parent object here. Because this cell is inserted into qtablewidget using setitem (), qtablewidget will automatically gain ownership of it.
Each qtablewidgetitem can store some data, and each data "role" is a qvariant. The most common roles are QT: editrole and QT: displayrole. Editing a role indicates the data to be edited, and displaying a role indicates the data to be displayed. The two are the same data, but in the cell, edit the formula of the role corresponding to the cell, and display the value of the role corresponding to the cell (result of calculating the formula ).
Qtablewidgetitem * cell: Clone () const
{
Return new cell (* This );
}
When qtablewidget creates a new cell, the clone () function is called. For example, when a user inputs text to an empty cell that has never been used. The instance passed to qtablewidget: setitemprototype () is cloned. Because the Member-level copy is sufficient for the cell, we use the default copy constructor automatically created by C ++ in the clone () function to create a new cell instance.
Void cell: setformula (const qstring & formula)
{
Setdata (QT: editrole, formula );
}
The setformula () function sets the cell formula. It is a simple and convenient function that calls setdata () for editing a role. It is called in Spreadsheet: setformula.
The formula () function is called in Spreadsheet: formula. Like setformula (), it is also a convenient function, but this time it is used to obtain the editrole data of the item.
Void cell: setdata (INT role, const qvariant & value)
{
Qtablewidgetitem: setdata (role, value );
If (role = QT: editrole)
Setdirty ();
}
If we have a new formula, we set cacheisdirty to true to ensure that the cell value is recalculated the next time text () is called.
Although we call the text () method of the cell instance in Spreadsheet: Text (), the text () function is not defined in the cell. The text () function is a convenient function provided by qtablewidgetitem. It waits for calling data (QT: Display-role). tostring ().
Void cell: setdirty ()
{
Cacheisdirty = true;
}
Setdirty () function call is used to force the recalculation of the cell value. It simply sets cacheisdirty to true, which means that cachevalue is not up-to-date. The re-calculation process is not executed until necessary.
Qvariant cell: Data (INT role) const
{
If (role = QT: displayrole ){
If (value (). isvalid ()){
Return Value (). tostring ();
} Else {
Return "####";
}
} Else if (role = QT: textalignmentrole ){
If (value (). Type () = qvariant: string ){
Return int (QT: alignleft | QT: alignvcenter );
} Else {
Return int (QT: alignright | QT: alignvcenter );
}
} Else {
Return qtablewidgetitem: Data (role );
}
}
The data () function is the re-Implementation of the function in qtablewidgetitem. If QT: displayrole is used, it returns the text displayed in the workbook. If QT: editrole is used, the formula is returned. If QT :: if textalignmentrole is called, an appropriate alignment is returned. In the case of displayrole, it relies on value () to calculate the value of the corresponding cell. If this value is invalid (because the formula is incorrect), "####" is returned.
Cell: Value () used in data () returns a qvariant. A qvariant can store different types of values, such as double and qstring, and provides functions to convert them to other types. For example, if you call tostring (), a variable that stores the double value will generate a string that represents the double value. The qvariant constructed using the default constructor is an invalid variable.
Const qvariant invalid;
Qvariant cell: Value () const
{
If (cacheisdirty ){
Cacheisdirty = false;
Qstring formulastr = formula ();
If (formulastr. startswith (''')){
Cachedvalue = formulastr. mid (1 );
} Else if (formulastr. startswith ('= ')){
Cachedvalue = invalid;
Qstring expr = formulastr. mid (1 );
Expr. Replace ("","");
Expr. append (qchar: NULL );
Int Pos = 0;
Cachedvalue = evalexpression (expr, POS );
If (expr [POS]! = Qchar: NULL)
Cachedvalue = invalid;
} Else {
Bool OK;
Double D = formulastr. todouble (& OK );
If (OK ){
Cachedvalue = D;
} Else {
Cachedvalue = formulastr;
}
}
}
Return cachedvalue;
}
The private function value () returns the value of a cell. If cacheisdirty is true, we must recalculate its value.
If the formula starts with a single quotation mark (for example, "12345"), the single quotation mark occupies the position 0, and the value is the string from the public position 1 to the final position.
If the formula starts with an equal sign ('='), we extract the string starting from position 1 and delete the spaces. Then we call evalexpression () to calculate the value of this expression. Pos parameters are passed as references. It indicates the position of the Start character for parsing. After evalexpression () is called, if the resolution is successful, the character at the POs should be the qchar: NULL character we added. If the resolution fails before the end, we set the cachedvalue to invalid.
If the formula does not start with single quotes or equal signs, we try to use todouble () to convert it to a floating point value. If the conversion is successful, we set the cachedvalue to this result. Otherwise, we will set cachedvalue as the formula string. For example, "1.50" causes todouble () to be set to true and returns 1.5, while "World Population" causes todouble () to set OK to false and return 0.0.
By giving todouble () a pointer to the bool type, we can indicate a String Conversion and conversion error of the number value 0.0 (0.0 is also returned but this bool value is set to false ). Sometimes it is expected that 0 is returned when a conversion error occurs. In this case, we do not need to input a pointer pointing to the bool type. Due to performance and portability, QT never reports failures using C ++ exceptions. But in the QT program, this does not prevent you from using them, if your compiler supports them.
The value () function is declared as the const type. We must declare cachedvalue and cacheisvalid as mutable variables so that the compiler can allow us to modify them in constant functions. It may be tempting to change value () to a non-constant type and remove the mutable keyword, but it cannot be compiled because value () is called from the constant function data ().
Now we have completed the Formula Parsing section of the workbook program. The rest of this section includes evalexpression () and two helper functions evalterm () and evalfactor (). Because this code has nothing to do with GUI programming, you can rest assured to skip it and continue reading it in chapter 5th.
The evalexpression () function returns the workbook expression value. An expression is defined as one or more items separated by the '+' or '+ --' (whether it should be '-') operator. These items are defined as one or more factors separated by the '*' or '/' operator. By breaking down an expression into items and breaking down items into factors, we ensure the correct priority of operators.
For example, "2 * C5 + D6" is an expression, "2 * C5" is its first item, and "D6" is its second item. Item "2 * C5" has the first factor "2" and the second factor "C5", while item "D6" consists of only one factor "D6. A factor can be a data ("2"), a coordinate ("C5"), or an expression in parentheses. A single minus sign can be placed before the expression.
Figure 4.10 defines the syntax of a workbook. Each symbol (expression, item, and factor) in the syntax has a corresponding member function for parsing them, and the structure of these functions is closely different from that in the syntax. This analyzer is called a downward recursive analyzer.
Figure 4.10 Syntax of a workbook
Let's start with the evalexpression () function of the analysis expression:
Qvariant cell: evalexpression (const qstring & STR, Int & Pos) const
{
Qvariant result = evalterm (STR, POS );
While (STR [POS]! = Qchar: NULL ){
Qchar op = STR [POS];
If (op! = '+' & Op! = '-')
Return result;
++ Pos;
Qvariant term = evalterm (STR, POS );
If (result. Type () = qvariant: double
& Amp; term. Type () = qvariant: Double ){
If (OP = '+ '){
Result = result. todouble () + term. todouble ();
} Else {
Result = result. todouble ()-term. todouble ();
}
} Else {
Result = invalid;
}
}
Return result;
}
First, call evalterm () to obtain the value of the first item. If the subsequent characters are '+' or '+ --', call evalterm (). Otherwise, the expression is composed of a single item, so we will return its value as the value of the entire expression. After we have the values of the first two items, we calculate the result of this operation, which depends on the current operator. If both items are of the double type, our calculation result is of the double type. Otherwise, we set the result to invalid.
We continue like this until there are no more items. This works normally, because addition and subtraction are left-side. This means that "1-2-3" means "(1-2)-3", instead of "1-(2-3 )".
Qvariant cell: evalterm (const qstring & STR, Int & Pos) const
{
Qvariant result = evalfactor (STR, POS );
While (STR [POS]! = Qchar: NULL ){
Qchar op = STR [POS];
If (op! = '*' & Op! = '/')
Return result;
++ Pos;
Qvariant factor = evalfactor (STR, POS );
If (result. Type () = qvariant: double
& Factor. Type () = qvariant: Double ){
If (OP = '*'){
Result = result. todouble () * factor. todouble ();
} Else {
If (factor. todouble () = 0.0 ){
Result = invalid;
} Else {
Result = result. todouble ()/factor. todouble ();
}
}
} Else {
Result = invalid;
}
}
Return result;
}
The evalterm () function is very similar to evalexpression (), but it processes multiplication and division. In evalterm (), the only clever one is to avoid the division of 0, because this produces an error on some processors. It is unwise to compare the equality of floating-point numbers due to incorrect rounding, but it is safe to compare them with 0.0 to prevent the division of 0 errors.
Qvariant cell: evalfactor (const qstring & STR, Int & Pos) const
{
Qvariant result;
Bool negative = false;
If (STR [POS] = '-'){
Negative = true;
++ Pos;
}
If (STR [POS] = '('){
++ Pos;
Result = evalexpression (STR, POS );
If (STR [POS]! = ')')
Result = invalid;
++ Pos;
} Else {
Qregexp Regexp ("[A-Za-Z] [1-9] [0-9] {0, 2 }");
Qstring token;
While (STR [POS]. isletterornumber () | STR [POS] = '.'){
Token + = STR [POS];
++ Pos;
}
If (Regexp. exactmatch (token )){
Int column = token [0]. toupper (). Unicode ()-'A ';
Int ROW = token. mid (1). toint ()-1;
Cell * c = static_cast <cell *> (
Tablewidget ()-> item (row, column ));
If (c ){
Result = C-> value ();
} Else {
Result = 0.0;
}
} Else {
Bool OK;
Result = token. todouble (& OK );
If (! OK)
Result = invalid;
}
}
If (negative ){
If (result. Type () = qvariant: Double ){
Result =-result. todouble ();
} Else {
Result = invalid;
}
}
Return result;
}
The evalfactor () function is a little more complex than evalexpression () and evalterm. Note whether the factor is negative. Then check whether it starts with an opening bracket. If yes, we call evalexpression () to calculate the content in parentheses as an expression. When analyzing an enclosed expression, evalexpression () calls evalterm () and eval-factor (), and evalexpression (). This is recursively specified in the analyzer.
If a factor is not a nested expression, we extract the next sequence, which should be a cell coordinate or number. If the sequence matches the corresponding qregexp, We reference it as a cell and call its value () function with the given coordinates. This cell can be any one in a workbook and can depend on other cells. Dependency is not a problem. They will simply trigger more value () calls and (for "dirty" cells) more analysis until all affiliated units are evaluated. If the sequence is not a cell coordinate, we treat it as a number.
What happens if cell A1 contains the formula "= A1? Or what happens if cell A1 contains "= a2" and cell A2 contains "= A1. Although we didn't write any special code to detect circular dependencies, the analyzer handled these cases gracefully by returning an invalid qvariant value. This works well because we have set cacheisdirty to false before calling evalexpression () and set cachedvalue to invalid in value. If evalexpression () recursively calls value () to the same cell, it immediately returns an invalid value and the calculation result of the value of the entire expression is invalid.
Now we have completed the formula analyzer. It should be able to simply expand to process pre-defined spreadsheet program functions, such as sum () "and" AVG () "through the definition of extension factors ()". Another simple extension method is to implement the '+' operator of the string (used as a connection ). This does not need to modify the syntax of factor definition.