一、简述
上一则该文Qt之高DPI表明屏 – 软件系统重新整理讲诉了本栏处置高DPI表明的一连串预测操作过程,为的是更快的写作和排印,当中有许多试验计划没具体文本写下,即便写下来也没多灭音,所以会负面影响他们写作。
第一集该文Sonbhadra接著上一则该文的最终一拍子-自网络连接高DPI展开传授,虽然文本较为多,所以整座软件系统标识符量也会相当大,因而该文中也只会牵涉到整座DPI网络连接构架的核心理念和许多关键性标识符,有疑点热烈欢迎发问
上一则该文提及了T询问处,所以甚么是T询问处呢!上面他们来对症下药。
这儿本栏贴两个网络连接顺利完成之后的TWidget类,他们能先预测预测,也能TNUMBERV12V4,每处标识符的具体文本涵义。大部份标识符技术细节本栏后面会对症下药每处技术细节
//表达式新闻稿 //xxx.h #define CreateTWidget() CreateObject(Widget) class TWidget : public QWidget, public ICallDPIChanged { Q_OBJECT public: TWidget(float scale, QWidget * parent = nullptr); TWidget(QWidget * parent = nullptr);//不提议采用 TWidget(QWidget * parent, Qt::WindowFlags f);//不提议采用 ~TWidget(); public: //改写大小不一变动有关表达式 DECLARE_RESIZE(); void setLayout(QLayout * layout); public: //QWidget virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result)override; //ICallDPIChanged DECLARE_DPI(); //TWidget virtual void AdjustReiszeHandle(); DECLARE_DPI_SYMBOL; protected: WidgetResizeHandler resize_handler;// 用作全力支持弱化增大 拖曳 等机能 private: TigerUILib::ReiszeActions m_sizeActions; QSize m_size; QSize m_minimumSize; QSize m_maximumSize; ICallDPIChanged * m_pLayout = nullptr;//DPI发生改变时 通告产业布局 }; //表达式同时实现 //xxx.cpp TWidget::TWidget(float scale, QWidget * parent) : QWidget(parent) , dpi_scale(scale) { } TWidget::TWidget(QWidget * parent /*= nullptr*/) : QWidget(parent) { dpi_scale = WINDOW_SCALE; } TWidget::TWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { dpi_scale = WINDOW_SCALE; } TWidget::~TWidget() { DPIHelper()->RemoveDPIRecord(WINDOW_WINID); } void TWidget::setLayout(QLayout * layout) { WIDGET_RELEATE_LAYOUTS(layout); __super::setLayout(layout); } DEFINE_RESIZE(Widget); DEFINE_DPI(Widget); bool TWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) { MSG* pMsg = reinterpret_cast(message); switch (pMsg->message) { case WM_DPICHANGED: { DWORD dpi = LOWORD(pMsg->wParam); WId id = WINDOW_WINID; if (DPIHelper()->DPIChanged(dpi, id)) { ScaleChanged(DPIHelper()->GetDPIScale(id)); RefrushSheet(this, id); } } } return __super::nativeEvent(eventType, message, result); } void TWidget::ScaleChanged(float scale) { DEFINTE_SCALE_RESIZE(Widget); if (m_pLayout) { m_pLayout->ScaleChanged(scale); } AdjustReiszeHandle(); } void TWidget::AdjustReiszeHandle() { if (resize_handler.isWidgetMoving()) { resize_handler.dpiChanged(WINDOW_SCALE); } } 二、框架说明
用一段话描述一下DPI网络连接计划?
答:本栏提及的DPI网络连接计划其实原理很简单,没想象中所以复杂,计划也是中规中矩,当中遵守以下这么几条大的原则
首先就是window窗体自己去监测自身所在屏幕DPI发生变化,发生改变时通告产业布局展开缩放局部缩放后,然后对自身所包含的widget窗体和产业布局展开缩放不在产业布局中的窗体需要单独去控制缩放是不是说起来很简单,但是要同时实现这么两个流程还是有许多难度的,首先考虑的就是效率,如果做完效率跟不上所以一切都是瞎扯。
为的是更快的效率,本栏也是做了不需要的优化,优化的文本不在第一集该文中展开讨论,后续会单独分出一则该文说明点击领取Qt学习资料+视频教程~「链接」
上面是两个DPI适配框架的核心理念接口类,分别是DPI发生改变时的回调接口类和DPi管理接口类
struct ICallDPIChanged { virtual void ScaleChanged(float scale) = 0; virtual WId GetWID() const = 0; virtual void SetScale(float scale) = 0; }; #define STANDARD_DPI 96.0 struct IDPIHelper { virtual bool DPIChanged(unsigned short, WId) = 0; virtual void RemoveDPIRecord(WId) = 0;//移除指定native窗体的DPI记录 一般用作native窗体析构时 virtual float GetDPIScale(WId) const = 0; virtual用户主机是否只有一种DPI virtual void RefrushDPIRecords() = 0;//表明屏数量发生了变动 刷新历史表明屏DPI记录 virtual void SetDefault缩放值 只有当机器上大部份的表明屏为统一dpi时起作用 }; IDPIHelper * GetDPIHelper(); #define DPIHelper() GetDPIHelper() 1、ICallDPIChanged
dpi变动时回调类,当dpi发生改变时,通过该接口类中的ScaleChanged方法展开处置变动,比如说第一拍子中的TWidget类,他们也改写了这个接口,在该接口中他们对窗体进行了大小不一网络连接和产业布局网络连接
对象新闻稿中的表达式新闻稿采用了宏展开包装因而没直接表明出void TWidget::ScaleChanged(float scale) { DEFINTE_SCALE_RESIZE(Widget); if (m_pLayout) { m_pLayout->ScaleChanged(scale); } AdjustReiszeHandle();//如果窗体正在被拖曳需要网络连接拖曳的位置 } 2、IDPIHelper虽然篇幅原因,这儿把许多关键性同时实现节点列出
1、dpi变动入口
如下是dpi发生改变同时实现接口,表达式中干了三件事
首先监测dpi是否正在发生了变动,如果发生了变动则更新缓存中的window窗体的dpi缩放比接著读取window窗体中的qss标识生成新的qss样式字符串通告大部份悬浮窗体管理器,网络连接大部份悬浮窗体悬浮窗体指没产业布局的窗体,当悬浮窗体的父窗体dpi发生改变时,相应的悬浮窗体也需要展开网络连接bool CDPIHelper::DPIChanged(unsigned short dpi, WId id) { #ifndef HIGHDPI_ENABLE return false; #endif float scale = dpi / STANDARD_DPI; RefrushDPIRecords(); if (m_pWindowScale.contains(id)) { if (m_pWindowScale[id] == scale) { return false; } m_pWindowOldScale[id] = m_pWindowScale[id]; } m_pWindowScale[id] = scale; QWidget * window = QWidget::find(id); m_strQssFile = window->property(QSS_FIlE).toString(); if (m_strQssFile.isEmpty()) { m_strQssFile = DEFAULT_QSS_FILE; } else { if (m_strQssFile.endsWith(DEFAULT_QSS_SUFFIX) == false) { m_strQssFile.append(DEFAULT_QSS_SUFFIX); } } RefrushTimesSheet(Skin::TypeDefault, id); RefrushTimesSheet(Skin::TypeLight, id); CFloatingWidgetMgr::getInstance()->dpiChanged(id, scale); return true; }void CDPIHelper::RefrushTimesSheet(Skin::SKIN_TYPE skin, WId id) { float scale = GetDPIScale(id); int times = (int)(scale + 0.5001);//几倍图 //如果基础qss不存在 则需要从硬盘中读取 //读取时按照向上取整展开读取qss文件 //如果高分屏qss不存在 则读取一倍qss文件 if (m_StyleSheets[skin].size() < times) { m_StyleSheets[skin].resize(times); } std::wstring filePath = ImagePath::GetSkinFilePath(skin, m_strQssFile.toStdWString(), times); if (QFile::exists(QString::fromStdWString(filePath)) == false) { filePath = ImagePath::GetSkinFilePath(skin, m_strQssFile.toStdWString()); } QFile qss(QString::fromStdWString(filePath)); qss.open(QFile::ReadOnly); if (qss.isOpen()) { QString btnstylesheet = QObject::tr(qss.readAll()); m_StyleSheets[skin][times – 1][SCALE_ENLARGE(m_strQssFile, scale)] = btnstylesheet; qss.close(); } Q_ASSERT(m_StyleSheets[skin].size() > times – 1); //更新缓存中的换肤文件 m_StyleSheetMap[skin][SCALE_ENLARGE(m_strQssFile, scale)] = QtTigerHelper::ScaleSheet(m_StyleSheets[skin][times – 1][SCALE_ENLARGE(m_strQssFile, scale)], scale); } 3、悬浮窗体管理器
大多数的窗体都是在产业布局中顺利完成的,但是也有一小部分的询问处不在产业布局中,需要单独去网络连接,这个时候就需要采用CFloatingWidgetMgr产业布局管理器。
/** * 简介:悬浮询问处管理器 负责在DPI发生改变时通告悬浮询问处 全力支持如下类型的悬浮询问处: TFrame TPushButton TLabel TTableView TWidget TDialog TMainWindow */ class CFloatingWidgetMgr : public QObject { Q_OBJECT public: static CFloatingWidgetMgr * getInstance(); public: void addWidget(QWidget * widget); //dpi helper call void dpiChanged(WId id, float scale); private: QSet m_pWidgets; };
悬浮窗体网络连接高DPI也很简单,只需要把自己加入到悬浮窗体管理器中即可,是不是也很简单。
CFloatingWidgetMgr::getInstance()->addWidget(xxx); 三、计划预测
既然他们要改写Qt控件的非virtual接口,所以这个行为在C++语法上应该叫覆盖,要想调用他们覆盖的表达式,采用多态肯定是不行的,聪明的你肯定也想到了,他们在采用界面类时,只能采用T打头的控件类新闻稿对象,这样就会调用他们覆盖后的接口
上一则该文大致说过,要自网络连接高DPI他们需要网络连接四个项目,分别是询问处大小不一、字体大小不一、间距和图标,所以接下来就开始他们的预测操作过程
1、询问处大小不一
要网络连接软件询问处大小不一,他们总共需要改写如下14个和大小不一有关表达式,所以这只是大小不一有关的表达式,也就是QWidget的接口,其他更复杂的接口需要针对具体文本的类去改写
void resize(int w, int h);void resize(const QSize &); void setFixedHeight(int w); void setFixedWidth(int w);void setFixedSize(int w, int h);void setFixedSize(const QSize &s); void setMinimumSize(const QSize &);void setMinimumSize(int minw, int minh); void setMinimumHeight(int minh);void setMinimumWidth(int minw); void setMaximumSize(const QSize &);void setMaximumSize(int maxw, int maxh); void setMaximumHeight(int minh);void setMaximumWidth(int minw);
Qt的界面类我粗略估计了下,至少有几十个,如果每两个类都需要去网络连接,所以工作量可想而知,因而本栏想了两个办法,做了一连串宏,像上面标识符这样,只需要在他们想要网络连接的类中添加宏即可
//表达式新闻稿 #define DECLARE_RESIZE()\ void resize(int w, int h);void resize(const QSize &); void setFixedHeight(int w); \ void setFixedWidth(int w);void setFixedSize(int w, int h);void setFixedSize(const QSize &s);\ void setMinimumSize(const QSize &);void setMinimumSize(int minw, int minh);\ void setMinimumHeight(int minh);void setMinimumWidth(int minw);\ void setMaximumSize(const QSize &);void setMaximumSize(int maxw, int maxh);\ void setMaximumHeight(int minh);void setMaximumWidth(int minw);\
实际采用操作过程类似第一拍子那样,非常简单。
表达式新闻稿有了,接下来就是表达式同时实现,方法类似,本栏还是写了两个宏来网络连接有关弱化表达式,标识符下上面这样
//表达式同时实现 #define DEFINE_RESIZE(name)\ void T##name::resize(int w, int h){ m_sizeActions |= TigerUILib::RA_Resize; float scale = dpi_scale; m_size = QSize(w, h);;__super::resize(m_size.width() * scale, m_size.height() * scale);}\ void T##name::resize(const QSize & size){ m_sizeActions |= TigerUILib::RA_Resize; float scale = dpi_scale;m_size = size;__super::resize(m_size * scale);}\ void T##name::setFixedHeight(int h){m_sizeActions |= TigerUILib::RA_FixedHeight;float scale = dpi_scale;m_size.setHeight(h);__super::setFixedHeight(m_size.height() * scale);}\ void T##name::setFixedWidth(int w){m_sizeActions |= TigerUILib::RA_FixedWidth;float scale = dpi_scale;m_size.setWidth(w);__super::setFixedWidth(m_size.width() * scale);}\ void T##name::setFixedSize(int w, int h){m_sizeActions |= TigerUILib::RA_FixedSize;float scale = dpi_scale; m_size = QSize(w, h); __super::setFixedSize(m_size.width() * scale, m_size.height() * scale);}\ void T##name::setFixedSize(const QSize & size){m_sizeActions |= TigerUILib::RA_FixedSize;float scale = dpi_scale; m_size = size; __super::setFixedSize(m_size * scale);}\ void T##name::setMinimumSize(const QSize & size){m_sizeActions |= TigerUILib::RA_MinimumSize;float scale = dpi_scale;m_minimumSize = size; __super::setMinimumSize(m_minimumSize * scale);}\ void T##name::setMinimumSize(int w, int h){m_sizeActions |= TigerUILib::RA_MinimumSize;float scale = dpi_scale; m_minimumSize = QSize(w, h); __super::setMinimumSize(m_minimumSize.width() * scale, m_minimumSize.height() * scale);}\ void T##name::setMinimumHeight(int h){m_sizeActions |= TigerUILib::RA_MinimumHeight;float scale = dpi_scale;m_minimumSize.setHeight(h); __super::setMinimumHeight(m_minimumSize.height() * scale);}\ void T##name::setMinimumWidth(int w){m_sizeActions |= TigerUILib::RA_MinimumWidth;float scale = dpi_scale; m_minimumSize.setWidth(w); __super::setMinimumWidth(m_minimumSize.width() * scale);}\ void T##name::setMaximumSize(const QSize & size){m_sizeActions |= TigerUILib::RA_MaximumSize;float scale = dpi_scale; m_maximumSize = size; __super::setMaximumSize(m_maximumSize * scale);}\ void T##name::setMaximumSize(int w, int h){m_sizeActions |= TigerUILib::RA_MaximumSize;float scale = dpi_scale; m_maximumSize = QSize(w, h); __super::setMaximumSize(m_maximumSize.width() * scale, m_maximumSize.height() * scale);}\ void T##name::setMaximumHeight(int h){m_sizeActions |= TigerUILib::RA_MaximumHeight;float scale = dpi_scale; m_maximumSize.setHeight(h); __super::setMaximumHeight(m_maximumSize.height() * scale);}\ void T##name::setMaximumWidth(int w){m_sizeActions |= TigerUILib::RA_MaximumWidth;float scale = dpi_scale; m_maximumSize.setWidth(w); __super::setMaximumWidth(m_maximumSize.width() * scale);}
动态调整
仔细写作DEFINE_RESIZE宏中的任意两个表达式,就能发现每两个表达式中都有两个TigerUILib::WidgetAction标记,表示该对象的此表达式是否被调用过,标记之后有两个好处,那就是当他们软件所在屏幕的DPI发生改变时能有针对性的去调用有关表达式,上面是两个简单的测试标识符。
if (testflag(“setfixedWidth”)) { setFixedWidth(width * scale); }
说到这儿有必要介绍下DEFINTE_SCALE_RESIZE宏,如下标识符,就不解释了一看应该都会明白
#define DEFINTE_SCALE_RESIZE(name)\ if (m_sizeActions.testFlag(TigerUILib::RA_FixedWidth)){Q##name::setFixedWidth(m_size.width() * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_FixedHeight)){Q##name::setFixedHeight(m_size.height() * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_FixedSize)){Q##name::setFixedSize(m_size * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_Resize)){ QSize newSize = m_size * scale;if(minimumSize().width() > newSize.width()){Q##name::setMinimumSize(newSize);}Q##name::resize(newSize);}\ if (m_sizeActions.testFlag(TigerUILib::RA_MinimumSize)){Q##name::setMinimumSize(m_minimumSize * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_MinimumHeight)){Q##name::setMinimumHeight(m_minimumSize.height() * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_MinimumWidth)){Q##name::setMinimumWidth(m_minimumSize.width() * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_MaximumSize)){Q##name::setMaximumSize(m_maximumSize * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_MaximumHeight)){Q##name::setMaximumHeight(m_maximumSize.height() * scale);}\ if (m_sizeActions.testFlag(TigerUILib::RA_MaximumWidth)){ Q##name::setMaximumWidth(m_maximumSize.width() * scale); }\ dpi_scale = scale; 2、字体大小不一
Qt程序他们的字体大小不一都是在qss文件中展开标记,所以网络连接高DPI也就很简单了,只需要把96dpi下的数字大小不一按比例展开弱化即可。
知道方法后,做起来就很简单了,只需要写两个字符串替换表达式,把qss中的数值按比例弱化即可,方法如下。
数值弱化时有两个小技巧,那就是要做两个平滑处置,1.49px当做1px处置 1.5px当做2px,意思就是说在做数字当大的操作过程中,可能会出现小数,他们的原则是数值弱化后加上0.50001然后取整数部分。QString QtTigerHelper::ScaleSheet(const QString & sheet, float scale) { if (sheet.isEmpty()) { return sheet; } //1倍图时不需要做任何处置 if (scale == 1.0) { return sheet; } //弱化字体 QString tempStyle = sheet; QRegExp rx(“\\d+px”, Qt::CaseInsensitive); rx.setMinimal(true); int index = -1; while ((index = rx.indexIn(tempStyle, index + 1)) >= 0) { int capLen = rx.cap(0).length() – 2; QString snum = tempStyle.mid(index, capLen); snum = QString::number(qRound(snum.toInt() * scale)); tempStyle.replace(index, capLen, snum); index += snum.length(); if (index > tempStyle.size() – 2) { break; } } return tempStyle; } 3、间距Qt中的产业布局有2中方式能设置,能在标识符中通过接口设置,也可以通过qss展开设置,当然了这两种情况都需要网络连接。
产业布局的margin
记录调用了哪些设置大小不一的表达式,在dpi发生改变时重新设置一遍,类似于询问处大小不一变动时所作调整
if (testflag(“margin”)) { setContextMargin(…); }
padding和margin
方式和弱化字体一样,能通过统一的时机去处置读取原有qss文件,采用正则表达式生成scale版本的新qss文件。
4、图标
图标替换是两个相对来说较为复杂的事情,这儿有必要细说一下。
首先是工程中需要额外添加2x和3x分辨率的图标,1x图标为正常情况下采用的图标,2x和3x图标分别是高分辨率下的图标
替换图标有两种情况,一种是采用qss方式贴的图,另一种是自绘贴的图
qss方式
预先生成高分辨率下的整数倍xxx_2x.qss和xxx_3x.qss文件,需要强调一下,2x和3xqss文件中的字号还是一倍程序中的字号,实际使用的时候在动态弱化,如果想要程序的效率高许多可能还需要做许多缓存
自绘
如果是自绘文字和图片,那就需要自己控制缩放比,和图片压缩系数
缩放比:绘制文字时需要弱化的比例,计算方式为当前dpi值除以96.0,结果是两个浮点数,比如说1.5
压缩系数: 绘制图片的时候这儿有两个小窍门,当他们绘制缩放比为小数情况时,需要采用距离较近的整数图片展开压缩绘制,这样的情况他们就需要采用压缩系数展开动态调整绘制图片的大小不一
float ImagePath::GetStretchFactor(float scale) { if (scale < 1.5) { return scale; } else if (scale < 2.5) { return scale / 2; } else if (scale < 3.5) { return scale / 3; } else//缺省为3倍图拉伸 { return scale / 3; } }
以上就是DPI网络连接计划的大致思路了,因为篇幅原因没针对每两个widget和layout展开详细说明,有需要的能私聊。
四、有关该文