键鼠自动化2.0树形结构讲解
介绍
在键鼠自动化2.0中使用Qtc++实现了全自定义树形结构,实现任务的拖拽,复制粘贴,撤销重做,以及包括树形结构增加序号展示,以及增加搜索功能
实现
1.自定义节点
// 自定义节点类
class TreeNode : public QObject {
public:
TreeNode(QObject *parent = nullptr)
: QObject(parent) {}
public:
bool isInChild = false; //是否接受子节点
QString nodeText; //用于判断的节点名称
QString nodeItemTest; //显示的名称
QVariant taskData; //数据存储
QList<TreeNode*> children; //子节点
TreeNode* parent = nullptr; //父节点
MyStandardItem* item; //item
int number = -1; //临时使用
// 重载==运算符以判断nodeText是否相等
bool operator==(const TreeNode& other) const {
return nodeText == other.nodeText;
}
};
2.拖拽功能
重写拖拽相关函数
void startDrag(Qt::DropActions supportedActions);
void dragLeaveEvent(QDragLeaveEvent* event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void paintEvent(QPaintEvent* event);
部分核心代码
if (sourceNode->parent == nullptr && targetNode->parent == nullptr) { //父与父
//当前是父节点与父节点直接拖拽
int sourceRow = sourceNode->item->row();
qDebug() << MyDebug << "1111111111111111" << targetRow << sourceRow;
if (targetRow != sourceRow) {
//在目标源下插入一行
if (sourceNode->children.isEmpty()) {
TreeNode* node;
if (isAppendParent) {
node = this->appendChileItem(targetNode, sourceNode);
}
else {
node = this->insertTopItem(sourceNode, targetRow);
}
cmdAdd->appNodeList(node);
this->selectionModel()->select(node->item->index(), QItemSelectionModel::SelectCurrent);
//删除来源item
this->removeTopItem(sourceNode);
}
else if (!sourceNode->children.isEmpty()) {
//如果来源里面有子节点就需要递归插入,先查入头节点
TreeNode* newParentNode;
if (isAppendParent) {
newParentNode = this->appendChileItem(targetNode, sourceNode);
}
else {
newParentNode = this->insertTopItem(sourceNode, targetRow);
}
this->selectionModel()->select(newParentNode->item->index(), QItemSelectionModel::SelectCurrent);
//递归插入
for (int i = 0 ; i < sourceNode->children.size(); ++i) {
this->RecursionInsert(newParentNode, sourceNode->children.at(i));
}
cmdAdd->appNodeList(newParentNode);
//删除来源item
this->removeTopItem(sourceNode);
}
else {
qDebug() << MyDebug << "未知动作!!!!!!!!!!!!!!!!!!!!!!!!";
}
}
}
3.复制粘贴
void MyTreeView::copyItemRow()
{
QModelIndexList selectedIndexes = this->selectedIndexes();
if (selectedIndexes.size() <= 0) return;
// 使用自定义的比较函数进行排序(从大到小)
std::sort(selectedIndexes.begin(), selectedIndexes.end(), compareModelIndex);
m_CopyListData.clear();
for (int i = 0; i < selectedIndexes.size(); ++i) {
MyStandardItem* sourceItem = dynamic_cast<MyStandardItem*>(model->itemFromIndex(selectedIndexes.at(i)));
TreeNode* sourceNode = findNodeByText(sourceItem->m_NodeText, m_TreeListData);
TreeNode* nodeData = new TreeNode;
nodeData->isInChild = sourceNode->isInChild;
nodeData->nodeText = sourceNode->nodeText;
nodeData->nodeItemTest = sourceNode->nodeItemTest;
nodeData->parent = sourceNode->parent;
nodeData->taskData = sourceNode->taskData;
if (!sourceNode->children.isEmpty()) {
QList<TreeNode*> childNodeList;
RecursionCopyData(sourceNode, childNodeList);
nodeData->children = childNodeList;
}
m_CopyListData << nodeData;
}
}
void MyTreeView::pasteItemRow()
{
QModelIndexList selectedIndexes = this->selectedIndexes();
MyStandardItem* targetItem;
if (selectedIndexes.size() <= 0) {
targetItem = dynamic_cast<MyStandardItem*>(model->item(model->rowCount() - 1));
}
else {
std::sort(selectedIndexes.begin(), selectedIndexes.end(), compareModelIndex);
targetItem = dynamic_cast<MyStandardItem*>(model->itemFromIndex(selectedIndexes.first()));
}
if (!targetItem) return;
TreeNode* targetNode = findNodeByText(targetItem->m_NodeText, m_TreeListData);
QList<TreeNode*> undoNodeList;
for (int i = 0; i < m_CopyListData.size(); ++i) {
TreeNode* node = m_CopyListData.at(i);
//判断目标行是父节点还是子节点
if (targetNode->parent == nullptr) {
TreeNode* newParentNode = insertTopItem(node, targetItem->row() + 1);
//如果存在子节点递归插入
if (!node->children.isEmpty()) {
for (int number = 0 ; number < node->children.size(); ++number) {
RecursionInsert(newParentNode, node->children.at(number), false);
}
}
undoNodeList << newParentNode;
}
else {
// qDebug() << MyDebug << "222222222222222" << targetNode->nodeItemTest << targetItem->row() << node->nodeItemTest;
TreeNode* nodeTemp = insertChileItem(targetNode->parent, node, targetItem->row() + 1);
if (!node->children.isEmpty()) {
for (int number = 0 ; number < node->children.size(); ++number) {
RecursionInsert(nodeTemp, node->children.at(number), false);
}
}
undoNodeList << nodeTemp;
}
}
AddRowCommand* cmd = new AddRowCommand(this, undoNodeList);
m_Undo.append(cmd);
m_Redo.clear();
emit signalUpdateTableView();
}
4.撤销重做
实现基类command,虚函数撤销与重做,依次实现add和del以及多行拖拽类存储。
class Command {
public:
virtual ~Command() = default;
virtual void undo() = 0;
virtual void redo() = 0;
};
class AddRowCommand : public Command
{
public:
AddRowCommand(MyTreeView* view, QList<TreeNode*> nodeList = QList<TreeNode*>());
void appNodeList(TreeNode* nodeData);
void undo();
void redo();
private:
MyTreeView* m_View;
QList<int> m_RowList;
QList<bool> m_IsParentList;
QList<QString> m_ParentNodeText;
QList<TreeNode*> m_NodeList;
};
//该类注意事项
//1.要注意先增加类对象再删除node,否则删除后再增加找不到子节点和自身对象等等问题
class DelRowCommand : public Command
{
public:
DelRowCommand(MyTreeView* view, QList<TreeNode*> nodeList = QList<TreeNode*>());
void appNodeList(TreeNode* nodeData);
void undo();
void redo();
private:
MyTreeView* m_View;
QList<int> m_RowList; //item的行存储,因为item会丢失所以保存行
QList<bool> m_IsParentList; //保存item是否是父节点
QList<QString> m_ParentNodeText; //父节点nodetext保存
QList<TreeNode*> m_NodeList;
};
class DragRowCommand : public Command
{
public:
DragRowCommand(MyTreeView* view, QList<Command*> cmdList, bool bigToSmall);
void undo();
void redo();
private:
MyTreeView* m_View;
QList<Command*> m_CmdList;
bool m_BigToSmall;
};
5.行号
如何实现QTreeView的行号功能?
这里采用了QTabelView功能,布局中放下了tableview和treeview,行号在treeview的左侧,当滚动或者新增数据,来更新一下视图数据,当展开或者合并节点,同步更新数据,来实现所有节点的独立行号。
public slots:
//读取滚动条数据同步
void onReadScrollValue(int value);
//遍历节点子节点下的所有数量
int CountTotalChildren(TreeNode* node);
//递归更新子节点数据
void RecursionUpdateChild(TreeNode* node);
//展开
void onEntered(const QModelIndex &index);
//合并
void onCollapsed(const QModelIndex &index);
//获取是删除还是del
void onReadIsAddAndDel(int isAdd, bool isHide);
//更新视图数据
void onUpdateView();
protected:
void wheelEvent(QWheelEvent* event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
6. QTreeview的搜索功能
核心代码节点递归搜索,递归搜索节点返回所有包含的数据,通过存到列表中,加一个当前index来实现上下的切换,
QList<TreeNode*> MyTreeView::findNodeItemText(const QString &findStr)
{
QList<TreeNode*> TreeNodeList;
for (int i = 0; i < m_TreeListData.size(); ++i) {
TreeNode* node = m_TreeListData.at(i);
recursiveFindNodeByTextItem(findStr, node, TreeNodeList);
}
return TreeNodeList; // 未找到匹配的节点
}
void MyTreeView::recursiveFindNodeByTextItem(const QString &targetText, TreeNode *currentNode, QList<TreeNode *> &list)
{
QString nodeStr = currentNode->nodeItemTest;
nodeStr.remove(Tools::getInstance()->m_HtmlTitleBegin);
nodeStr.remove(Tools::getInstance()->m_HtmlTitleEnd);
nodeStr.remove(Tools::getInstance()->m_HtmlTextBegin);
nodeStr.remove(Tools::getInstance()->m_HtmlTextEnd);
nodeStr.remove(Tools::getInstance()->m_HtmlHiglightBegin);
nodeStr.remove(Tools::getInstance()->m_HtmlHiglightEnd);
if (nodeStr.contains(targetText)) {
list.append(currentNode);
}
for (TreeNode* child : currentNode->children) {
recursiveFindNodeByTextItem(targetText, child, list);
}
}
7.其他功能
1.关于如何实现的treeview的富文本
重新绘制的富文本,当然会比正常文本的资源消耗更高,速度较慢,实际测试几十万行并不会卡,但是比较慢一点,日常能接受。
//自定义代理类用来绘制文字富文本
class RichTextDelegate : public QStyledItemDelegate
{
public:
RichTextDelegate(QObject* parent = nullptr)
: QStyledItemDelegate(parent)
{}
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
void RichTextDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QString text = index.data(Qt::DisplayRole).toString();
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
painter->save();
// 手动绘制背景,用于选中和进入的样式
if (opt.state & QStyle::State_Selected) {
// 选中状态下的背景颜色为204, 232, 255
QBrush backgroundBrush(QColor(185, 232, 255));
painter->fillRect(opt.rect, backgroundBrush);
}
else if (opt.state & QStyle::State_MouseOver) {
// 进入状态下的背景颜色为225, 243, 255
QBrush backgroundBrush(QColor(235, 243, 255));
painter->fillRect(opt.rect, backgroundBrush);
}
// 手动绘制文本
QTextDocument doc;
doc.setHtml(text);
opt.text = "";
painter->translate(option.rect.topLeft());
doc.drawContents(painter);
painter->restore();
}