一、猜想与试验
说明一下,我已经很久没有写qt了,这两天准备写着玩玩,在我写的小demo中呢,有个场景,我需要让表格进行自适应填充满窗口,同时列也可以进行拖拽宽度,那么这个场景呢,我就试着找了下网上的资料(后面我会解释如何在初始化时正确的去处理一些我们想处理并能正确获取值)。
对表格自动填充(列自适应分配列宽)的设置:
// 设置列表自动填充满窗口
ui->information_tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
我在mainwindow.cpp构造中进行了处理,效果出来了,确实填充满了
于是我想可拖拽,就在构造中进行了如下设置:
// 设置列表自动填充满窗口
ui->information_tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
// 设置每列都能够拖拽
autoAdjustmentTableWidth();
// 设置每列都能够拖拽,且自适应组件宽度
void MainWindow::autoAdjustmentTableWidth()
{
qDebug()<<"width:"<<width;
int tableCount = ui->information_tableWidget->columnCount();
// 设置每列可拖拽
for(int column = 0;column<tableCount;column++) {
ui->information_tableWidget->horizontalHeader()->setSectionResizeMode(column, QHeaderView::Interactive);
}
}
我看了效果,奇迹发生了,样式全不见了,填充呢?
好家伙,按我想得应该是填充满,并且可拖拽,拖动超出是出现滚动条,这是个神马
于是我又开始找解决办法,找到了这个,说是设置列宽,好吧,我就想到我自己来动态设置列宽,于是我就把上面的autoAdjustmentTableWidth()方法改了一下,下面这样:
void MainWindow::autoAdjustmentTableWidth()
{
int width = ui->information_tableWidget->width();
qDebug()<<"width:"<<width;
int tableCount = ui->information_tableWidget->columnCount();
int columnItenWidth = width / tableCount;
int lastColumnItenWidth = width - (columnItenWidth*(tableCount-1));
// 设置每列可拖拽
for(int column = 0;column<tableCount;column++) {
ui->information_tableWidget->horizontalHeader()->setSectionResizeMode(column, QHeaderView::Interactive);
if(column == tableCount-1) {
ui->information_tableWidget->setColumnWidth(column,lastColumnItenWidth);
} else {
ui->information_tableWidget->setColumnWidth(column,columnItenWidth);
}
}
}
结果,这个是个什么鬼?
扯淡呢!
于是又找,网上说构造时获取不到真正的真正的表格宽度,好吧,可以监听窗口大小改变事件,于是我在mainwindow.h中增加了该事件的监听,重写了QWidget中的resizeEvent方法
void MainWindow::resizeEvent(QResizeEvent *event)
{
int width = ui->information_tableWidget->geometry().width();;
qDebug()<<"动态width:"<<width;
autoAdjustmentTableWidth();
}
效果如下,这是扯蛋蛋呢
当我拖动窗口大小的时候,我看打印的宽度width值也变化了,同时,界面也正常了,
那么就是说这个这个窗口大小事件的改变并不会在初始化时进行调用。
听了会歌曲,我冷静下来,想了想对象的构造过程,当我们在进行初始化对象时,首先是通过构造函数进行初始化的,现在这个主窗口对象是系统进行初始化构造的,那么我们在构造函数里面进行的操作,其实就相当于系统在new对象,也就是
MainWindow* main = new MainWindow();
那么就会执行到我们在mainwindow.cpp中构造中进行的操作,后续系统在进行对象方法的调用,比如调用resizeEvent方法,那么我们在构造中去获取组件的宽度等等我们想获得的在界面的设置的一些参数时,那么可能实际获取的是默认值,因为这些参数设置可能是在调用构造后进行的读取,然后再设置到MainWindow中,那么我们获取的肯定不是已经设置的参数值,展示现象也说明了这一点,所以其实我们要进行一些初始化的操作在构造函数中就不行,设置一些参数性质的东西可以,但是如果要读取我们界面设置的一些值可能就不行了,那么怎么才能读取到呢,就是在系统读取设置处理完将参数带进MainWindow对象中才行,这也就是,我们在resizeEvent方法中的操作,在我们拖动窗口大小时生效了,因为MainWindow对象已经创建了,参数也传递到了对象中,基于这个想法,我就开始找,有没有什么方法时MainWindow对象构造后一定会调取的方法,这样我才能获取到正确的值,功夫不负有心人,我在QWidget中找到了下面的方法:
也就是绘制事件的方法,因为我想的是其实我们的窗口也好,组件也罢,其实都是绘制出来的,时时刻刻都在绘制,所以窗口在绘制时一定会读取一些参数值才能够进行绘制,所以我在这个方法中中可能可以获取到一些正确的设置参数值,基于这个猜想,我在mainwindow.h重写了这个方法:
void MainWindow::paintEvent(QPaintEvent *event)
{
int width = ui->information_tableWidget->width();
qDebug()<<"width:"<<width;
}
打印结果:
跟我在界面设置的值没差太多,毕竟我不是获取的组件的真正大小,那么就说明了绘制事件是在读取参数绘制之后,我们能够拿到整正的数据,那么基于此,我们的想法能够去实现了。
二、具体实现
基于以上猜想,我们就可以在初始化时这样操作,因为绘制事件是在时时时时刻刻发生的,所以我们在绘制事件中进行操作,我们在方法中操作的时候,因为是进行初始化操作,所以我们必须要有个标志来记录我们是在初始化时进行操作的,在调用时只在初始化时调用一次,避免每一次绘制事件都调用,和我们的想法相悖,同时消耗时间和内存。那么具体操作如下:
1.、在mainwindow.h添加个变量和方法
1)bool isWindowHasStarted = false; 变量
2)void changeWindowHasStartedStatus(bool status); 方法
private:
Ui::MainWindow *ui;
// 窗体是否已经启动,默认false,未启动
bool isWindowHasStarted = false;
// 窗口大小改变事件
virtual void resizeEvent(QResizeEvent * event);
// 图形绘制事件
virtual void paintEvent(QPaintEvent *event);
/**
* @brief autoAdjustmentTableWidth 自动调整表格列宽
* @param tableWidget 列表对象
* @param defaultMinWidthParam 默认最小自动分配的表格宽度,表示的是当显示表格的宽度比这个值小的时候就不会进行自动分配宽度了,而是会以横向滚动条显示
* @param defaultMinItemWidthParam 自动分配时默认最小时单列的宽度,当自动分配是计算出的平均列宽小于该值,会采用该值进行设置
* @param columnWidthMap 用户自定义设置列宽,key为表格列索引,从0开始,value表示设置改列的宽度,当设置的有效列数量和表格总列数相同时,自动分配就会失效
*/
void autoAdjustmentTableWidth(QTableWidget *tableWidget,int defaultMinWidthParam,int defaultMinItemWidthParam,QMap<int,int> columnWidthMap);
// 自动调整表格宽度
void autoAdjustmentTableWidth();
// 改变isWindowHasStarted(窗体是否启动)的状态值
void changeWindowHasStartedStatus(bool status);
void MainWindow::changeWindowHasStartedStatus(bool status)
{
this->isWindowHasStarted = status;
}
这两个主要作用是记录窗体是否已经初始化启动完成
2、绘制事件paintEvent中进行初始化操作
在paintEvent(绘制事件中)我们实现调用自动调整方法,这样初始化后窗口一定是填充满窗口的,如下:
void MainWindow::paintEvent(QPaintEvent *event)
{
if(isWindowHasStarted) {
return;
}
// 当窗口初始化绘制时改变表格大小
autoAdjustmentTableWidth();
// 改变isWidowSatrtAdjustmentTable为true,则后续的绘制事件就会在上一步被拦截,不进行表格处理,我们要的是在初始化时对表格进行一个自适应适配
//,但是如果在构造函数里面处理则不行,因为构造函数中获取的全是初始值,不是调整过后的组件大小和参数
// 构造函数里面只是在准备对象,也就是准备属性设置的参数等操作,绘制时才去读取我们在构造时设置的一些参数,在进行界面的绘制
// 真正的改变其实是在绘制之后,所以我们要在绘制里面来处里对表格初始设置才能有效果
changeWindowHasStartedStatus(true);
}
注意:
1)在最开始一定要判断isWindowHasStarted(窗体是否启动)这个变量,如果为true,就直接返回,这样就后续中就不会执行到后面我们初始化的操作
2)在初始化完成后的代码最后面一定要调用changeWindowHasStartedStatus(改变isWindowHasStarted状态值)这个方法,将值设置为true,表明窗体已经初始化了,后续也就是从第二次开始绘制事件进来就会被最开始的判断拦截掉了
3)总结就是最开始的代码是判断拦截,中间的是我们一系列初始化操作,最后的代码是将表明窗体启动的变量设置为true
3、后续的一些操作
因为我这里是让表格进行自适应操作,所以我需要在窗体大小改变事件中进行操作,让表格自动变化,如果你们是其他操作,看情况即可,我这里调用resizeEvent事件方法进行自适应操作
void MainWindow::resizeEvent(QResizeEvent *event)
{
autoAdjustmentTableWidth();
}
最终的表格自适应宽度方法:
/**
* @brief autoAdjustmentTableWidth 自动调整表格列宽
* @param tableWidget 列表对象
* @param defaultMinWidthParam 默认最小自动分配的表格宽度,表示的是当显示表格的宽度比这个值小的时候就不会进行自动分配宽度了,而是会以横向滚动条显示
* @param defaultMinItemWidthParam 自动分配时默认最小时单列的宽度,当自动分配是计算出的平均列宽小于该值,会采用该值进行设置
* @param columnWidthMap 用户自定义设置列宽,key为表格列索引,从0开始,value表示设置改列的宽度,当设置的有效列数量和表格总列数相同时,自动分配就会失效
*/
void MainWindow::autoAdjustmentTableWidth(QTableWidget *tableWidget,int defaultMinWidthParam,int defaultMinItemWidthParam,QMap<int,int> columnWidthMap)
{
// 默认最小的列表宽度,表示的是当显示列表的宽度比这个值小的时候就不会进行自动分配宽度了
int defaultMinWidth = defaultMinWidthParam;
// 自动分配时默认最小时单列的宽度,当columnWidthMap设置的总宽度和自动分配计算出的列宽度之和大于了显示列表宽度时,自动分配列会采用的值
int defaultMinItemWidth = defaultMinItemWidthParam;
// 获取显示的列头宽度
int width = tableWidget->horizontalHeader()->width();
qDebug()<<"width:"<<width;
// 列头总数
int tableAllCount = tableWidget->horizontalHeader()->count();
// 显示的列头总数
int showTableCount = 0;
// 显示列头所在列索引的集合
QList<int> showColumns;
// 计算实际的所有显示列的宽度之和
int actualWidth = 0;
for(int column = 0;column<tableAllCount;column++) {
// 这里处理掉隐藏的列,隐藏列不参与分配,如果隐藏列参与分配,就会出现显示的出现空白,因为空白部分被隐藏列占据了
if(!tableWidget->isColumnHidden(column)) {
actualWidth = actualWidth + tableWidget->columnWidth(column);
showTableCount++;
showColumns.append(column);
}
}
// qDebug()<<"showTableCount:"<<showTableCount;
// width < defaultMinWidth的作用就是当显示列表的宽度比默认最小的列表宽度小的时候就不会进行自动分配宽度了,也就是在这里会被拦截
// 当显示列表的宽度比默认最小的列表宽度要大时,就会进行自动调整,同时只要计算出的实际宽度比显示的列头宽度要大,就不用重新调整大小重新进行分配
if(actualWidth > width && width < defaultMinWidth) {
return;
}
// 计算出设置列的总宽度
int setItemAllWidth = 0;
// 有效的设置集合
QMap<int,int> validColumnWidthMap;
QList<int> keys = columnWidthMap.keys();
for(int key: keys) {
if(key >=0 && key < tableAllCount && showColumns.contains(key)) {
int value = columnWidthMap.value(key, 0);
setItemAllWidth = setItemAllWidth + value;
validColumnWidthMap.insert(key, value);
// qDebug()<<"key:"<<key<<"value:"<<value;
}
}
int autoColumnCount = showTableCount-validColumnWidthMap.size();
int autoAllColumnWidth = setItemAllWidth > width ? 0: width - setItemAllWidth;
// 计算出每列的平均宽度(这里单独区分出一个第一个自动分配宽度的值是因为除法有误差,所以单独一个就能解决误差问题带来的填充超出或小一点的问题)
int columnItemWidth = defaultMinItemWidth;
int fisrtColumnItemWidth = defaultMinItemWidth;
// 如果需要自动分配的列数量不为0,则计算自动分配列的宽度
if(autoColumnCount != 0) {
columnItemWidth = autoAllColumnWidth / autoColumnCount;
fisrtColumnItemWidth = autoAllColumnWidth - (columnItemWidth*(autoColumnCount-1));
}
// 计算出的平均值小于设定的默认自动分配最小值,则直接按最小值处理
if(columnItemWidth< defaultMinItemWidth || fisrtColumnItemWidth < defaultMinItemWidth) {
columnItemWidth = defaultMinItemWidth;
fisrtColumnItemWidth = defaultMinItemWidth;
}
// 设置每列可拖拽,并且平均分配宽度
bool isFisrt = true;
for(int count = 0;count<showColumns.size();count++) {
int column = showColumns.at(count);
tableWidget->horizontalHeader()->setSectionResizeMode(column, QHeaderView::Interactive);
// 如果设置的map包含该列,则设置为用户设置的值
if(validColumnWidthMap.contains(column)) {
tableWidget->setColumnWidth(column,validColumnWidthMap.value(column));
continue;
}
// 如果不是用户设置列,则按照平均分配设置列宽
if(isFisrt) {
tableWidget->setColumnWidth(column,fisrtColumnItemWidth);
isFisrt = false;
} else {
tableWidget->setColumnWidth(column,columnItemWidth);
}
}
}
void MainWindow::autoAdjustmentTableWidth()
{
QMap<int,int> columnWidthMap;
columnWidthMap.insert(0,100);
autoAdjustmentTableWidth(ui->information_tableWidget,900,150,columnWidthMap);
}
最终,效果达到了,自适应宽度且可进行拖拽操作,摒弃了下面这个鸡肋设置
// 设置列表自动填充满窗口
ui->information_tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);