基于Qt的app开发第八天
写在前面
笔者是一个大一下计科生,本学期的课程设计自命题完成一个督促学生自律的打卡软件,目前已经完成了待办和打卡部分功能,本篇要完成规划板块不需要存储就能实现的功能
需求分析
这一板块内容相比前两个板块还有一些特殊,因为它需要展示两张表,而且每个大规划中的任务数不确定
显式顺序分析:第一个界面视图展示一列列长期规划,然后这一个个规划块里边又有一个一个小任务,且标注完成状态;选中规划块的列点击修改会跳转到第二个界面,然后每个任务块算一行,选中对应行可以修改,点新建可以新加一行,然后跳转到第三个界面;点击新建也是跳转到第二个界面,然后后续就是一样的
实现思路
(1)难点分析
这个项目的难点在于对tableView的操作,tableView可以设置行选中和列选中。这个板块第一个界面设置列选中,第二个界面设置行选中。通过tableView的QstandardItModel的索引来插每行和每列,新增小任务就是在最后一行的上一行插入新任务块
(2)思路梳理
先把控件改成tableView,第一个界面不需要插表头,第二个界面要插表头。然后还是用QString型的QList数据类型来做中介变量,因为这次的时间和打卡板块是一样的,只需要String存储。
实现数据导入和打卡板块几乎是一致的,只需要关注行和列的索引
具体实现
(1)修改控件
修改控件如图,并修改命名
(2)初始化表格
初始化表格就仿照前两个板块即可,包含引用头文件、开模型。
//这个函数的作用是获取并初始化第一个界面的TableView控件
void Plan::GetPlanShowTableView()
{//获取界面中的表格对象QTableView *planShow = ui->planShowTableView;// 设置表格的点击模式为列单选ui->planShowTableView->setSelectionBehavior(QAbstractItemView::SelectColumns); // 选择整列ui->planShowTableView->setSelectionMode(QAbstractItemView::SingleSelection); // 单选模式planShow->setModel(model_1);
}//这个函数的作用是获取并初始化第二个界面的TableView控件
void Plan::GetPlanDecideTableView()
{//获取界面中的表格对象QTableView *planDecide = ui->planDecideTableView;//设置planDecideTableView的表头model_2->setColumnCount(4);model_2->setHorizontalHeaderLabels({"开始时间", "结束时间", "事项", "完成情况"});//设置表格的点击模式为行单选ui->planDecideTableView->setSelectionBehavior(QAbstractItemView::SelectRows); // 选择整行ui->planDecideTableView->setSelectionMode(QAbstractItemView::SingleSelection); // 单选模式planDecide->setModel(model_2);
}
声明变量和包含头文件不重复了,这里附上两个表格的初始化函数
注意要在构造函数中调用这两个函数,笔者刚才跑出来发现没有表格,仔细检查发现是因为构造函数没调用函数
(3)实现新建
这个板块的新建稍微有些复杂,其实也不是复杂,就是实现长度更长了一点,现在笔者来梳理一下传递顺序:
第三个界面里设置起始时间和结束时间,事项以及完成情况——>在第二个界面里的表格出现这么一行,然后在第二个界面可以输入自我评价——>在第一个界面里的表格里出现一列,这一列包含标题、任务块、自我评价。任务块就是第二个界面表格里的内容
由第三个界面传到第二个界面可以使用QString型的动态数组当中介变量,第二个界面传到第一个界面稍稍与前边的板块不同
笔者这里说明一下我的思路:先把标题传回第一个界面,再把自我评价传回第一个界面,然后每个任务块还是使用动态数组传递,每个任务块插入的基准是最后一行的上一行
下边再来分析一下函数实现的顺序:
按钮槽函数自然还是要分成修改和新建功能的if判断
新建功能因为没有原文本,所以直接无脑往回传就可以了。在第三个界面的应用按钮的槽函数里将文本框内容传到第二个界面里,在第二个界面的应用槽函数里将表格里的内容传到第一个界面里
做到第二个界面向第一个界面传递时笔者遇到了问题,因为任务块可能不只有一个,所以需要用一个循环遍历所有行。然后每一行都向第一个界面里传数据
还有另一个问题:因为两个界面里都有应用和修改按钮,笔者最开始觉得设置一个模式选择器就可以,逐步深入的过程中发现还是需要每个界面各一个模式选择器
if(addOrRevise_1==1){//这几行代码的作用是获取第二个界面输入框里的文本QString title=ui->nameInput->text();QString evalution=ui->evaluationInput->toPlainText();int col = model_1->columnCount();model_1->insertColumn(col);QStandardItem *item=new QStandardItem(title);model_1->setItem(0,col,item);item=new QStandardItem(evalution);model_1->setItem(1,col,item);int currentRow=0;//这个循环的作用是把表格里的内容全部放到第一个界面里while(currentRow<model_2->rowCount()){//这几行代码是向中介数组中导入表格数据QModelIndex index = model_2->index(currentRow, 0);QString startTime = index.data().toString();index=model_2->index(currentRow,1);QString endTime=index.data().toString();index=model_2->index(currentRow,2);QString event=index.data().toString();index=model_2->index(currentRow,3);QString isSuccess=index.data().toString();shiftPlanList.append(startTime);shiftPlanList.append(endTime);shiftPlanList.append(event);shiftPlanList.append(isSuccess);//这几行代码的作用是向planShowtableView里添加一个任务块int lastRow = 0;if (model_1->hasChildren()){// 从模型底部向上查找第一个有数据的单元格for (int i = model_1->rowCount() ; i >= 0; i--){if (!model_1->index(i, col).data().isNull()){lastRow = i; // 找到后,插入位置为这一行break;}}}for (int crow = lastRow,cindex=0; crow < lastRow+4; crow++,cindex++){QStandardItem *item = new QStandardItem(shiftPlanList.at(cindex));model_1->setItem(crow, col, item);}QStandardItem *item = new QStandardItem(evalution);model_1->setItem(lastRow+4,col,item);shiftPlanList.clear();currentRow++;}//把这个索引置零,防止干扰后续操作currentRow=0;//这句代码的作用是清空这个数组以及第二个界面的内容,方便下次使用shiftPlanList.clear();if (model_2){// 清空所有行(保留表头)model_2->removeRows(0, model_2->rowCount());}ui->evaluationInput->clear();ui->nameInput->clear();addOrRevise_1=0;}
这是点了第二个界面的应用按钮之后的槽函数
if(addOrRevise_2==1){//这几行代码的作用是获取第三个界面输入框里的文本QString startTime=ui->plannextnext_startTimeInput->text();QString endTime=ui->plannextnext_endTimeInput->text();QString event=ui->plannextnext_eventInput->text();QString isSuccess=ui->plannextnext_isSuccessChoice->currentText();//这几行代码的作用是向中介数组添加数据shiftTaskList.append(startTime);shiftTaskList.append(endTime);shiftTaskList.append(event);shiftTaskList.append(isSuccess);//这几行代码的作用是向planDecidetableView里添加一行int row = model_2->rowCount();model_2->insertRow(row);for (int col = 0; col < 4; col++){QStandardItem *item = new QStandardItem(shiftTaskList.at(col));model_2->setItem(row, col, item);}//这句代码的作用是清空这个数组以及第三个界面的内容,方便下次使用shiftTaskList.clear();ui->plannextnext_startTimeInput->setText("");ui->plannextnext_endTimeInput->setText("");ui->plannextnext_eventInput->setText("");ui->plannextnext_isSuccessChoice->setCurrentText(0);addOrRevise_2=0;}
这是第三个界面的应用按钮的槽函数
核心思路就是上边提到的,在这里着重说一下笔者遇到的困难:
编程前总觉得自己的思路特别完美,但是在没有经验的情况下,考虑难免是不全面的,所以总会出现各种意料之外的问题。笔者对第一个界面的表格操作就是屡屡出错,刚开始先插成一行一行的了 ,后来又把自我评价给覆盖了,后来又出现各种问题。那笔者是怎么解决的呢?就是往进加一些辅助变量,然后再改一下插入逻辑,这个板块越写笔者越觉得像屎山了,现在修改功能还没实现事情就有点超出笔者的控制范围了。
不过没有关系,没有屎山代码的积累就写不出好代码。作为我的处女作,屎山也是很正常的
(4)实现修改
修改功能又比新建功能复杂了一些,因为它还涉及到把表中数据往后边的界面传递。
还是先梳理逻辑:用户按下修改按钮,然后被选中的一列存进第二个界面,标题存在标题输入框,自我评价存在自我评价输入框,每一个任务块一行一行地存,这里肯定也要用一个循环了。然后把这列东西全部删除,点第二个界面的应用按钮后再把这一列加进去,需要注意插入位置一定是选中列;如果在第二个界面用户点击修改,被选中的一行是任务块,然后跳到第三个界面,把这一行的数据存进第三个界面,然后把这行删除,点第三个界面的应用按钮把这一行加进去,插入位置是选中行
//这个函数的作用是修改规划----------------------------------未完成
void Plan::on_revisePlanButton_clicked()
{ui->stackedWidget->setCurrentIndex(1);addOrRevise_1=2;//这个for循环是为了获取当前选中列的最后一行int lastRow = 0;if (model_1->hasChildren()){// 从模型底部向上查找第一个有数据的单元格for (int i = model_1->rowCount() ; i >= 0; i--){if (!model_1->index(i, currentCol).data().isNull()){lastRow = i;break;}}}//这几行代码是把标题和自我评价传到第二个界面里QModelIndex index = model_1->index(0, currentCol);QString title=index.data().toString();ui->nameInput->setText(title);index=model_1->index(lastRow,currentCol);QString evalution=index.data().toString();ui->evaluationInput->setText(evalution);int currentRow=1;//这个while循环的作用是将第一个界面的表格中的每个任务块导入第二个界面的表格中while(currentRow<lastRow){index=model_1->index(currentRow,currentCol);QString startTime = index.data().toString();index=model_1->index(currentRow+1,currentCol);QString endTime=index.data().toString();index=model_1->index(currentRow+2,currentCol);QString event=index.data().toString();index=model_1->index(currentRow+3,currentCol);QString isSuccess=index.data().toString();currentRow+=4;shiftPlanList.append(startTime);shiftPlanList.append(endTime);shiftPlanList.append(event);shiftPlanList.append(isSuccess);//这几行代码的作用是向planDecidetableView里添加一行int taskRow = model_2->rowCount();model_2->insertRow(taskRow);for (int col = 0; col < 4; col++){QStandardItem *item = new QStandardItem(shiftPlanList.at(col));model_2->setItem(taskRow, col, item);}shiftPlanList.clear();}
}
这个函数的作用是点击第一个界面的修改函数之后把所有数据传进第二个界面中
困难倒是没啥,关键是细节要到位,要不然行和列可能会冲突
else if(addOrRevise_1==2){model_1->removeColumn(currentCol);//这几行代码的作用是获取第二个界面输入框里的文本QString title=ui->nameInput->text();QString evalution=ui->evaluationInput->toPlainText();int col = currentCol;model_1->insertColumn(col);QStandardItem *item=new QStandardItem(title);model_1->setItem(0,col,item);item=new QStandardItem(evalution);model_1->setItem(1,col,item);int currentRow=0;//这个循环的作用是把表格里的内容全部放到第一个界面里while(currentRow<model_2->rowCount()){//这几行代码是向中介数组中导入表格数据QModelIndex index = model_2->index(currentRow, 0);QString startTime = index.data().toString();index=model_2->index(currentRow,1);QString endTime=index.data().toString();index=model_2->index(currentRow,2);QString event=index.data().toString();index=model_2->index(currentRow,3);QString isSuccess=index.data().toString();shiftPlanList.append(startTime);shiftPlanList.append(endTime);shiftPlanList.append(event);shiftPlanList.append(isSuccess);//这几行代码的作用是向planShowtableView里添加一个任务块int lastRow = 0;if (model_1->hasChildren()){// 从模型底部向上查找第一个有数据的单元格for (int i = model_1->rowCount() ; i >= 0; i--){if (!model_1->index(i, col).data().isNull()){lastRow = i; // 找到后,插入位置为这一行break;}}}for (int crow = lastRow,cindex=0; crow < lastRow+4; crow++,cindex++){QStandardItem *item = new QStandardItem(shiftPlanList.at(cindex));model_1->setItem(crow, col, item);}QStandardItem *item = new QStandardItem(evalution);model_1->setItem(lastRow+4,col,item);shiftPlanList.clear();currentRow++;}//把这个索引置零,防止干扰后续操作currentRow=0;//这句代码的作用是清空这个数组以及第二个界面的内容,方便下次使用shiftPlanList.clear();if (model_2){// 清空所有行(保留表头)model_2->removeRows(0, model_2->rowCount());}ui->evaluationInput->clear();ui->nameInput->clear();addOrRevise_1=0;}
这个函数和新建唯一的区别就是把原列删除,然后把删除那列插一个新的进去。这段代码没有复用我感觉很屎,但是没办法,前期都是这样做的,为了保持架构的统一,还有让我的思路没那么混乱,我决定就这样继续堆屎了
//这个函数的作用是修改已经存在的规划更细致的部分
void Plan::on_reviseProcessButton_clicked()
{addOrRevise_2=2;ui->stackedWidget->setCurrentIndex(2);//这段代码的作用是将表格中被选中的行的内容依次存进中介数组if (getCurrentRow != -1){for (int col = 0; col < 4; col++){QModelIndex index = model_2->index(getCurrentRow, col);QVariant data = model_2->data(index);shiftTaskList.append(data.toString());}}//这段代码的作用是在界面中设置中介数组中的内容ui->plannextnext_startTimeInput->setText(shiftTaskList[0]);ui->plannextnext_endTimeInput->setText(shiftTaskList[1]);ui->plannextnext_eventInput->setText(shiftTaskList[2]);int isSuccessIndex = ui->plannextnext_isSuccessChoice->findText(shiftTaskList[3]);if (isSuccessIndex != -1){ui->plannextnext_isSuccessChoice->setCurrentIndex(isSuccessIndex);}//要把中介数组清空,方便下次使用shiftTaskList.clear();
}
这是第二个界面的修改按钮的槽函数,作用就是把第二个界面表格中的东西导入第三个界面的输入框中
else if(addOrRevise_2==2){//这段代码的作用是获取当前界面的输入框的内容QString startTime=ui->plannextnext_startTimeInput->text();QString endTime=ui->plannextnext_endTimeInput->text();QString event=ui->plannextnext_eventInput->text();QString isSuccess=ui->plannextnext_isSuccessChoice->currentText();//这段代码的作用是把捕获到的内容再存进中介数组中shiftTaskList.append(startTime);shiftTaskList.append(endTime);shiftTaskList.append(event);shiftTaskList.append(isSuccess);//这个循环的作用是把中介数组的内容传进选中的行中for (int col = 0; col < 4; col++){QStandardItem *item = new QStandardItem(shiftTaskList.at(col));model_2->setItem(getCurrentRow, col, item);}//清空中介数组方便下次使用shiftTaskList.clear();//将界面置空方便下次使用ui->plannextnext_startTimeInput->setText("");ui->plannextnext_endTimeInput->setText("");ui->plannextnext_eventInput->setText("");ui->plannextnext_isSuccessChoice->setCurrentIndex(0);//将选择模式置空,防止干扰下次使用addOrRevise_2=0;}
这个分支就是完成第三个界面的应用,把选中那一行的内容改成第三个界面修改后的内容
篇末总结
做完这个板块笔者又有了很多感悟,也许这是一种厚积薄发?
1.初期编程不难,难的是一直调试改bug,笔者对表格一直不出数据这个问题修改很久
2.人毕竟是有局限性的,一直做下去就会有越来越多的理解,不做的话永远想不到那一步,有些实现逻辑往往是突发奇想的
3.前边的应用按钮槽函数分情况其实是没有必要的,因为修改和新建的区别无非不过就是有没有往后边的界面传数据,这一点在笔者最开始的时候没有意识到,做到后边再梳理逻辑时想明白了
4.中介数组更适合数据集较大时,这样用一个循环比较方便,其实作用并不是很大,那笔者先前认为的天才想法其实也就那样,归根结底是经验不足,对Qt的理解还不到位
5.变量最好不要高一层第一层做同名变量,因为这样很可能分不清,然后出bug找不到;变量能少用就少用,代码复用的时候改的不用那么费劲
6.要注意库函数的传参顺序,可能会出现问题
在这个项目中我的进步很大,越往后做越觉得前期的工作是在堆屎,但是以后我毕竟不会再做C++相关的项目了,所以重构的话可能会是很久以后的事情,现在就往屎山继续堆屎,处女作嘛,屎山很正常,只要我能进步就行了。后续应该会把这个项目传到github上存着,留作纪念
这个板块是目前我事实上做的最难的一个板块,但是花的时间却没有第一个板块多,实现了从无到有之后,难度真的大大减小。