为什么80%的码农都做不了架构师?>>>
0. 源代码下载(qt5)
https://github.com/leichaojian/qt/tree/master/spreadsheet
1. 子类化QMainWindowCopyright © 2008 Software Inc."" Spreadsheet is a small application that ""demonstrates QAction, QMainWindow, QMenuBar, ""QStatusBar, QTableWidget, QToolBar, and many other ""Qt classes."));
2. 子类化QTableWidget #include
#include "gotocelldialog.h"
#include "mainwindow.h"
#include "sortdialog.h"
#include "spreadsheet.h"MainWindow::MainWindow()
{spreadsheet = new Spreadsheet;//设置为中央窗口部件setCentralWidget(spreadsheet);createActions();createMenus();createContextMenu();createToolBars();createStatusBar();readSettings();findDialog = 0;//显示窗口左上角的图标setWindowIcon(QIcon(":/images/icon.png"));setCurrentFile("");
}//closeEvent事件:中途拦截close信号,用来确定是否要关闭窗口
void MainWindow::closeEvent(QCloseEvent *event)
{if (okToContinue()) {//保存设置,接收此事件writeSettings();event->accept();} else {//忽略此事件event->ignore();}
}void MainWindow::newFile()
{//说明保存成功if (okToContinue()) {//保存成功后,清空界面,设置标题spreadsheet->clear();setCurrentFile("");}
}void MainWindow::open()
{if (okToContinue()) {//创建文件对话框(getOpenFileName)并且选择过滤后的文件名(fileName)//"Spreadsheet files (*.sp)"中Spreadsheet files为描述文字,而*.sp为过滤条件QString fileName = QFileDialog::getOpenFileName(this,tr("Open Spreadsheet"), ".",tr("Spreadsheet files (*.sp)"));//如果文件不为空,则加载文件if (!fileName.isEmpty())loadFile(fileName);}
}bool MainWindow::save()
{//是当前文件而非新建文件if (curFile.isEmpty()) {return saveAs();} else {return saveFile(curFile);}
}bool MainWindow::saveAs()
{//getSaveFileName:得到保存的文件,如果文件存在,则会提示是否要覆盖QString fileName = QFileDialog::getSaveFileName(this,tr("Save Spreadsheet"), ".",tr("Spreadsheet files (*.sp)"));if (fileName.isEmpty())return false;return saveFile(fileName);
}void MainWindow::find()
{if (!findDialog) {findDialog = new FindDialog(this);connect(findDialog, SIGNAL(findNext(const QString &,Qt::CaseSensitivity)),spreadsheet, SLOT(findNext(const QString &,Qt::CaseSensitivity)));connect(findDialog, SIGNAL(findPrevious(const QString &,Qt::CaseSensitivity)),spreadsheet, SLOT(findPrevious(const QString &,Qt::CaseSensitivity)));}//非模态对话框--->show()findDialog->show();//raise()使窗口成为顶层窗口findDialog->raise();//activateWindow()激活顶层窗口findDialog->activateWindow();
}void MainWindow::goToCell()
{GoToCellDialog dialog(this);//exec为模态对话框(这里不同于show的非模态对话框)if (dialog.exec()) {QString str = dialog.lineEdit->text().toUpper();spreadsheet->setCurrentCell(str.mid(1).toInt() - 1,str[0].unicode() - 'A');}
}//对局部区域进行排序
void MainWindow::sort()
{SortDialog dialog(this);QTableWidgetSelectionRange range = spreadsheet->selectedRange();//选定的列('A' + 1, 'A' + 4 ==> 'B','E')dialog.setColumnRange('A' + range.leftColumn(),'A' + range.rightColumn());if (dialog.exec()) {SpreadsheetCompare compare;compare.keys[0] =dialog.primaryColumnCombo->currentIndex();compare.keys[1] =dialog.secondaryColumnCombo->currentIndex() - 1;compare.keys[2] =dialog.tertiaryColumnCombo->currentIndex() - 1;compare.ascending[0] =(dialog.primaryOrderCombo->currentIndex() == 0);compare.ascending[1] =(dialog.secondaryOrderCombo->currentIndex() == 0);compare.ascending[2] =(dialog.tertiaryOrderCombo->currentIndex() == 0);spreadsheet->sort(compare);}
}void MainWindow::about()
{QMessageBox::about(this, tr("About Spreadsheet"),tr("Spreadsheet 1.1
""
}void MainWindow::openRecentFile()
{if (okToContinue()) {//qobject_cast动态类型转换--->这里sender()的含义是什么?QAction *action = qobject_cast
}void MainWindow::updateStatusBar()
{locationLabel->setText(spreadsheet->currentLocation());formulaLabel->setText(spreadsheet->currentFormula());
}void MainWindow::spreadsheetModified()
{setWindowModified(true);updateStatusBar();
}//一个动作(action)就是一个可以添加到任意数量的菜单和工具栏上的项
void MainWindow::createActions()
{//New动作newAction = new QAction(tr("&New"), this);newAction->setIcon(QIcon(":/images/new.png"));newAction->setShortcut(QKeySequence::New); //快捷键newAction->setStatusTip(tr("Create a new spreadsheet file"));connect(newAction, SIGNAL(triggered()), this, SLOT(newFile()));//Open动作openAction = new QAction(tr("&Open..."), this);openAction->setIcon(QIcon(":/images/open.png"));openAction->setShortcut(QKeySequence::Open);openAction->setStatusTip(tr("Open an existing spreadsheet file"));connect(openAction, SIGNAL(triggered()), this, SLOT(open()));//Save动作saveAction = new QAction(tr("&Save"), this);saveAction->setIcon(QIcon(":/images/save.png"));saveAction->setShortcut(QKeySequence::Save);saveAction->setStatusTip(tr("Save the spreadsheet to disk"));connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));//Save as动作saveAsAction = new QAction(tr("Save &As..."), this);saveAsAction->setStatusTip(tr("Save the spreadsheet under a new ""name"));connect(saveAsAction, SIGNAL(triggered()), this, SLOT(saveAs()));//最近打开文件动作for (int i = 0; i
#if QT_VERSION <0x040102// workaround for a QTableWidget bug in Qt 4.1.1connect(showGridAction, SIGNAL(toggled(bool)),spreadsheet->viewport(), SLOT(update()));
#endifautoRecalcAction &#61; new QAction(tr("&Auto-Recalculate"), this);autoRecalcAction->setCheckable(true);autoRecalcAction->setChecked(spreadsheet->autoRecalculate());autoRecalcAction->setStatusTip(tr("Switch auto-recalculation on or ""off"));connect(autoRecalcAction, SIGNAL(toggled(bool)),spreadsheet, SLOT(setAutoRecalculate(bool)));aboutAction &#61; new QAction(tr("&About"), this);aboutAction->setStatusTip(tr("Show the application&#39;s About box"));connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));aboutQtAction &#61; new QAction(tr("About &Qt"), this);aboutQtAction->setStatusTip(tr("Show the Qt library&#39;s About box"));connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}void MainWindow::createMenus()
{//addMenu用于创建一个窗口部件fileMenu &#61; menuBar()->addMenu(tr("&File"));fileMenu->addAction(newAction);fileMenu->addAction(openAction);fileMenu->addAction(saveAction);fileMenu->addAction(saveAsAction);separatorAction &#61; fileMenu->addSeparator(); //分隔符//最近打开的文件for (int i &#61; 0; i
}/**************************
任何Qt窗口部件都可以有一个与之相关联的QAction列表。要为该应用程序提供一个上下文菜单&#xff0c;可以将
所需要的动作添加到Spreadsheet窗口部件中&#xff0c;并且将那个窗口部件的上下文菜单策略(context menu policy)
设置为一个显示这些动作的上下文菜单。当用户在一个窗口部件上单击鼠标右键&#xff0c;或者是在键盘上按下一个与平台相关的按键时&#xff0c;就可以激活这些上下文菜单。
*****************************/
void MainWindow::createContextMenu()
{//将菜单栏cutAction&#xff0c;copyAction和pasteAction关联到spreadsheet中spreadsheet->addAction(cutAction);spreadsheet->addAction(copyAction);spreadsheet->addAction(pasteAction);spreadsheet->setContextMenuPolicy(Qt::ActionsContextMenu);
}void MainWindow::createToolBars()
{fileToolBar &#61; addToolBar(tr("&File"));fileToolBar->addAction(newAction);fileToolBar->addAction(openAction);fileToolBar->addAction(saveAction);editToolBar &#61; addToolBar(tr("&Edit"));editToolBar->addAction(cutAction);editToolBar->addAction(copyAction);editToolBar->addAction(pasteAction);editToolBar->addSeparator();editToolBar->addAction(findAction);editToolBar->addAction(goToCellAction);
}void MainWindow::createStatusBar()
{locationLabel &#61; new QLabel(" W999 ");//默认情况下是向左对齐并且垂直居中locationLabel->setAlignment(Qt::AlignHCenter);locationLabel->setMinimumSize(locationLabel->sizeHint());formulaLabel &#61; new QLabel;//设定缩进大小formulaLabel->setIndent(3);statusBar()->addWidget(locationLabel);//1为伸展因子&#xff0c;防止状态显示过于靠近statusBar()->addWidget(formulaLabel, 1);connect(spreadsheet, SIGNAL(currentCellChanged(int, int, int, int)),this, SLOT(updateStatusBar()));//修改(modified)后更新状态栏connect(spreadsheet, SIGNAL(modified()),this, SLOT(spreadsheetModified()));//更新状态栏updateStatusBar();
}//读取配置---通常为键值方式
void MainWindow::readSettings()
{QSettings settings("Software Inc.", "Spreadsheet");restoreGeometry(settings.value("geometry").toByteArray());recentFiles &#61; settings.value("recentFiles").toStringList();updateRecentFileActions();bool showGrid &#61; settings.value("showGrid", true).toBool();showGridAction->setChecked(showGrid);bool autoRecalc &#61; settings.value("autoRecalc", true).toBool();autoRecalcAction->setChecked(autoRecalc);
}void MainWindow::writeSettings()
{QSettings settings("Software Inc.", "Spreadsheet");settings.setValue("geometry", saveGeometry());settings.setValue("recentFiles", recentFiles);settings.setValue("showGrid", showGridAction->isChecked());settings.setValue("autoRecalc", autoRecalcAction->isChecked());
}bool MainWindow::okToContinue()
{if (isWindowModified()) {int r &#61; QMessageBox::warning(this, tr("Spreadsheet"),tr("The document has been modified.\n""Do you want to save your changes?"),QMessageBox::Yes | QMessageBox::No| QMessageBox::Cancel);if (r &#61;&#61; QMessageBox::Yes) {return save();} else if (r &#61;&#61; QMessageBox::Cancel) {return false;}}return true;
}bool MainWindow::loadFile(const QString &fileName)
{//readFile从磁盘中读取文件并显示出来if (!spreadsheet->readFile(fileName)) {statusBar()->showMessage(tr("Loading canceled"), 2000);return false;}setCurrentFile(fileName);statusBar()->showMessage(tr("File loaded"), 2000);return true;
}bool MainWindow::saveFile(const QString &fileName)
{if (!spreadsheet->writeFile(fileName)) {statusBar()->showMessage(tr("Saving canceled"), 2000);return false;}setCurrentFile(fileName);statusBar()->showMessage(tr("File saved"), 2000);return true;
}void MainWindow::setCurrentFile(const QString &fileName)
{curFile &#61; fileName;setWindowModified(false);QString shownName &#61; tr("Untitled");if (!curFile.isEmpty()) {//strippedName:移除文件名中的路径字符shownName &#61; strippedName(curFile);//从最近打开文件中移除当前文件recentFiles.removeAll(curFile);//将当前文件加入最近打开文件的开头recentFiles.prepend(curFile);//更新最近打开文件updateRecentFileActions();}//显示标题(*代表已修改而未保存)setWindowTitle(tr("%1[*] - %2").arg(shownName).arg(tr("Spreadsheet")));
}void MainWindow::updateRecentFileActions()
{QMutableStringListIterator i(recentFiles);//用来移除任何不存在的文件while (i.hasNext()) {if (!QFile::exists(i.next()))i.remove();}for (int j &#61; 0; j
}QString MainWindow::strippedName(const QString &fullFileName)
{return QFileInfo(fullFileName).fileName();
}
3. 子类化QTableWidgetItem #include
#include "spreadsheet.h"Spreadsheet::Spreadsheet(QWidget *parent): QTableWidget(parent)
{autoRecalc &#61; true;//将item的原型设置为Cell类型setItemPrototype(new Cell);//选择模式为&#xff1a;ContiguousSelection&#xff0c;则允许单矩形选择框方法setSelectionMode(ContiguousSelection);connect(this, SIGNAL(itemChanged(QTableWidgetItem *)),this, SLOT(somethingChanged()));//调用clear()来重新调整表格的尺寸大小并且设置列标题clear();
}//得到当前位置
QString Spreadsheet::currentLocation() const
{return QChar(&#39;A&#39; &#43; currentColumn())&#43; QString::number(currentRow() &#43; 1);
}//返回当前单元格的公式
QString Spreadsheet::currentFormula() const
{return formula(currentRow(), currentColumn());
}//自定义selectedRange(),用于返回选中的表格范围
QTableWidgetSelectionRange Spreadsheet::selectedRange() const
{QList
}void Spreadsheet::clear()
{//调整表格为0行0列&#xff0c;做到清空所有的表格内容setRowCount(0);setColumnCount(0);//重新设置表格的大小为RowCount*ColumnCountsetRowCount(RowCount);setColumnCount(ColumnCount);for (int i &#61; 0; i
}//从二进制文件中读取数据并写入表格中
bool Spreadsheet::readFile(const QString &fileName)
{QFile file(fileName);if (!file.open(QIODevice::ReadOnly)) {QMessageBox::warning(this, tr("Spreadsheet"),tr("Cannot read file %1:\n%2.").arg(file.fileName()).arg(file.errorString()));return false;}QDataStream in(&file);in.setVersion(QDataStream::Qt_4_3);quint32 magic;in >> magic;if (magic !&#61; MagicNumber) {QMessageBox::warning(this, tr("Spreadsheet"),tr("The file is not a Spreadsheet file."));return false;}clear();quint16 row;quint16 column;QString str;QApplication::setOverrideCursor(Qt::WaitCursor);while (!in.atEnd()) {in >> row >> column >> str;setFormula(row, column, str);}QApplication::restoreOverrideCursor();return true;
}//通过二进制方式写入文件中
bool Spreadsheet::writeFile(const QString &fileName)
{QFile file(fileName);if (!file.open(QIODevice::WriteOnly)) {QMessageBox::warning(this, tr("Spreadsheet"),tr("Cannot write file %1:\n%2.").arg(file.fileName()).arg(file.errorString()));return false;}QDataStream out(&file);out.setVersion(QDataStream::Qt_4_3);out <
{QList
void Spreadsheet::cut()
{copy();del();
}void Spreadsheet::copy()
{//由于在构造函数中setSelectionMode(ContiguousSelection);则选择范围无法大于1&#xff0c;需自定义selectedRange()QTableWidgetSelectionRange range &#61; selectedRange();QString str;//行与行之间用&#39;\n&#39;来分割&#xff0c;列与列之间用&#39;\t&#39;来分割for (int i &#61; 0; i
}void Spreadsheet::paste()
{QTableWidgetSelectionRange range &#61; selectedRange();QString str &#61; QApplication::clipboard()->text();QStringList rows &#61; str.split(&#39;\n&#39;);int numRows &#61; rows.count();int numColumns &#61; rows.first().count(&#39;\t&#39;) &#43; 1;//这里不够智能&#xff0c;必须选定一样大小的范围才能粘贴上去(excel和wps只要选定一个表格即可)if (range.rowCount() * range.columnCount() !&#61; 1&& (range.rowCount() !&#61; numRows|| range.columnCount() !&#61; numColumns)) {QMessageBox::information(this, tr("Spreadsheet"),tr("The information cannot be pasted because the copy ""and paste areas aren&#39;t the same size."));return;}for (int i &#61; 0; i
{QList
}void Spreadsheet::selectCurrentRow()
{selectRow(currentRow());
}void Spreadsheet::selectCurrentColumn()
{selectColumn(currentColumn());
}void Spreadsheet::recalculate()
{for (int row &#61; 0; row
}//判断是否自动重新计算并更新
void Spreadsheet::setAutoRecalculate(bool recalc)
{autoRecalc &#61; recalc;if (autoRecalc)recalculate();
}void Spreadsheet::findNext(const QString &str, Qt::CaseSensitivity cs)
{int row &#61; currentRow();int column &#61; currentColumn() &#43; 1;while (row
{int row &#61; currentRow();int column &#61; currentColumn() - 1;while (row >&#61; 0) {while (column >&#61; 0) {if (text(row, column).contains(str, cs)) {clearSelection();setCurrentCell(row, column);activateWindow();return;}--column;}column &#61; ColumnCount - 1;--row;}QApplication::beep();
}void Spreadsheet::somethingChanged()
{if (autoRecalc)recalculate();emit modified();
}Cell *Spreadsheet::cell(int row, int column) const
{//这里item是一个函数return static_cast
}//设定公式
void Spreadsheet::setFormula(int row, int column,const QString &formula)
{Cell *c &#61; cell(row, column);if (!c) {c &#61; new Cell;setItem(row, column, c);}c->setFormula(formula);
}//返回给定单元格中的公式-->返回是公式的结果
QString Spreadsheet::formula(int row, int column) const
{Cell *c &#61; cell(row, column);if (c) {return c->formula();} else {return "";}
}//返回row行&#xff0c;column列的数据
QString Spreadsheet::text(int row, int column) const
{Cell *c &#61; cell(row, column);if (c) {return c->text();} else {return "";}
}bool SpreadsheetCompare::operator()(const QStringList &row1,const QStringList &row2) const
{for (int i &#61; 0; i
}
#include
{//设置缓存为最新setDirty();
}QTableWidgetItem *Cell::clone() const
{return new Cell(*this);
}void Cell::setData(int role, const QVariant &value)
{//通过特定的模式(role)来显示数据(value)QTableWidgetItem::setData(role, value);//编辑模式(EditRole)下&#xff0c;单元格需要重新计算if (role &#61;&#61; Qt::EditRole)setDirty();
}QVariant Cell::data(int role) const
{//显示文本if (role &#61;&#61; Qt::DisplayRole) {if (value().isValid()) {return value().toString();} else {return "####";}} else if (role &#61;&#61; Qt::TextAlignmentRole) {//返回一个对齐方式if (value().type() &#61;&#61; QVariant::String) {return int(Qt::AlignLeft | Qt::AlignVCenter);} else {return int(Qt::AlignRight | Qt::AlignVCenter);}} else {return QTableWidgetItem::data(role);}
}void Cell::setFormula(const QString &formula)
{setData(Qt::EditRole, formula);
}QString Cell::formula() const
{return data(Qt::EditRole).toString();
}void Cell::setDirty()
{cacheIsDirty &#61; true;
}const QVariant Invalid;//得到单元格的数据
QVariant Cell::value() const
{if (cacheIsDirty) {cacheIsDirty &#61; false;QString formulaStr &#61; formula();//数据以单引号(&#39;)开头&#xff0c;则为字符串if (formulaStr.startsWith(&#39;\&#39;&#39;)) {cachedValue &#61; formulaStr.mid(1);} else if (formulaStr.startsWith(&#39;&#61;&#39;)) {cachedValue &#61; Invalid;QString expr &#61; formulaStr.mid(1);expr.replace(" ", "");expr.append(QChar::Null);//为&#61;号情况下&#xff0c;计算公式的值int pos &#61; 0;cachedValue &#61; evalExpression(expr, pos);if (expr[pos] !&#61; QChar::Null)cachedValue &#61; Invalid;} else {//否则&#xff0c;为double类型bool ok;double d &#61; formulaStr.toDouble(&ok);if (ok) {cachedValue &#61; d;} else {cachedValue &#61; formulaStr;}}}return cachedValue;
}QVariant Cell::evalExpression(const QString &str, int &pos) const
{QVariant result &#61; evalTerm(str, pos);while (str[pos] !&#61; QChar::Null) {QChar op &#61; str[pos];if (op !&#61; &#39;&#43;&#39; && op !&#61; &#39;-&#39;)return result;&#43;&#43;pos;QVariant term &#61; evalTerm(str, pos);if (result.type() &#61;&#61; QVariant::Double&& term.type() &#61;&#61; QVariant::Double) {if (op &#61;&#61; &#39;&#43;&#39;) {result &#61; result.toDouble() &#43; term.toDouble();} else {result &#61; result.toDouble() - term.toDouble();}} else {result &#61; Invalid;}}return result;
}QVariant Cell::evalTerm(const QString &str, int &pos) const
{QVariant result &#61; evalFactor(str, pos);while (str[pos] !&#61; QChar::Null) {QChar op &#61; str[pos];if (op !&#61; &#39;*&#39; && op !&#61; &#39;/&#39;)return result;&#43;&#43;pos;QVariant factor &#61; evalFactor(str, pos);if (result.type() &#61;&#61; QVariant::Double&& factor.type() &#61;&#61; QVariant::Double) {if (op &#61;&#61; &#39;*&#39;) {result &#61; result.toDouble() * factor.toDouble();} else {if (factor.toDouble() &#61;&#61; 0.0) {result &#61; Invalid;} else {result &#61; result.toDouble() / factor.toDouble();}}} else {result &#61; Invalid;}}return result;
}QVariant Cell::evalFactor(const QString &str, int &pos) const
{QVariant result;bool negative &#61; false;if (str[pos] &#61;&#61; &#39;-&#39;) {negative &#61; true;&#43;&#43;pos;}if (str[pos] &#61;&#61; &#39;(&#39;) {&#43;&#43;pos;result &#61; evalExpression(str, pos);if (str[pos] !&#61; &#39;)&#39;)result &#61; Invalid;&#43;&#43;pos;} else {QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}");QString token;while (str[pos].isLetterOrNumber() || str[pos] &#61;&#61; &#39;.&#39;) {token &#43;&#61; str[pos];&#43;&#43;pos;}if (regExp.exactMatch(token)) {int column &#61; token[0].toUpper().unicode() - &#39;A&#39;;int row &#61; token.mid(1).toInt() - 1;Cell *c &#61; static_cast
}