一 自适应工具类介绍:
1.1 功能
控件能跟随界面大小的变化实现字体、大小同比例的变化
1.2 优点
控件大小,字体可跟随界面大小同比例任意变化。
同一套程序能兼容不同分辨率及不同DPI的显示器
对于控件数目固定不变的UI区域:只需要将控件拖拽到指定位置即可,不需要使用弹簧及布局等qt属性
对于控件数目有可能会根据需求变化的UI区域:可以使用qt原有的布局,但解放了qt原有布局中不能改变字体的属性。
二 自适应工具类使用:
2.1 基本使用方法
使用说明: <1> 使用setAutoResizeFlag()方法设置自动搜索的控件类
<2> 使用addAutoResizeItem()方法增加需要自适应的类型,addNoAutoResizeItem()方法去除不需要自适应的类型
<3> 使用pushAllResizeItem()方法,完成自适应类的添加
<4> 在界面改变大小后调用doAutoResize()方法完成应用程序的自适应
2.2局限性
目前仅支持使用点大小来设置控件的字体大小,否则不能实现完全自适应,并且目前为了自适应各种分辨率,字体的自适应会在计算缩放比例之后得出的结果之后向下取整再减一。
2.3注意事项
在使用本工具类实现窗口自适应时,并且同时使用了qt自带的布局时必须符号以下要求,否则界面在变化时控件位置将不会改变,大小也不会改变。
<1> 布局必须依托于WIdget来布局,即需要在Widget上布局,而不是直接选中多个控件,点击某种布局
<2> 布局Widget类名必须包含layoutWgt
2.4 调用示例
// 将主界面大小传入(这是UI设计中的尺寸)
AutoResizer *m_autoResizeHandler = new AutoResizer(this, this->rect().width(), this->rect().height());
// 设置要自适应的控件类型
m_autoResizeHandler->setAutoResizeFlag(AutoResizer::AUTO_INC_LABEL | AutoResizer::AUTO_INC_BUTTON |
AutoResizer::AUTO_INC_EDITOR | AutoResizer::AUTO_INC_COMBOBOX);
// 添加自定义或工具类中没有的控件类型
m_autoResizeHandler->addAutoResizeItem(/*这里面必须是QWidget的子类*/)
// 初始化自适应控件指针和初始字体的映射
m_autoResizeHandler->doneAllResizeItem();
// 最后一步重写resizeEvent方法
void GUI_MainForm::resizeEvent(QResizeEvent *ev)
{
m_autoResizeHandler->doAutoResize();
QWidget::resizeEvent(ev);
}
三 源码
3.1 头文件
namespace AutoResize{
/*自适应控件原始的属性*/
struct AutoResizeOriginalData
{
QRect dataRect; /*控件原始矩形区域*/
QFont dataFont; /*控件原始字体属性*/
};
}
class AutoResizerPrivate;
class AUTORESIZE_EXPORT AutoResizer
{
Q_DECLARE_PRIVATE(AutoResizer)
public:
/*自适应枚举标志*/
enum AUTO_RESIZE_ENUM
{
AUTO_INC_LABEL = 0x1, /*Label自适应标志*/
AUTO_INC_BUTTON = 0x2, /*Button自适应标志*/
AUTO_INC_EDITOR = 0x4, /*LineEdit自适应标志*/
AUTO_INC_COMBOBOX = 0x8 /*ComboBox自适应标志*/
};
public:
AutoResizer(QWidget *pObj, float fWidth, float fHeight);
~AutoResizer(void);
/* 添加自适应枚举标志未包含控件 */
void addAutoResizeItem(QWidget *pWidget);
/* 添加不需要自适应工具类处理的控件 */
void addNoAutoResizeItem(QWidget *pWidget);
/* 返回水平自适应比例 */
float getHorResizeRatio(void) const;
/* 返回垂直自适应比例 */
float getVerResizeRatio(void) const;
/* 返回字体自适应比例 */
float getFontResizeRatio(void) const;
/* 设置自适应工具类标志 */
void setAutoResizeFlag(int iflag);
/* 将所有需要自适应控件的属性添加到给定的Map中 */
void doneAllResizeItem(void);
/* 控件自适应操作 */
void doAutoResize(void);
protected:
/* 自适应界面工具成员变量类指针 */
QScopedPointer<AutoResizerPrivate> d_ptr;
private:
/*删除拷贝函数*/
Q_DISABLE_COPY(AutoResizer)
/* 计算自适应的比例 */
void pri_calculateResizeRatio(void);
/* 字体自适应 */
void pri_fontAutoResize(QWidget *pObj, int iFontSize);
/* 搜索并处理布局的控件 */
void pri_dealLayoutItems(void);
/* 忽略布局中的所有子类控件 */
void pri_ignoreAllChiledren(QObject *pObj);
};
3.2 私有成员类声明
class AutoResizerPrivate
{
Q_DECLARE_PUBLIC(AutoResizer)
public:
AutoResizerPrivate(AutoResizer *pPtr)
: q_ptr(pPtr)
, m_bAutoResize(false)
{}
private:
/* 自适应工具类指针 */
AutoResizer *q_ptr;
/* 自适应基础控件对象 */
QWidget *m_pAutoBaseObj;
/* 需要自适应字体和大小的控件映射 */
QMap<QWidget *, AutoResize::AutoResizeOriginalData> m_mapResize;
/* 需要自适应字体的控件映射(不会对此映射中的控件大小进行自适应) */
QMap<QWidget *, AutoResize::AutoResizeOriginalData> m_mapFont;
/* 需要自适应大小及字体的控件列表 */
QList<QWidget *> m_listAutoResize_Items;
/* 不需要自适应大小的控件列表 */
QList<QWidget *> m_listNoAutoResize_Items;
int m_iAutoResizeFlag; /* 自适应类控件类型标志 */
float m_fHorRatio; /* 水平缩放比例 */
float m_fVerRatio; /* 垂直缩放比例 */
float m_fFontRatio; /* 字号缩放比例 */
float m_fBaseWidth; /* 自适应父类控件宽度 */
float m_fBaseHeight; /* 自适应父类控件高度 */
float m_fDpiRatio; /* 屏幕逻辑DPI点数 */
bool m_bAutoResize; /* 是否开启自适应标记 */
};
3.3 类实现
/**
* @brief AutoResizer::AutoResizer
* @param pObj 自适应工具类的基类控件指针
* @param fWidth 自适应工具类的基类对象宽度
* @param fHeight 自适应工具类的基类对象高度
*/
AutoResizer::AutoResizer(QWidget *pObj, float fWidth, float fHeight)
: d_ptr(new AutoResizerPrivate(this))
{
Q_D(AutoResizer);
/*初始化自适应工具类基础属性*/
d->m_fBaseWidth = fWidth;
d->m_fBaseHeight = fHeight;
d->m_pAutoBaseObj = pObj;
d->m_fHorRatio = 1.0;
d->m_fVerRatio = 1.0;
d->m_fFontRatio = 1.0;
/* 计算逻辑每英寸点数 */
int iDPI = static_cast<int>(QApplication::primaryScreen()->logicalDotsPerInch());
/* 按照逻辑DPI来设置子不同屏幕下的自适应 */
d->m_fDpiRatio = static_cast<float>(96.0 / iDPI);
/* 处理有布局的控件 */
pri_dealLayoutItems();
}
AutoResizer::~AutoResizer(void)
{
/* 此析构函数不需要清理 */
}
/**
* @brief AutoResizer::doneAllResizeItem 添加所有需要缩放的Item
* @note 在改变控件自适应操作之前必须完成调用
*/
void AutoResizer::doneAllResizeItem(void)
{
Q_D(AutoResizer);
QWidget *qWidget = nullptr;
AutoResize::AutoResizeOriginalData resizeData;
QRect qRect;
/* 将需要自适应的控件加入自定义映射 */
for (auto it = d->m_listAutoResize_Items.begin(); it != d->m_listAutoResize_Items.end(); ++it)
{
qWidget = *it;
/* 获得左上角点位置 */
qRect = qWidget->geometry();
qRect.setX(qWidget->x());
qRect.setY(qWidget->y());
/* 获取原始尺寸 */
qRect.setWidth(abs(qRect.width()));
qRect.setHeight(abs(qRect.height()));
/* 加入自适应映射 */
resizeData.dataRect = qRect;
resizeData.dataFont = qWidget->font();
d->m_mapResize[qWidget] = resizeData;
}
if (d->m_iAutoResizeFlag & AUTO_INC_LABEL)
{
QList<QLabel *> qLabelList = d->m_pAutoBaseObj->findChildren<QLabel *>();
for (auto it = qLabelList.begin(); it != qLabelList.end(); it++)
{
qWidget = *it;
/* 获得左上角点位置 */
qRect = qWidget->geometry();
qRect.setX(qWidget->x());
qRect.setY(qWidget->y());
/* 获取原始尺寸 */
qRect.setWidth(abs(qRect.width()));
qRect.setHeight(abs(qRect.height()));
/* 加入自适应映射 */
resizeData.dataRect = qRect;
resizeData.dataFont = qWidget->font();
d->m_mapResize[qWidget] = resizeData;
}
}
if (d->m_iAutoResizeFlag & AUTO_INC_COMBOBOX)
{
QList<QComboBox *> comboxList = d->m_pAutoBaseObj->findChildren<QComboBox *>();
for (auto it = comboxList.begin(); it != comboxList.end(); it++)
{
qWidget = *it;
/* 获得左上角点位置 */
qRect = qWidget->geometry();
qRect.setX(qWidget->x());
qRect.setY(qWidget->y());
/* 获取原始尺寸 */
qRect.setWidth(abs(qRect.width()));
qRect.setHeight(abs(qRect.height()));
/* 加入自适应映射 */
resizeData.dataRect = qRect;
resizeData.dataFont = qWidget->font();
d->m_mapResize[qWidget] = resizeData;
}
}
if (d->m_iAutoResizeFlag & AUTO_INC_BUTTON)
{
QList<QAbstractButton *> _buttonList = d->m_pAutoBaseObj->findChildren<QAbstractButton *>();
for (auto it = _buttonList.begin(); it != _buttonList.end(); it++)
{
qWidget = *it;
/* 获得左上角点位置 */
qRect = qWidget->geometry();
qRect.setX(qWidget->x());
qRect.setY(qWidget->y());
/* 获取原始尺寸 */
qRect.setWidth(abs(qRect.width()));
qRect.setHeight(abs(qRect.height()));
/* 加入自适应映射 */
resizeData.dataRect = qRect;
resizeData.dataFont = qWidget->font();
d->m_mapResize[qWidget] = resizeData;
}
}
if (d->m_iAutoResizeFlag & AUTO_INC_EDITOR)
{
QList<QLineEdit *> _editorList = d->m_pAutoBaseObj->findChildren<QLineEdit *>();
for (auto it = _editorList.begin(); it != _editorList.end(); it++)
{
qWidget = *it;
/* 获得左上角点位置 */
qRect = qWidget->geometry();
qRect.setX(qWidget->x());
qRect.setY(qWidget->y());
/* 获取原始尺寸 */
qRect.setWidth(abs(qRect.width()));
qRect.setHeight(abs(qRect.height()));
/* 加入自适应映射 */
resizeData.dataRect = qRect;
resizeData.dataFont = qWidget->font();
d->m_mapResize[qWidget] = resizeData;
}
}
/* 本块结束(将需要自适应的控件加入自定义映射) */
/* 将不需要自适应的控件从自定义映射移除 */
for (auto it = d->m_listNoAutoResize_Items.begin(); it != d->m_listNoAutoResize_Items.end(); it++)
{
if (d->m_mapResize.contains(*it)) d->m_mapResize.remove(*it);
}
/* 本块结束(将不需要自适应的控件从自定义映射移除) */
d->m_bAutoResize = true;
}
/**
* @brief AutoResizer::getHorResizeRatio
* @return 返回水平缩放系数
*/
float AutoResizer::getHorResizeRatio(void) const
{
Q_D(const AutoResizer);
return d->m_fHorRatio;
}
/**
* @brief AutoResizer::getVerResizeRatio
* @return 返回垂直缩放系数
*/
float AutoResizer::getVerResizeRatio(void) const
{
Q_D(const AutoResizer);
return d->m_fVerRatio;
}
/**
* @brief AutoResizer::getFontResizeRatio
* @return 返回字体缩放系数
*/
float AutoResizer::getFontResizeRatio(void) const
{
Q_D(const AutoResizer);
return d->m_fFontRatio;
}
/**
* @brief AutoResizer::pri_calculateResizeRatio
* @note 计算当前窗口缩放系数并将缩放比例最大的赋值给字体缩放系数
*/
void AutoResizer::pri_calculateResizeRatio(void)
{
Q_D(AutoResizer);
d->m_fHorRatio = d->m_pAutoBaseObj->width() / d->m_fBaseWidth;
d->m_fVerRatio = d->m_pAutoBaseObj->height() / d->m_fBaseHeight;
d->m_fFontRatio = d->m_fHorRatio < d->m_fVerRatio ? d->m_fHorRatio : d->m_fVerRatio;
}
/**
* @brief AutoResizer::pri_fontAutoResize 字体自适应接口
* @param pObj 传入指针(需要自适应的控件)
* @param iFontSize 传入字体大小(原始)
*/
void AutoResizer::pri_fontAutoResize(QWidget *pObj, int iFontSize)
{
Q_D(AutoResizer);
if (iFontSize <= 0)
{
return;
}
/* 定义局部数据类型 */
bool bHasTextStyle = false; //控件是否有样式标记
float fFontSize = iFontSize * d->m_fFontRatio; //计算得到自适应后的字体大小
QString fontTextReg = "font:\\s+[0-9]+pt"; //匹配样式的正则字符串
QString fontFormat = "font: %1pt"; //替换字体字符串
QString fontSizeReg = "[0-9]+";
QString styleText = pObj->styleSheet(); //如果是通过样式表修改的,则需要替换样式 否则将不会生效
QString fontText = "";
QString fontSizeText = "";
QFont changedFont;
QRegExp reg = QRegExp(fontTextReg);
QRegExp size = QRegExp(fontSizeReg);
/* 本块结束(定义局部数据类型) */
/* 修改tablewidget类型控件的样式表 */
QTableWidget *tableWidget = dynamic_cast<QTableWidget *>(pObj);
if (tableWidget)
{
QStringList partNames = QStringList() << "QTableWidget"
<< "QHeaderView"
<< "QHeaderView::section"
<< "QTableWidget::item"
<< "QTableWidget::item::selected";
foreach (QString partName, partNames)
{
fontTextReg = QString("%1\\s*\\{[^\\}]*font:\\s*([0-9]+)pt;[^\\}]*\\}").arg(partName);
int posIndex = 0;
reg.setPattern(fontTextReg);
while ((posIndex = reg.indexIn(pObj->styleSheet(), posIndex)) != -1)
{
int fontSizePos = reg.pos(1); // 获取捕获组1中的内容的位置
int fontSizeLength = reg.cap(1).length(); // 获取捕获组1中的内容的长度
// 获取捕获的字体大小
//int iFontSize = reg.cap(1).toInt();
if (floor(static_cast<double>(fFontSize)) < 1)
{
fFontSize = 1;
}
// 构建新的文本,将字体大小替换为新值
styleText.replace(fontSizePos, fontSizeLength,
QString("%1").arg(floor(static_cast<double>(fFontSize))));
posIndex++;
}
tableWidget->setStyleSheet(styleText);
}
return;
}
/* 本块结束(修改tablewidget类型控件的样式表) */
if (reg.indexIn(pObj->styleSheet()) != -1)
{
fontText = reg.capturedTexts().at(0);
if (size.indexIn(fontText) != -1)
{
bHasTextStyle = true;
}
}
/* 如果设置了样式表则调整样式表,否则就设置点大小*/
if (bHasTextStyle)
{
/*由于qss中 不支持浮点精度控制,由于计算的缩放比例是浮点数
* 但是当文字刚好与控件相贴合时,此时进行缩放的控件和字体直接有可能会因为控件按照比例缩放了
* 但是字号只是由12.79 ---> 变到了 12.19
* 就会导致某些尺寸下文字显示不完整。
* 所以在窗口进行如果对计算的浮点字号向上取整减1,还是会遇到上面的问题
* 如果只是自适应几种主流的分辨率,个人认为向上取整减1
* 如果想使用任何比例下的自适应,向下取整减1
*/
if (floor(static_cast<double>(fFontSize)) - 1 < 1)
{
fFontSize = 2;
}
styleText.replace(reg, fontFormat.arg(floor(static_cast<double>(fFontSize)) - 1));
pObj->setStyleSheet(styleText);
}
else
{
changedFont = pObj->font();
if (floor(static_cast<double>(fFontSize)) - 1 < 1) //最小化控制 不能设置小于1的字体
{
fFontSize = 2;
}
changedFont.setPointSizeF(static_cast<qreal>(fFontSize) - 1);
pObj->setFont(changedFont);
}
/* 本块结束(如果设置了样式表则调整样式表,否则就设置点大小*/
}
/**
* @brief AutoResizer::pri_dealLayoutItems 处理布局中的控件
* @note 自适应类不会去调整布局中控件类的大小
*/
void AutoResizer::pri_dealLayoutItems(void)
{
Q_D(AutoResizer);
QString desName = "layoutWidget";
/*会返回直接子类 以及简介子类
* 并且是在QWiget上进行的布局,而不是直接布局,同时QWidget必须符合命名规范layoutWidget
*/
QList<QLayout *> layoutList = d->m_pAutoBaseObj->findChildren<QLayout *>();
for (auto it = layoutList.begin(); it != layoutList.end(); it++)
{
QString objName = (*it)->parent()->objectName();
if (objName.contains(desName))
{
QWidget *layoutWidget = qobject_cast<QWidget *>((*it)->parent());
d->m_listAutoResize_Items.push_back(layoutWidget);
pri_ignoreAllChiledren(layoutWidget);
}
}
}
/**
* @brief AutoResizer::pri_ignoreAllChiledren
* @param obj 布局中的控件
* @details 忽略布局中的控件,并将布局中的控件以及控件的font属性加入map
* @note 并不会加入布局本身(该布局必须依赖于Widget来布局(做为一个QWidget的间接子类),不能直接使用布局)
*/
void AutoResizer::pri_ignoreAllChiledren(QObject *pObj)
{
Q_D(AutoResizer);
QList<QObject *> children = pObj->children();
for (auto it = children.begin(); it != children.end(); it++)
{
QWidget *item = qobject_cast<QWidget *>(*it);
if (!item)
{
continue;
}
AutoResize::AutoResizeOriginalData resizeData;
d->m_listNoAutoResize_Items.push_back(item);
resizeData.dataFont = item->font();
d->m_mapFont[item] = resizeData;
}
}
/**
* @brief AutoResizer::addOtherItem
* @param widget 没有被枚举集搜索到的控件类指针
*/
void AutoResizer::addAutoResizeItem(QWidget *widget)
{
Q_D(AutoResizer);
d->m_listAutoResize_Items.push_back(widget);
}
/**
* @brief AutoResizer::addNoAutoResizeItem 添加不需要自适应的控件
* @param widget 传入参数(不需要自适应的控件指针)
*/
void AutoResizer::addNoAutoResizeItem(QWidget *widget)
{
Q_D(AutoResizer);
d->m_listNoAutoResize_Items.push_back(widget);
}
/**
* @brief AutoResizer::setAutoResizeFlag
* @param flag 设置自适应类型标记
*/
void AutoResizer::setAutoResizeFlag(int iFlag)
{
Q_D(AutoResizer);
d->m_iAutoResizeFlag = iFlag;
}
/**
* @brief AutoResizer::doAutoResize 执行自适应操作
*/
void AutoResizer::doAutoResize(void)
{
Q_D(AutoResizer);
pri_calculateResizeRatio();
if (d->m_bAutoResize)
{
QMapIterator<QWidget *, AutoResize::AutoResizeOriginalData> _itarator(d->m_mapResize);
QFont changedFont;
while (_itarator.hasNext())
{
_itarator.next();
/* 计算自适应后的左上角点和宽高以及字体 */
QWidget *_item = _itarator.key();
QRect tmp = _itarator.value().dataRect;
tmp.setWidth(static_cast<int>(tmp.width() * d->m_fHorRatio));
tmp.setHeight(static_cast<int>(tmp.height() * d->m_fVerRatio));
QRect after = QRect(static_cast<int>(tmp.x() * d->m_fHorRatio), static_cast<int>(tmp.y() * d->m_fVerRatio),
static_cast<int>(tmp.width()), static_cast<int>(tmp.height()));
changedFont = _itarator.value().dataFont; //根据界面缩放比例计算 字体缩放
pri_fontAutoResize(_item,
static_cast<int>(changedFont.pointSize() * d->m_fDpiRatio)); //先改变字体 再去设置缩放
_item->setGeometry(after);
/* 本块结束(计算自适应后的左上角点和宽高以及字体) */
/* 设置tabewidget的行高 */
QTableWidget *tableWidget = dynamic_cast<QTableWidget *>(_item);
if (tableWidget)
{
for (int i = 0; i < tableWidget->rowCount(); ++i)
{
tableWidget->setRowHeight(i, tableWidget->viewport()->height() / tableWidget->rowCount());
}
}
/* 本块结束(设置tabewidget的行高) */
}
/* 处理布局中控件的字体 */
QMapIterator<QWidget *, AutoResize::AutoResizeOriginalData> mapFont(d->m_mapFont);
while (mapFont.hasNext())
{
mapFont.next();
QWidget *_item = mapFont.key();
changedFont = mapFont.value().dataFont;
pri_fontAutoResize(_item, static_cast<int>(changedFont.pointSize() * d->m_fDpiRatio));
}
/* 本块结束(处理布局中控件的字体) */
}
}
四.说明
该工具类尚未完善,并可能存在部分问题,有优化或Bug烦请评论指出。在此多谢!!!