[QT_015]Qt学习之基于条目控件的自定义特性(拖拽+右键菜单+样式)

本文转自:《Qt编程指南》        作者:奇先生

Qt编程指南,Qt新手教程,Qt Programming Guide

本节介绍基于条目控件的定制特性,首先介绍条目的拖拽,列表控件、表格控件、树形控件内置了支持拖拽的特性,添加少许代码即可使用。然后介绍控件的右 键菜单构造方 法,采用的方法是修改基类 QWidget 的 contextMenuPolicy 属性,并添加弹出菜单槽函数,这种方法对于所有 QWidget 派生类控件都通用。最后简要介绍基于条目控件的样式表设置,定制高亮条目显示、双色交替行显示、表头和角按钮配色等。三个小节分别设置一个示例,学以致用。
 

8.4.1 条目的拖拽(Drag and Drop)


QListWidget、QTableWidget、 QTreeWidget 自带了拖拽条目的功能,拖拽通常有两种应用场景:
第一种场景是内部拖拽
仅在控件内部使用,用于调整条目的先后顺序,实现控件内的条目移动功能。对于树形条目,内部移动还可以改变条目的父子关系,比如将子节点提升为兄弟节 点。实现 控件内部拖拽只需要一句代码,设置拖拽模式为 QAbstractItemView::InternalMove,即

ui->treeWidget->setDragDropMode(QAbstractItemView::InternalMove);

对于列表控件、表格控件、树形控件,都是调用一样的函数 setDragDropMode(QAbstractItemView::InternalMove) 。QAbstractItemView::InternalMove 只会在控件内部移动条目,不会复制条目,仅仅调整排列顺序或层级。

拖拽的第二种场景,就是跨界拖拽
在控件之间拖拽,比如将 listWidget1 的条目 A 拖给 listWidget2,这种拖拽效果是复制一个新的条目 A 给 listWidget2,不是移动,而是条目新建和数据复制。结果就是 listWidget1 有自己的 A 条目, listWidget2 创建了新条目,新条目数据克隆自 listWidget1 的 A 条目。这种模式也支持 listWidget1 的条目自己拖给自己,就是克隆新的条目 A ,添加给 listWidget1 自身。不仅是同类型的条目控件之间可以拖拽,不同类型如 QListWidget、QTableWidget、 QTreeWidget ,它们三者之间也可以互相拖拽条目。效果都是复制源条目,将新建的条目添加给接收拖拽的控件。
为基于条目的控件启用跨界拖拽功能,需要进行如下设置:


(1)设置控件的 dragEnabled 属性为 true;
(2)设置控件的视口 viewport() 的 acceptDrops 属性为 true;
(3)为用户显示拖拽动作的鼠标效果,设置控件的 showDropIndicator 属性为 true;
(4)设置控件的拖拽模式为能拖能拽的 QAbstractItemView::DragDrop 。


拖拽条目一般都是单选模式,只拖拽一个。(其实多选模式可以拖拽多个,就是不太常见。)
对于单选模式拖拽条目,示例代码如下:

QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection); //单选模式
listWidget->setDragEnabled(true);  //可以拖出源条目
listWidget->viewport()->setAcceptDrops(true); //可以接收拖入
listWidget->setDropIndicatorShown(true); //启用拖拽的显示效果
listWidget->setDragDropMode(QAbstractItemView::DragDrop); //使用能拖能拽的模式


对于基于条目的三种控件,内部拖拽是条目移动,跨界拖拽是条目新建并复制。我们下面通过简单示例学习拖拽功能。
打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 itemdragdrop,创建路径 D:\QtProjects\ch08,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
我们打开 widget.ui 界面文件,按照下图拖入控件:

第一行是列表控件,默认对象名 listWidget;
第二行是树形控件,默认对象名 treeWidget;
第三行是表格控件,默认对象名 tableWidget;
第四行是两个单选按钮,“内部拖拽”radioButtonInter、“跨界拖拽”radioButtonOuter,第四行使用水平布局。
窗口整体使用垂直布局,尺寸 500*500 。
我们右击单选按钮,为两个单选按钮分别添加槽函数,选第二个 clicked(bool) :

三个条目控件的内容用代码编写,图形界面设置就到这,我们下面开始编辑头文件 widget.h 的代码:

#ifndef WIDGET_H#define WIDGET_H#include <QWidget>
#include <QListWidget>//列表控件
#include <QTreeWidget>//树形控件
#include <QTableWidget>//表格控件namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();private slots:void on_radioButtonInter_clicked(bool checked);void on_radioButtonOuter_clicked(bool checked);private:Ui::Widget *ui;//设置 QAbstractItemView 派生类的跨界拖拽功能//对列表控件、树形控件、表格控件通用,C++多态性void SetOuterDragDrop( QAbstractItemView *view );};#endif // WIDGET_H

widget.h 添加了三个条目控件类的头文件引用;
然后是两个单选按钮的槽函数;
最后添加了一个设置启用跨界拖拽功能的函数,这个函数对三种条目控件都是通用的。

下面来看 widget.cpp源文件的内容,首先是头文件包含、构造函数、析构函数:

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);//构造列表控件的条目for(int i=0; i<5; i++){QListWidgetItem *itemL = new QListWidgetItem( ui->listWidget );itemL->setText( tr("listItem %1").arg(i) );}//设置树形控件2列ui->treeWidget->setColumnCount( 2 );//各列均匀拉伸ui->treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);//树形控件构造条目for(int i=0; i<5; i++){QTreeWidgetItem *itemT = new QTreeWidgetItem( ui->treeWidget );itemT->setText(0, tr("treeItem %1, 0").arg(i) );itemT->setText(1, tr("t%1, 1").arg(i) );}//设置表格 3*3ui->tableWidget->setColumnCount( 3 );ui->tableWidget->setRowCount( 3 );//各列均匀拉伸ui->tableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );//构造表格条目for(int i=0; i<3; i++){for(int j=0; j<3; j++){QTableWidgetItem *itemTA = new QTableWidgetItem();itemTA->setText( tr("tableItem %1, %2").arg(i).arg(j) );ui->tableWidget->setItem( i, j, itemTA );}}//默认选中内部移动模式ui->radioButtonInter->setChecked(true);on_radioButtonInter_clicked(true);//启用内部移动
}Widget::~Widget()
{delete ui;
}

构造函数首先为列表控件新建了 5 个条目,按照行编号设置条目文本。
然后设置树形控件的列数为 2,设置树头视图使得各列均匀拉伸;
为树形控件新建了 5 个顶级条目,根据行号、列号设置 5 个条目的文本,每个条目 2 列文本。
然后设置表格为 3 行  3 列,设置水平表头使得各列均匀拉伸;
为表格新建 3*3 个条目,每个条目根据行号、列号设置文本。
最后设置“内部拖拽”单选按钮为选中状态,并调用该按钮槽函数设置三个条目控件为内部拖拽模式。

接下来看看“内部拖拽”单选按钮的槽函数:

void Widget::on_radioButtonInter_clicked(bool checked)
{if(checked){//列表控件启用内部移动ui->listWidget->setDragDropMode(QAbstractItemView::InternalMove);//树形控件启用内部移动ui->treeWidget->setDragDropMode(QAbstractItemView::InternalMove);//表格控件启用内部移动ui->tableWidget->setDragDropMode(QAbstractItemView::InternalMove);}
}

这个函数非常简单,判断该按钮如果为选中状态,那么调用三个条目控件的 setDragDropMode() 函数设置拖拽模式为内部移动。

下面看看“跨界拖拽”单选按钮的槽函数:

void Widget::on_radioButtonOuter_clicked(bool checked)
{if(checked){//列表控件启用跨界拖拽SetOuterDragDrop(ui->listWidget);//树形控件启用跨界拖拽SetOuterDragDrop(ui->treeWidget);//表格控件启用跨界拖拽SetOuterDragDrop(ui->tableWidget);}
}

这个函数也是超级简单的,检查按钮如果为选中状态,那就为三个条目控件调用 SetOuterDragDrop() 函数。
实际干活的是下面的 SetOuterDragDrop() 函数:

//启用跨界拖拽
void Widget::SetOuterDragDrop(QAbstractItemView *view)
{view->setSelectionMode(QAbstractItemView::SingleSelection); //单选模式view->setDragEnabled(true);  //可以拖出源条目view->viewport()->setAcceptDrops(true); //视口可以接收拖入view->setDropIndicatorShown(true); //启用拖拽的显示效果view->setDragDropMode(QAbstractItemView::DragDrop); //使用能拖能拽的模式
}

这个函数对 QAbstractItemView  派生类对象是通用的,QListWidget、QTableWidget、 QTreeWidget 都是它的派生类(孙辈的派生类)。该函数第一句是设置选中模式为单选;第二句是设置可以拖出源条目;第三句是设置控件的视口(显示可见部分)可以接收条目拖入;第四句是显示拖拽的鼠标效果;最后是设置拖拽模式为 QAbstractItemView::DragDrop,能够拖出和拽入。
通过单独的 SetOuterDragDrop() 函数设置跨界拖拽,避免为每个条目控件都敲五局代码,省了很多事,以后不管几个条目控件,都只需要调用 SetOuterDragDrop() 函数就能设置跨界拖拽了。
本示例代码讲解到这,下面构建运行该程序,程序启动时效果如下图:

测试内部拖拽功能,对三个控件内部进行拖拽:

列表控件的内部拖拽只改变条目的先后顺序;树形控件的内部拖拽不仅可以改变先后顺序,也可以改变层级关系;
测试表格控件内部拖拽,将(0,0)单元格条目拖到(0,1)单元格后,(0,0)单元格被清空,
(0,1)单元格填上了 "tableItem 0,0" 文本,表格控件的内部拖拽相当于剪切+粘贴,会清空源单元格,覆盖目的单元格。

我们点击“跨界拖拽”按钮,进入跨界拖动的模式:

我们把树形控件的条目"treeItem 4,0     t4,1" 拖到列表控件和表格控件里面的底部空白位置,可以看到进入列表控件后被拆成两个条目,占据列表控件的末尾两行;拖给表格控件后,拆成两列,是两个单元格条目。
示例中列表控件只有 1 列,树形控件 2 列,表格控件 3 列,它们之间是可以互相拖入条目的,但是一般不建议列数不同的控件互相拖拽条目。这里只是测试功能,实际程序中要尽量在列数相同的情况下拖拽。读者还可以进行其他拖拽测试,我们下面小节开始讲解右键菜单的创建,这个知识稍微提前了一点讲解,但是很实用。
 

8.4.2 自定义右键菜单(ContextMenu)


右键菜单功能很常见,我们添加槽函数的操作就是通过右键菜单实现的。在 Qt 库中,每一条菜单项称为 QAction,多条菜单项组成一个菜单,菜单类是 QMenu。菜单项 QAction 不仅可以用于菜单,工具栏的按钮也是 QAction 实现的。QAction 常用构造函数如下:

QAction(const QString & text, QObject * parent)
QAction(const QIcon & icon, const QString & text, QObject * parent)

菜单项可以有文本和图标,第一个构造函数只有文本,第二个构造函数同时带了图标和文本。菜单项一般直接以窗口为 parent,方便全局管理。
用户点击菜单项触发如下信号:

void QAction::triggered(bool checked = false) //点击菜单项触发信号

参数里 checked 一般用不到,只有在设置菜单项可以勾选的时候才用。菜单项设置勾选功能是通过下面函数实现:

void  QAction::setCheckable(bool)


单独的菜单项是不能弹出显示的,需要将菜单项添加到菜单或工具栏才能使用。菜单 QMenu 的构造函数如下:

QMenu(QWidget * parent = 0)
QMenu(const QString & title, QWidget * parent = 0)

菜单本身可以有一个文本,比如 QtCreator 拥有多个菜单,第一个是“文件”菜单,“文件”两个字就是菜单的文本,如下图所示:

通常情况下,由多个 QAction 组成一个 QMenu,然后由多个 QMenu 组成一个 QMenuBar。
QtCreator 有 8 个 QMenu 菜单,共同组成一行 QMenuBar(菜单条,或叫菜单栏),QMenuBar 以后到了主窗口程序章节再详细讲解,本小节只是初步学一下右键菜单功能。

右键菜单通常不显示菜单自身的文本,只会弹出各个菜单项。创建了 QAction 对象后,将其添加到菜单的函数如下:

void QMenu::addAction(QAction * action)

QMenu 还有其他 方便添加菜单项的函数,这里不一一列举了,以后主窗口程序章节会详细讲解这个类。
QMenu 的基类是 QWidget,本身也属于一个控件,弹出菜单一般使用如下两个函数:

void QMenu::popup(const QPoint & p, QAction * atAction = 0) //异步弹出菜单
QAction * QMenu::​exec(const QPoint & p, QAction * action = 0)//同步弹出菜单

这两个函数弹出菜单的效果是一样的,唯一的差别是 exec() 函数会返回被用户点击的菜单项指针,如果用户没有点击菜单项,那么返回 NULL。
这两个函数参数也是一样的,第一个是菜单显示位置的坐标 p,注意 p 是以屏幕左上角为原点(0,0),而控件反馈的坐标一般是相对控件自己的内部坐标,需要用转换函数 **widget->mapToGlobal( p ) ,将 p 转换为屏幕坐标。第二个参数 action 是指显示菜单时保证菜单项 action 恰好显示在 p 点位置,方便用户优先选择 action 菜单项。

QWidget 派生类控件都支持自定义右键菜单,但是默认没有启用这个功能,要启用这个功能,需要进行两步操作:
(1)设置控件的 contextMenuPolicy 属性数值为 Qt::CustomContextMenu,即自定义右键菜单模式;
(2)为请求弹出右键菜单的信号添加槽函数,该信号原型为:

void QWidget::customContextMenuRequested(const QPoint & pos)   //pos 是相对控件自身的坐标,需要转为全局屏幕坐标弹菜单

为请求右键菜单信号添加槽函数后,在槽函数里执行菜单的 popup() 或 exec() 就能弹出菜单了。

菜单类 QMenu 主要是显示用途,而实际的功能是通过菜单项 QAction 的槽函数实现,一般每个 QAction 都对应一个槽函数,该槽函数关联到 QAction::triggered() 信号。

下面我们通过一个示例,为列表控件添加自定义的菜单,实现添加、编辑、删除条目和清空所有条目的功能。
打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 contextmenu,创建路径 D:\QtProjects\ch08,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
我们打开 widget.ui 界面文件,按照下图拖入控件:

第一行是标签,文本为“请用右键菜单操作:”;
第二行是列表控件,默认对象名 listWidget 。
窗口整体使用垂直布局,尺寸 400*300 。本例子的功能都使用代码实现,下面首先编辑 widget.h 文件代码:

#ifndef WIDGET_H#define WIDGET_H#include <QWidget>
#include <QListWidget>//列表控件
#include <QMenu>//菜单
#include <QAction>//菜单项namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();//添加槽函数
public slots://弹出右键菜单的槽函数void onCustomContextMenuRequested(const QPoint & pos);//添加条目菜单项的槽函数void onAddItemTriggered();//编辑条目菜单项的槽函数void onEditItemTriggered();//删除条目菜单项的槽函数void onDelItemTriggered();//清空所有条目的菜单项槽函数void onClearAllTriggered();private:Ui::Widget *ui;//保存右键菜单的指针QMenu *m_menuContext;//创建菜单并关联信号和槽函数void CreateMenu();
};#endif // WIDGET_H

widget.h 文件先添加了列表控件、菜单、菜单项的头文件包含;
在窗口类里面手动添加了 public slots,总共五个槽函数:
第一个槽函数用于关联控件的请求弹出右键菜单信号;
后面四个槽函数对应四个菜单项的点击信号;
最后添加了菜单指针 m_menuContext,以及创建右键菜单、关联信号和槽的函数 CreateMenu()。

下面来分段查看源文件 widget.cpp 代码,首先是头文件包含、构造函数、析构函数:

#include "widget.h"#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);//创建菜单,并关联信号和槽函数CreateMenu();
}Widget::~Widget()
{delete ui;
}

widget.cpp 添加了消息框和调试打印头文件,构造函数只添加了一句 CreateMenu() 函数调用。该函数具体代码如下:

void Widget::CreateMenu()
{//创建右键菜单对象m_menuContext = new QMenu(tr("ContextMenu")); //右键菜单其实不显示ContextMenu文本//创建“添加条目”菜单项并添加到菜单QAction *actAdd = new QAction(tr("添加条目"), this);m_menuContext->addAction( actAdd );//创建“编辑条目”菜单项并添加到菜单QAction *actEdit = new QAction(tr("编辑条目"), this);m_menuContext->addAction( actEdit );//创建“删除条目”菜单项并添加到菜单QAction *actDel = new QAction(tr("删除条目"), this);m_menuContext->addAction( actDel );//创建“清空所有”菜单项并添加到菜单QAction *actClearAll = new QAction(tr("清空所有"), this);m_menuContext->addAction( actClearAll );//设置列表控件可以有自定义右键菜单ui->listWidget->setContextMenuPolicy( Qt::CustomContextMenu );//关联弹出菜单信号connect(ui->listWidget, SIGNAL(customContextMenuRequested(QPoint)),this, SLOT(onCustomContextMenuRequested(QPoint)) );//为四个菜单项关联点击信号到槽函数connect(actAdd, SIGNAL(triggered()), this, SLOT(onAddItemTriggered()));connect(actEdit, SIGNAL(triggered()), this, SLOT(onEditItemTriggered()));connect(actDel, SIGNAL(triggered()), this, SLOT(onDelItemTriggered()));connect(actClearAll, SIGNAL(triggered()), this, SLOT(onClearAllTriggered()));//创建完毕return;
}

该函数首先创建菜单对象,保存到成员变量 m_menuContext;
然后分别创建了四个菜单项“添加条目”、“编辑条目”、“删除条目”、“清空所有”,并添加给菜单 m_menuContext;
接着设置列表控件的右键菜单策略为  Qt::CustomContextMenu ,即使用自定义的右键菜单;
关联 listWidget 请求弹出右键菜单的信号到槽函数;
最后将四个菜单项的点击信号关联到对应的槽函数上。

下面来看弹出右键菜单的槽函数代码:

//弹出右键菜单的槽函数
void Widget::onCustomContextMenuRequested(const QPoint & pos)
{//控件内的相对坐标转为屏幕坐标//是列表控件发出的信号,就用列表控件的转换函数QPoint screenPos = ui->listWidget->mapToGlobal( pos );//弹出菜单QAction *actRet = m_menuContext->exec( screenPos );if(NULL != actRet)//检查非空才能使用该指针{qDebug()<<tr("返回的菜单项:") + actRet->text();}
}

由于弹菜单的请求是由列表控件发出的,所以我们首先在该函数里使用列表控件的转换函数 mapToGlobal(),
将参数里的 pos 转为屏幕绝对坐标的 screenPos ;
然后调用菜单的 exec() 函数显示右键菜单;
m_menuContext->exec( screenPos ) 函数的返回值是用户点击的菜单项指针,如果没有点击就返回 NULL,
我们对返回值进行非空判断,对于非空指针打印菜单项的文本。

上面的槽函数仅仅是显示出右键菜单,并不具有实际的操作功能,实际的功能是通过四个菜单项关联的槽函数逐个实现的。
下面来看第一个“添加条目”菜单项的槽函数:

//添加条目菜单项的槽函数
void Widget::onAddItemTriggered()
{QListWidgetItem *itemNew = new QListWidgetItem(tr("新建条目"));//设置可以编辑itemNew->setFlags( itemNew->flags() | Qt::ItemIsEditable );//添加给控件ui->listWidget->addItem( itemNew );//设置新条目为选中的条目ui->listWidget->setCurrentItem( itemNew );//显示条目的编辑框ui->listWidget->editItem( itemNew );
}

该函数新建一个条目,默认文本“新建条目”,设置条目的双击可编辑标志位;
添加条目给列表控件,并设置新条目为当前选中状态;
最后自动开启新条目的编辑框,方便用户直接编辑新条目的文本。
因为条目设置了双击可编辑标志位,用户以后也可以双击编辑,而不需要使用单独的编辑控件来设置文本。

第二个是“编辑条目”菜单项的槽函数:

//编辑条目菜单项的槽函数
void Widget::onEditItemTriggered()
{//获取选中的条目QListWidgetItem *curItem = ui->listWidget->currentItem();if(NULL == curItem){qDebug()<<tr("没有选中的条目。");return; //返回}//设置选中条目可以编辑curItem->setFlags( curItem->flags() | Qt::ItemIsEditable );//显示选中条目的编辑框ui->listWidget->editItem( curItem );
}

该函数首先获取选中的条目,如果没有选中的条目就打印信息并返回。
如果有选中的条目,设置该条目为双击可编辑状态,并显示该条目的编辑框,这样用户就可以修改条目文本了。
当用户点击其他位置,该条目的编辑框失去焦点时,列表控件自动关闭条目的编辑框。

第三个是“删除条目”菜单项的槽函数:

//删除条目菜单项的槽函数
void Widget::onDelItemTriggered()
{//获取选中的条目QListWidgetItem *curItem = ui->listWidget->currentItem();if(NULL == curItem){qDebug()<<tr("没有选中的条目。");return; //返回}//删除条目delete curItem; curItem = NULL;
}

该函数比较简单,获取当前选中的条目,如果指针为空不处理;
如果指针不空,那么删除该条目,并将指针置空。

最后是“清空所有”菜单项的槽函数:

//清空所有条目的菜单项槽函数
void Widget::onClearAllTriggered()
{//判断条目个数,如果没条目不需要操作,直接返回int nCount = ui->listWidget->count();if(nCount < 1){return;}//提示Yes、No询问消息框,获取返回值,防止用户误操作全删//如果用户选“Yes”就全删,否则不操作int buttonRet = QMessageBox::question(this, tr("清空所有"), tr("请确认是否清空所有条目?"));if( QMessageBox::Yes == buttonRet )//用户选择了“Yes”{ui->listWidget->clear();}else //否则不处理{return;}
}

该函数首先获取列表控件的条目数量,如果没有条目就不处理,直接返回。
如果有条目,那么先弹出询问对话框 QMessageBox::question() ,
询问对话框默认是 Yes 和 No 两个按钮,用户点击任意一个按钮都会返回该按钮的常量值,
Yes 按钮对应的数值就是 QMessageBox::Yes。
当用户点击了 Yes 按钮时,说明用户确认要清空所有条目,那么执行清空操作;
如果用户点击了 No或者两个都不点击,直接关闭询问对话框,那么不处理,保留所有条目。
这个函数针对用户可能的误操作做了询问处理,防止用户误操作导致所有条目都被删除。

一般删除全部内容之类的操作,程序都要弹出询问对话框,提醒用户确认一下是否真的全部删除。
如果不进行询问,用户误操作把几个小时编辑的内容全删了,那样用户就抓狂了。

这个示例的代码讲解到这,我们构建运行示例,通过右键菜单添加几个条目,然后测试“清空所有”菜单项的功能:

如果点击 Yes 按钮就真的全清空了,如果点击 No 或者不点击这两个按钮,直接点右上角 X 关闭对话框,那么条目都会保留。
读者还可以测试其他菜单项功能,这里不截图了。我们下面小节对基于条目的控件简单设置几个样式表,定制一下外观。
 

8.4.3 基于条目控件的样式表(Style Sheet)


本小节以表格控件为例,介绍基于条目的控件常用的样式表设置,涵盖表格控件各个部分的定制显示。
(1)表格整体的前景色、背景色
一般所有的控件都有前景色、背景色,设置方法都是一样的,样式表代码举例:

color: darkblue;
background-color: cyan;

color 就是前景色,上面设置为深蓝色;background-color 就是背景色,上面设置为青色。
(2)双色交替行的设置
如果希望表格奇偶行的颜色不同,两种颜色交替显示行的背景色,那么需要开启双色交替行的显示:

tableWidget->setAlternatingRowColors( true );  //这是C++代码

序号为偶数的行使用默认背景 background-color 颜色填充,序号为奇数的行使用 alternate-background-color 。
一般不用修改默认背景色,只修改交替背景色即可以,比如样式表代码:

alternate-background-color: skyblue;

这样就是白色背景行与天蓝色背景行交替显示。如果不设置样式表,默认交替的 alternate-background-color  是浅灰色的。
(3)高亮选中条目的颜色设置
高亮条目单独有前景色和背景色可以设置,样式表举例:

selection-color: red;
selection-background-color: yellow;

selection-color 就是高亮条目前景色,上面样式表就是红色文本;selection-background-color 是高亮条目的背景色,上面举例的样式表就是黄色背景填充。
(4)表格的网格线颜色设置
列表控件和树形控件都没有网格线,只有表格控件有网格线,样式表举例:

gridline-color: darkgreen;

上面就是将网格线设置成 深绿色。
(5)表头的颜色设置
树形控件和表格控件都有头部,头部的本质是 QHeaderView 类,我们设置该类的前景色、背景色就能修改表头配色,样式表举例:

QHeaderView{
    color: darkblue;
    background-color: cyan;
}

注意要用类名 QHeaderView 和大括号把前景色、背景色的样式表文本包裹起来,这样限定修改表头的配色,而不会影响表格控件整体的配色。
(6)角按钮的颜色设置
表格左上角有个特殊的角按钮,点击它会选中表格全部内容,比如下图左上角红色的部分就是表格的角按钮:

角按钮既不属于水平表头,也不属于垂直表头,而是单独的类 QTableCornerButton。这个角按钮没有函数可以获取它的指针,但是可以配置它的显示颜色。
设置角按钮的配色,需要同时设置背景色和边框的颜色,如果不设置边框颜色,在有些窗口主题里面会看不到角按钮的背景色效果。设置角按钮配色的样式表举例:

QTableCornerButton::section {
    background: red;
    border: 2px outset red;
}

上面设置角按钮的背景色为红色,边框宽度2像素,边框也是红色。
(7)配置所有条目的颜色
基于条目的控件都可以配置条目颜色,通过 **Widget::item 设置条目配色,样式表举例:

QTableWidget::item{
    color: darkblue;
    background-color: cyan;
}

这会配置所有条目的前景色为深蓝,即文字颜色,背景色用青色填充。注意,这个条目配色会覆盖掉双色交替行、 高亮选中条目的配色,一般不要单独设置 ::item 颜色,那样会看不到高亮选中的颜色了。
(8)配置滚动条的颜色
滚动条的类是 QScrollBar,我们对该类设置前景色和背景色即可,样式表举例:

QScrollBar{
    color: yellow;
    background-color: green;
}

上面配置滚动条用绿色背景填充,然后用前景色显示滚动条两端的三角形箭头颜色,如下图所示:

(9)加与不加 类名大括号 的区别
我们设置表格控件的前景色和背景色时,可以有两种写法,第一种不加类名和大括号:

color: red;
background-color: yellow; 

如果直接把上面文本设置为表格控件样式表,那么显示效果如下:

可以看到表格控件内嵌的所有子控件颜色全部改变了,无论是角按钮、水平表头、垂直表头、单元格、水平滚动条、垂直滚动条等等都变色了。不加类名和大括号时,样式表对表格控件所有组成部分都生效。
如果我们用类名 QTableWidget 和大括号包裹住样式表文本,比如:

QTableWidget{
    color: red;
    background-color: yellow;
}

将该文本设置为表格控件的样式表,显示效果如下:

这次角按钮、两个表头、两个滚动条都没有变色。颜色改变的只有单元格以及右下角空白位置、狭缝等部位。
我们这里将角按钮、两个表头、两个滚动条称为表格控件的子控件区域;
单元格、右下角空白位置、狭缝等称为表格控件的直辖区域。

将样式表文本用类名和大括号包裹之后,样式就仅对表格控件的直辖区域生效,而子控件区域不会生效。子控件区域一般有自己的样式表定制方式,就是用子控件的类名和大括号包裹,在大括号里配置子控件颜色。

在配置控件颜色时,可以使用类名大括号包裹的方式,也可以不用类名大括号包裹,根据显示效果需求和用户爱好来定。
如果需要一体化配色,那么就不需要类名大括号包裹;如果希望子控件区域和直辖区域分别配色,那么就用类名大括号包裹样式表,注意子控件区域的配色总是需要子控件类名和大括号包裹。

另外说明一下:如果表格控件配置了整体的前景色和背景色,子控件也单独配置了前景色和背景色,那么在该子控件区域内优先使用单独配置的子控件颜色,即子控件配置优先。对于同一个颜色配置项,比如第一次指定前景色为红色,第二次指定前景色为蓝色,那么后配置的蓝色生效,即对同一颜色配置项,后配置的优先。

本小节样式表的内容介绍到这,下面通过一个简单例子测试几个样式表配色。
打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 tablestyle,创建路径 D:\QtProjects\ch08,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
我们打开 widget.ui 界面文件,按照下图拖入控件:

第一行是表格控件,默认对象名 tableWidget;
第二行和第三行总共六个按钮,文本对象名分别为:
“双色交替行”pushButtonAlternatingRowColors、“选中条目定制”pushButtonSelectionCustom、“所有条目定制”pushButtonItemCustom,
“角按钮定制”pushButtonCornerButtonCustom,“表头定制”pushButtonHeaderCustom、“清空样式表”pushButtonClearStyle;
六个按钮使用网格布局,窗口整体使用垂直布局,窗口尺寸 440*330 。
布局完成后,我们为六个按钮逐一添加槽函数:

本例子的功能都用代码实现,首先是头文件 widget.h 的代码:

#ifndef WIDGET_H#define WIDGET_H#include <QWidget>namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();private slots:void on_pushButtonAlternatingRowColors_clicked();void on_pushButtonSelectionCustom_clicked();void on_pushButtonItemCustom_clicked();void on_pushButtonCornerButtonCustom_clicked();void on_pushButtonHeaderCustom_clicked();void on_pushButtonClearStyle_clicked();private:Ui::Widget *ui;
};#endif // WIDGET_H

六个按钮的槽函数是通过右键菜单添加的,文件代码没有手动添加代码,保持原样即可。

下面分段来看源文件 widget.cpp 的代码,首先是头文件包含、构造函数、析构函数:

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);//设置行列 4*4ui->tableWidget->setColumnCount(4);ui->tableWidget->setRowCount(4);//设置表格水平头,各列均匀分布ui->tableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );//新建表格条目for(int i=0; i<4; i++){for(int j=0; j<4; j++){//新建条目,并根据行号、列号设置文本QTableWidgetItem *itemNew = new QTableWidgetItem();itemNew->setText( tr("tableItem %1, %2").arg(i).arg(j) );ui->tableWidget->setItem( i, j, itemNew );}}//构建完毕
}Widget::~Widget()
{delete ui;
}

构造函数里首先设置表格为 4 列 4 行,设置水平表头,使得表格各列均匀拉伸;
然后用两重循环为表格创建条目,条目的文本根据行号、列号设置。

下面来看第一个按钮“双色交替行”对应的槽函数:

//本例子都用类名和大括号包住里面的内容
void Widget::on_pushButtonAlternatingRowColors_clicked()
{//启用双色交替行显示ui->tableWidget->setAlternatingRowColors( true );//定制样式表,交替行采用天蓝色,表格的网格线用深绿色QString strStyle = " QTableWidget{ alternate-background-color: skyblue; ""gridline-color: darkgreen; } " ;//添加给表格控件,旧的样式表保留ui->tableWidget->setStyleSheet( ui->tableWidget->styleSheet() + strStyle );
}

该函数首先设置表格控件为双色交替行显示;
然后设置样式表的文本,设置交替行的颜色为天蓝色,设置网格线的颜色为深绿;
最后把文本设置为表格的样式表,并且用字符串拼接保留了控件原有旧的样式表。
本小节的例子都用类名和大括号包住样式表,因为后面需要对子控件进行定制。
如果样式表需要设置多次或子控件需要定制,那么一般建议用类名和大括号包裹样式表。

下面来看第二个按钮“选中条目定制”对应的槽函数:

void Widget::on_pushButtonSelectionCustom_clicked()
{//selection-color 是选中条目的前景色//selection-background-color 是选中条目的背景色QString strStyle = " QTableWidget{ selection-color: red; ""selection-background-color: yellow; } ";//添加给表格控件,旧的样式表保留ui->tableWidget->setStyleSheet( ui->tableWidget->styleSheet() + strStyle );//设置当前条目为高亮色QTableWidgetItem *curItem = ui->tableWidget->currentItem();if(NULL != curItem){curItem->setSelected(true); //标上选中的高亮色}
}

该函数先设置样式表文本,选中高亮条目的前景色为红色(文字颜色),背景用黄色填充;
然后将文本设置为表格控件的样式表,并保留了旧的样式表;
最后获取当前条目(带虚线框的条目),如果非空,就将该条目设置为选中状态(虚框+高亮颜色),方便直接查看设置的选中高亮条目效果。

下面来看第三个按钮“所有条目定制”对应的槽函数:

void Widget::on_pushButtonItemCustom_clicked()
{// QTableWidget::item 就是所有条目的样式表配置// color 是前景色,background-color 是背景色QString strStyle = " QTableWidget::item{ ""color: blue; ""background-color: lightgreen; ""} " ;//设置给表格控件,QTableWidget::item 样式表与前面两个函数的样式表冲突ui->tableWidget->setStyleSheet( ui->tableWidget->styleSheet() + strStyle );
}

该函数比较简单,设置样式表文本,然后将文本设置为表格控件的样式表,注意 QTableWidget::item 样式表会与前面两个函数的样式表效果有冲突,实际运行时 QTableWidget::item 样式表会覆盖前面的双色交替行、选中高亮颜色的配置。

下面来看第四个按钮“角按钮定制”对应的槽函数:

void Widget::on_pushButtonCornerButtonCustom_clicked()
{// QTableCornerButton::section 就是设置表格左上角的按钮风格QString strStyle =  " QTableCornerButton::section{ "" background: green;  "" border: 2px outset green; ""} " ;//添加给表格控件,旧的样式表保留ui->tableWidget->setStyleSheet( ui->tableWidget->styleSheet() + strStyle );
}

该函数设置样式表文本,注意角按钮的样式表需要同时设置背景色和边框颜色,保证角按钮能显示出配置的颜色;
然后把文本设置为表格控件的样式表,并保留之前旧的样式表效果。

下面来看第五个按钮“表头定制”对应的槽函数:

void Widget::on_pushButtonHeaderCustom_clicked()
{//QHeaderView 就是表头的类,定制该类的样式表//前景色背景色的一般都是 color  和 background-colorQString strStyle = " QHeaderView{ ""color: darkblue; ""background-color: cyan; ""} " ;//添加给表格控件,旧的样式表保留ui->tableWidget->setStyleSheet( ui->tableWidget->styleSheet() + strStyle );
}

该函数也是设置样式表文本,然后把文本设置为表格控件的样式表,并保留之前旧的样式表效果。
表格的水平表头和垂直表头都是 QHeaderView 类,该样式表对两个表头都管用。

下面来看第六个按钮“清空样式表”对应的槽函数:

void Widget::on_pushButtonClearStyle_clicked()
{//打印旧的样式表信息qDebug()<<"old style sheets: \r\n"<<ui->tableWidget->styleSheet()<<endl;//置空样式表ui->tableWidget->setStyleSheet("");//取消双色交替行的显示ui->tableWidget->setAlternatingRowColors( false );
}

这个清空函数打印了原本的样式表,然后将表格控件的样式表设置为空文本,顺便将双色交替行的显示效果关了。

示例的代码讲解到这,我们构建运行该例子,点击程序左边四个按钮,看到效果:

双色交替行、选中高亮颜色、角按钮、表头的颜色均变成我们代码里设置的颜色了。
这时候如果我们点击“所有条目定制”按钮,看到下图效果:

双色交替行和选中高亮色的效果都没了,全被 QTableWidget::item 样式表覆盖了。一般不要轻易配置 QTableWidget::item 样式表颜色,会与双色交替行和高亮选中色冲突。

本文链接:https://my.lmcjl.com/post/1492.html

展开阅读全文

4 评论

留下您的评论.